diff --git a/.github/workflows/code_testing.yml b/.github/workflows/code_testing.yml index 5990f72..31e7bf8 100644 --- a/.github/workflows/code_testing.yml +++ b/.github/workflows/code_testing.yml @@ -62,7 +62,7 @@ jobs: uses: dice-group/cpp-conan-release-reusable-workflow/.github/actions/add_conan_provider@main - name: Configure CMake - run: cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=On -DBUILD_EXAMPLES=On -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -G Ninja -B build . + run: cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=On -DWITH_SODIUM=On -DBUILD_EXAMPLES=On -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -G Ninja -B build . env: CC: ${{ steps.install_cc.outputs.cc }} CXX: ${{ steps.install_cc.outputs.cxx }} @@ -74,7 +74,7 @@ jobs: - name: Run tests working-directory: build - run: ctest --verbose -j2 + run: ctest --verbose -j2 -E ".*Benchmark.*" - name: Run examples working-directory: build diff --git a/.github/workflows/detect-pobr-diff.yml b/.github/workflows/detect-pobr-diff.yml index 179adeb..3f8aaf0 100644 --- a/.github/workflows/detect-pobr-diff.yml +++ b/.github/workflows/detect-pobr-diff.yml @@ -17,6 +17,8 @@ jobs: base-branch: ${{ github.base_ref }} search-path: > include/dice/hash/internal + include/dice/hash/blake + include/dice/hash/lthash abi-version-header: include/dice/hash/version.hpp abi-version-const: dice::hash::pobr_version secrets: diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 0997b1f..5392482 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -17,7 +17,7 @@ jobs: with: public_artifactory: true os: ubuntu-22.04 - compiler: clang-14 + compiler: clang-17 cmake-version: 3.24.0 conan-version: 2.3.0 secrets: diff --git a/CMakeLists.txt b/CMakeLists.txt index abc4e63..83ffdf7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,14 @@ cmake_minimum_required(VERSION 3.24) project( dice-hash - VERSION 0.4.8 + VERSION 0.4.9 DESCRIPTION "dice-hash provides a framework to generate stable hashes. It provides state-of-the-art hash functions, supports STL containers out of the box and helps you to defines stable hashes for your own structs and classes." HOMEPAGE_URL "https://dice-group.github.io/dice-hash/") set(POBR_VERSION 1) # Persisted Object Binary Representation Version +include(cmake/boilerplate_init.cmake) +boilerplate_init() + # set gcc-10 and clang-10 as minimum versions see # https://stackoverflow.com/questions/14933172/how-can-i-add-a-minimum-compiler-version-requisite#14934542 set(MIN_COMPILER_VERSION_GCC "10.0.0") @@ -27,21 +30,59 @@ endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.hpp.in ${CMAKE_CURRENT_SOURCE_DIR}/include/dice/hash/version.hpp) +OPTION(WITH_SODIUM "Enable usage of the external library sodium for Blake2b, Blake2Xb and LtHash support" OFF) + if (PROJECT_IS_TOP_LEVEL) if (BUILD_TESTING) set(CONAN_INSTALL_ARGS "${CONAN_INSTALL_ARGS};-o=&:with_test_deps=True") endif () + if (WITH_SODIUM) + set(CONAN_INSTALL_ARGS "${CONAN_INSTALL_ARGS};-o=&:with_sodium=True") + endif () endif () -add_library(${PROJECT_NAME} INTERFACE) -add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) +if (WITH_SODIUM) + find_package(libsodium REQUIRED) + find_package(highway REQUIRED) +endif () + +add_subdirectory(include/dice/hash/blake/internal/blake3) -target_include_directories( - ${PROJECT_NAME} - INTERFACE $) +if (WITH_SODIUM) + add_library(${PROJECT_NAME} + include/dice/hash/lthash/MathEngine_Hwy.cpp + ) + + target_include_directories( + ${PROJECT_NAME} + PUBLIC + $ + ) + target_link_libraries(${PROJECT_NAME} + PUBLIC + libsodium::libsodium + blake3 + PRIVATE + highway::highway + ) +else() + add_library(${PROJECT_NAME} INTERFACE) + + target_include_directories( + ${PROJECT_NAME} + INTERFACE + $ + ) + target_link_libraries(${PROJECT_NAME} + INTERFACE + blake3 + ) +endif() + +add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -include(cmake/install_interface_library.cmake) -install_interface_library(${PROJECT_NAME} "include") +include(cmake/install_library.cmake) +install_cpp_library(${PROJECT_NAME} "include") if(PROJECT_IS_TOP_LEVEL AND BUILD_TESTING) include(CTest) diff --git a/LICENSE b/LICENSE deleted file mode 100644 index fc5c4b1..0000000 --- a/LICENSE +++ /dev/null @@ -1,662 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - dice-hash: a general purpose high-performance hash for C++ STL - Copyright (C) 2018 - today Datascience Group, Universität Paderborn, - Warburger Str. 100, 33098 Paderborn, Germany - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..68ffa85 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Data Science Group at UPB + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 08fbba1..123c284 100644 --- a/README.md +++ b/README.md @@ -7,17 +7,19 @@ dice-hash provides a framework to generate stable hashes. It provides state-of-t - [wyhash](https://github.com/wangyi-fudan/wyhash) - "martinus", the internal hash function from [robin-hood-hashing](https://github.com/martinus/robin-hood-hashing) +These three, additional, general purpose hash functions are also (optionally) provided +- [Blake2b](https://www.blake2.net) +- [Blake2Xb](https://www.blake2.net/blake2x.pdf) +- [LtHash](https://engineering.fb.com/2019/03/01/security/homomorphic-hashing) + **📦 STL out of the box:** dice-hash supports many common STL types already: arithmetic types like `bool`, `int`, `double`, ... etc.; collections like `std::unordered_map/set`, `std::map/set`, `std::vector`, `std::tuple`, `std::pair`, `std::optional`, `std::variant`, `std::array` and; all combinations of them. -**🔩 extensible:** dice-hash supports you with helper functions to define hashes for your own classes. Checkout [usage](#usage). - - - +**🔩 extensible:** dice-hash supports you with helper functions to define hashes for your own classes. Checkout [usage](#usage). ## Requirements - -A C++20 compatible compiler. Code was only tested on x86_64. +- A C++20 compatible compiler. Code was only tested on x86_64. +- If you want to use [Blake2b](https://www.blake2.net), [Blake2Xb](https://www.blake2.net/blake2x.pdf) or [LtHash](https://engineering.fb.com/2019/03/01/security/homomorphic-hashing): [libsodium](https://doc.libsodium.org/) (either using conan or a local system installation) (for more details scroll down to "Usage for general data hashing") ## Include it into your projects @@ -29,7 +31,7 @@ To use it with [conan](https://conan.io/) you need to add the repository: conan remote add dice-group https://conan.dice-research.org/artifactory/api/conan/tentris ``` -To use it add `dice-hash/0.4.8` to the `[requires]` section of your conan file. +To use it add `dice-hash/0.4.9` to the `[requires]` section of your conan file. You can now add it to your target with: ```cmake @@ -55,13 +57,14 @@ make -j tests_dice_hash Note: This example uses conan as dependency provider, other providers are possible. See https://cmake.org/cmake/help/latest/guide/using-dependencies/index.html#dependency-providers -## usage +## Usage for C++ container hashing You need to include a single header: ```c++ #include ``` The hash is already defined for a lot of common types. In that case you can use the `DiceHash` just like `std::hash`. +This means these hashes return `size_t`, if you need larger hashes skip to the section below. ```c++ dice::hash::DiceHash hash; hash(42); @@ -104,3 +107,52 @@ One simple example can be found [here](examples/customContainer.cpp). If you want to use `DiceHash` in a different structure (like `std::unordered_map`), you will need to set `DiceHash` as the correct template parameter. [This](examples/usageForUnorderedSet.cpp) is one example. + +## Usage for general data hashing +**The hash functions mentioned in this section are enabled/disabled using the feature flag `WITH_SODIUM=ON/OFF`.** +**Enabling this flag (default behaviour) results in [libsodium](https://doc.libsodium.org/) being required as a dependency.** +**If using conan, [libsodium](https://doc.libsodium.org/) will be fetched using conan, otherwise dice-hash will look for a local system installation.** + +The hashes mentioned here are not meant to be used in C++ containers as they do _not_ return `size_t`. +They are instead meant as general hashing functions for arbitrary data. + +### [Blake2b](https://www.blake2.net/) - ["fast secure hashing"](https://www.blake2.net/) (with output sizes from 16 bytes up to 64 bytes) +["BLAKE2 is a cryptographic hash function faster than MD5, SHA-1, SHA-2, and SHA-3, yet is at least as secure as the latest standard SHA-3."](https://www.blake2.net/) + +To use it you need to include +```c++ +#include +``` +For a usage examples see: [examples/blake2b.cpp](examples/blake2b.cpp). + +### [Blake2Xb](https://www.blake2.net/blake2x.pdf) - arbitrary length hashing based on [Blake2b](https://www.blake2.net/) +Blake2Xb is a hash function that produces hashes of arbitrary length. + +To use it you need to include +```c++ +#include +``` +For a usage examples see: [examples/blake2xb.cpp](examples/blake2xb.cpp). + +### [Blake3](https://github.com/BLAKE3-team/BLAKE3-specs/blob/master/blake3.pdf) - one function, fast everywhere +Blake3 is an evolution of Blake2. + +To use it you need to include +```c++ +#include +``` +For a usage examples see: [examples/blake3.cpp](examples/blake3.cpp). + +### [LtHash](https://engineering.fb.com/2019/03/01/security/homomorphic-hashing/) - homomorphic/multiset hashing +LtHash is a multiset/homomorphic hash function, meaning, instead of working on streams of data, it digests +individual "objects". This means you can add and remove "objects" to/from an `LtHash` (object by object) +as if it were a multiset and then read the hash that would result from hashing that multiset. + +Small non-code example that shows the basic principle: +> LtHash({apple}) + LtHash({banana}) - LtHash({peach}) + LtHash({banana}) = LtHash({apple1, banana2, peach-1}) + +To use it you need to include +```c++ +#include +``` +For a usage example see [examples/ltHash.cpp](examples/ltHash.cpp). diff --git a/cmake/boilerplate_init.cmake b/cmake/boilerplate_init.cmake new file mode 100644 index 0000000..2873601 --- /dev/null +++ b/cmake/boilerplate_init.cmake @@ -0,0 +1,28 @@ +macro(boilerplate_init) + ## enforce standard compliance + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) + set(CMAKE_CXX_STANDARD 20) + + ## C++ compiler flags + if (MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Wall") + else () + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wcast-qual") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0") + endif () + + ## C++ language visibility configuration + if (NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET AND + NOT DEFINED CMAKE_VISIBILITY_INLINES_HIDDEN) + set(CMAKE_CXX_VISIBILITY_PRESET default) + set(CMAKE_VISIBILITY_INLINES_HIDDEN NO) + endif () + + # conan requires cmake build type to be specified and it is generally a good idea + if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/CMakeCache.txt) + if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE) + endif () + endif () +endmacro() diff --git a/cmake/install_interface_library.cmake b/cmake/install_interface_library.cmake deleted file mode 100644 index 788d352..0000000 --- a/cmake/install_interface_library.cmake +++ /dev/null @@ -1,41 +0,0 @@ -include(GNUInstallDirs) -include(CMakePackageConfigHelpers) - -function(install_interface_library LIBRARY_NAME INCLUDE_PATH) - - target_include_directories( - ${LIBRARY_NAME} INTERFACE $) - - set_property(TARGET ${LIBRARY_NAME} PROPERTY EXPORT_NAME ${LIBRARY_NAME}) - - install( - TARGETS ${LIBRARY_NAME} - EXPORT ${LIBRARY_NAME}-targets - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) - - - write_basic_package_version_file( - "${LIBRARY_NAME}-config-version.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion) - - configure_package_config_file( - "${PROJECT_SOURCE_DIR}/cmake/dummy-config.cmake.in" - "${PROJECT_BINARY_DIR}/${LIBRARY_NAME}-config.cmake" - INSTALL_DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) - - install( - EXPORT ${LIBRARY_NAME}-targets - FILE ${LIBRARY_NAME}-targets.cmake - NAMESPACE ${PROJECT_NAME}:: - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) - - install(FILES "${PROJECT_BINARY_DIR}/${LIBRARY_NAME}-config.cmake" - "${PROJECT_BINARY_DIR}/${LIBRARY_NAME}-config-version.cmake" - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) - - install(DIRECTORY ${PROJECT_SOURCE_DIR}/${INCLUDE_PATH}/ DESTINATION include) -endfunction() diff --git a/cmake/install_library.cmake b/cmake/install_library.cmake new file mode 100644 index 0000000..7605cf6 --- /dev/null +++ b/cmake/install_library.cmake @@ -0,0 +1,46 @@ +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +function(install_cpp_library TARGET_NAME INCLUDE_PATH) + if (NOT ${ARGC} GREATER_EQUAL 2) + message( + FATAL_ERROR + "you did not specify the target and the include path in the parameter!") + endif () + + target_include_directories( + ${TARGET_NAME} INTERFACE $) + + install(TARGETS ${TARGET_NAME} ${ARGN} + EXPORT ${TARGET_NAME}-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) + + write_basic_package_version_file("${TARGET_NAME}-config-version.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY ExactVersion) + + configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/lib-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) + # here we have two possibilities: either CMAKE_INSTALL_DATAROOTDIR (share) or CMAKE_INSTALL_LIBDIR (lib/lib64) + # we just have to be consistent for one target + + install( + EXPORT ${PROJECT_NAME}-targets + FILE ${PROJECT_NAME}-targets.cmake + NAMESPACE ${PROJECT_NAME}:: + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) + + install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" + "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) + + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_PATH}/ + DESTINATION include + FILES_MATCHING PATTERN "*.hpp" PATTERN "*.h") +endfunction() \ No newline at end of file diff --git a/cmake/lib-config.cmake.in b/cmake/lib-config.cmake.in new file mode 100644 index 0000000..fa2a9df --- /dev/null +++ b/cmake/lib-config.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") +check_required_components("@PROJECT_NAME@") \ No newline at end of file diff --git a/conanfile.py b/conanfile.py index 5001c85..758049e 100644 --- a/conanfile.py +++ b/conanfile.py @@ -11,18 +11,35 @@ class DiceHashConan(ConanFile): author = "DICE Group " homepage = "https://github.com/dice-group/dice-hash" url = homepage - topics = ("hash", "wyhash", "xxh3", "robin-hood-hash", "C++", "C++20") - settings = "build_type", "compiler", "os", "arch" - generators = ("CMakeDeps", "CMakeToolchain") - exports = "LICENSE" - exports_sources = "include/*", "CMakeLists.txt", "cmake/*", "LICENSE" + topics = ("hash", "wyhash", "xxh3", "robin-hood-hash", "Blake2b", "Blake2Xb", "LtHash", "C++", "C++20") + + settings = "os", "compiler", "build_type", "arch" options = { + "shared": [True, False], + "fPIC": [True, False], "with_test_deps": [True, False], + "with_sodium": [True, False] } default_options = { + "shared": False, + "fPIC": False, "with_test_deps": False, + "with_sodium": False } + exports_sources = "include/*", "CMakeLists.txt", "cmake/*", "LICENSE-MIT", "LICENSE-APACHE" + + generators = ("CMakeDeps", "CMakeToolchain") + + def requirements(self): + if self.options.with_sodium: + self.requires("libsodium/cci.20220430", transitive_headers=True) + self.requires("highway/1.2.0") + + if self.options.with_test_deps: + self.test_requires("catch2/3.3.2") + self.test_requires("metall/0.23.1") + def set_name(self): if not hasattr(self, 'name') or self.version is None: cmake_file = load(self, os.path.join(self.recipe_folder, "CMakeLists.txt")) @@ -36,34 +53,38 @@ def set_version(self): cmake_file = load(self, os.path.join(self.recipe_folder, "CMakeLists.txt")) self.description = re.search(r"project\([^)]*DESCRIPTION\s+\"([^\"]+)\"[^)]*\)", cmake_file).group(1) - def requirements(self): - if self.options.with_test_deps: - self.test_requires("catch2/2.13.9") - def layout(self): cmake_layout(self) - def build(self): - cmake = CMake(self) - cmake.configure() - cmake.build() + _cmake = None + + def _configure_cmake(self): + if self._cmake is None: + self._cmake = CMake(self) + self._cmake.configure(variables={"WITH_SODIUM": self.options.with_sodium}) - def package_id(self): - self.info.clear() + return self._cmake + + def build(self): + self._configure_cmake().build() def package(self): - cmake = CMake(self) - cmake.install() + self._configure_cmake().install() for dir in ("lib", "res", "share"): rmdir(self, os.path.join(self.package_folder, dir)) - copy(self, pattern="LICENSE*", dst="licenses", src=self.folders.source_folder) + rmdir(self, os.path.join(self.package_folder, "cmake")) + rmdir(self, os.path.join(self.package_folder, "share")) + copy(self, pattern="LICENSE*", dst=os.path.join(self.package_folder, "licenses"), src=self.folders.source_folder) + copy(self, pattern="*.a", src=os.path.join(self.build_folder, "include/dice/hash/blake/internal/blake3"), dst=os.path.join(self.package_folder, "lib"), keep_path=False) + copy(self, pattern="*.a", src=self.build_folder, dst=os.path.join(self.package_folder, "lib"), keep_path=False) def package_info(self): - self.cpp_info.bindirs = [] - self.cpp_info.libdirs = [] - self.cpp_info.set_property("cmake_find_mode", "both") self.cpp_info.set_property("cmake_target_name", "dice-hash::dice-hash") self.cpp_info.set_property("cmake_file_name", "dice-hash") + + if self.options.with_sodium: + self.cpp_info.libs += ["dice-hash", "blake3"] + self.cpp_info.requires += ["libsodium::libsodium", "highway::highway"] diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 0621aff..1f234a5 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -2,37 +2,59 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_EXTENSIONS OFF) -add_executable(basicUsage basicUsage.cpp) -target_link_libraries(basicUsage PRIVATE +add_executable(example_basicUsage basicUsage.cpp) +target_link_libraries(example_basicUsage PRIVATE dice-hash::dice-hash ) -add_executable(policyUsage policyUsage.cpp) -target_link_libraries(policyUsage PRIVATE +add_executable(example_policyUsage policyUsage.cpp) +target_link_libraries(example_policyUsage PRIVATE dice-hash::dice-hash ) -add_executable(customType customType.cpp) -target_link_libraries(customType PRIVATE +add_executable(example_customType customType.cpp) +target_link_libraries(example_customType PRIVATE dice-hash::dice-hash ) -add_executable(customPolicy customPolicy.cpp) -target_link_libraries(customPolicy PRIVATE +add_executable(example_customPolicy customPolicy.cpp) +target_link_libraries(example_customPolicy PRIVATE dice-hash::dice-hash ) -add_executable(customContainer customContainer.cpp) -target_link_libraries(customContainer PRIVATE +add_executable(example_customContainer customContainer.cpp) +target_link_libraries(example_customContainer PRIVATE dice-hash::dice-hash ) -add_executable(usageForUnorderedSet usageForUnorderedSet.cpp) -target_link_libraries(usageForUnorderedSet PRIVATE +add_executable(example_usageForUnorderedSet usageForUnorderedSet.cpp) +target_link_libraries(example_usageForUnorderedSet PRIVATE dice-hash::dice-hash ) -add_executable(combineHashes combineHashes.cpp) -target_link_libraries(combineHashes PRIVATE +add_executable(example_combineHashes combineHashes.cpp) +target_link_libraries(example_combineHashes PRIVATE dice-hash::dice-hash ) + +if (WITH_SODIUM) + add_executable(example_blake2b blake2b.cpp) + target_link_libraries(example_blake2b PRIVATE + dice-hash::dice-hash + ) + + add_executable(example_blake2xb blake2xb.cpp) + target_link_libraries(example_blake2xb PRIVATE + dice-hash::dice-hash + ) + + add_executable(example_ltHash ltHash.cpp) + target_link_libraries(example_ltHash PRIVATE + dice-hash::dice-hash + ) +endif () + +add_executable(example_blake3 blake3.cpp) +target_link_libraries(example_blake3 PRIVATE + dice-hash::dice-hash +) diff --git a/examples/blake2b.cpp b/examples/blake2b.cpp new file mode 100644 index 0000000..40696b3 --- /dev/null +++ b/examples/blake2b.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include +#include + +void print_bytes(std::span bytes) noexcept { + for (auto b : bytes) { + std::cout << std::hex << static_cast(b); + } + std::cout << "\n\n"; +} + +int main() { + using namespace std::string_view_literals; + using namespace dice::hash::blake2b; + + auto data1 = as_bytes(std::span{"spherical cow"sv}); + auto data2 = as_bytes(std::span{"hello world"sv}); + auto data3 = as_bytes(std::span{"penguins"sv}); + + { // stateful hashing + Blake2b blake{max_output_extent}; + blake.digest(data1); + blake.digest(data2); + + std::vector output; + output.resize(max_output_extent); + + std::move(blake).finish(output); + + print_bytes(output); + } + + { // one-off hashing + std::vector output; + output.resize(32); + Blake2b<>::hash_single(data3, output); + + print_bytes(output); + } +} diff --git a/examples/blake2xb.cpp b/examples/blake2xb.cpp new file mode 100644 index 0000000..6247d90 --- /dev/null +++ b/examples/blake2xb.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include +#include + +void print_bytes(std::span bytes) noexcept { + for (auto b : bytes) { + std::cout << std::hex << static_cast(b); + } + std::cout << "\n\n"; +} + +int main() { + using namespace std::string_view_literals; + using namespace dice::hash::blake2xb; + + auto data1 = as_bytes(std::span{"spherical cow"sv}); + auto data2 = as_bytes(std::span{"hello world"sv}); + auto data3 = as_bytes(std::span{"penguins"sv}); + + { // stateful hashing + Blake2Xb blake; + blake.digest(data1); + blake.digest(data2); + + std::vector output; + output.resize(789); + + std::move(blake).finish(output); + + print_bytes(output); + } + + { // one-off hashing + std::vector output; + output.resize(58); + Blake2Xb<>::hash_single(data3, output); + + print_bytes(output); + } +} diff --git a/examples/blake3.cpp b/examples/blake3.cpp new file mode 100644 index 0000000..47631e0 --- /dev/null +++ b/examples/blake3.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include +#include + +void print_bytes(std::span bytes) noexcept { + for (auto b : bytes) { + std::cout << std::hex << static_cast(b); + } + std::cout << "\n\n"; +} + +int main() { + using namespace std::string_view_literals; + using namespace dice::hash::blake3; + + auto data1 = as_bytes(std::span{"spherical cow"sv}); + auto data2 = as_bytes(std::span{"hello world"sv}); + auto data3 = as_bytes(std::span{"penguins"sv}); + + { // stateful hashing + Blake3 blake; + blake.digest(data1); + blake.digest(data2); + + std::vector output; + output.resize(789); + + std::move(blake).finish(output); + + print_bytes(output); + } + + { // one-off hashing + std::vector output; + output.resize(58); + Blake3<>::hash_single(data3, output); + + print_bytes(output); + } +} diff --git a/examples/customPolicy.cpp b/examples/customPolicy.cpp index 0d5ad1d..19772eb 100644 --- a/examples/customPolicy.cpp +++ b/examples/customPolicy.cpp @@ -11,7 +11,7 @@ struct MyCustomPolicy { static std::size_t hash_fundamental(T x) noexcept { return static_cast(42 * x); } - static std::size_t hash_bytes(void const *ptr, std::size_t len) noexcept { + static std::size_t hash_bytes([[maybe_unused]] void const *ptr, std::size_t len) noexcept { return len; } static std::size_t hash_combine(std::initializer_list hashes) noexcept { @@ -26,7 +26,7 @@ struct MyCustomPolicy { std::size_t result = 0; //some Hashstates need to know how many elements need to be hashed public: - explicit HashState(std::size_t size) noexcept {} + explicit HashState([[maybe_unused]] std::size_t size) noexcept {} void add(std::size_t hash) noexcept { result = result xor hash; } diff --git a/examples/customType.cpp b/examples/customType.cpp index 6f463c5..690fb6a 100644 --- a/examples/customType.cpp +++ b/examples/customType.cpp @@ -13,7 +13,7 @@ class MyCustomClass { // With this, the DiceHash can work on private data template - friend class dice::hash::dice_hash_overload; + friend struct dice::hash::dice_hash_overload; }; namespace dice::hash { diff --git a/examples/ltHash.cpp b/examples/ltHash.cpp new file mode 100644 index 0000000..2fb24d2 --- /dev/null +++ b/examples/ltHash.cpp @@ -0,0 +1,40 @@ +#include + +#include +#include +#include +#include + +void print_bytes(std::span bytes) noexcept { + for (auto b : bytes) { + std::cout << std::hex << static_cast(b); + } + std::cout << "\n\n"; +} + +int main() { + using namespace dice::hash::lthash; + using namespace std::string_view_literals; + + auto obj1 = as_bytes(std::span{"spherical cow"sv}); + auto obj2 = as_bytes(std::span{"hello world"sv}); + + LtHash16 lthash; + print_bytes(lthash.checksum()); + + lthash.add(obj1); + std::vector const checksum1{lthash.checksum().begin(), lthash.checksum().end()}; + print_bytes(checksum1); + + lthash.add(obj2); + std::vector const checksum2{lthash.checksum().begin(), lthash.checksum().end()}; + print_bytes(checksum2); + + assert(checksum1 != checksum2); + + lthash.remove(obj2); + std::vector const checksum3{lthash.checksum().begin(), lthash.checksum().end()}; + print_bytes(checksum3); + + assert(checksum3 == checksum1); +} diff --git a/include/dice/hash/DiceHash.hpp b/include/dice/hash/DiceHash.hpp index 75557b3..c5c1a21 100644 --- a/include/dice/hash/DiceHash.hpp +++ b/include/dice/hash/DiceHash.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -113,6 +114,9 @@ namespace dice::hash { return Policy::hash_combine({dice_hash(std::get(tuple))...}); } + template + static constexpr bool is_fundamental = std::is_fundamental_v || std::is_same_v, std::byte>; + public: /** Base case for dice_hash. * This case is only chosen if no other match is found in this struct. @@ -132,7 +136,7 @@ namespace dice::hash { * @return Hash value. */ template - requires std::is_fundamental_v> static std::size_t dice_hash(T const &fundamental) noexcept { + requires is_fundamental> static std::size_t dice_hash(T const &fundamental) noexcept { return Policy::hash_fundamental(fundamental); } @@ -198,7 +202,7 @@ namespace dice::hash { */ template static std::size_t dice_hash(std::array const &arr) noexcept { - if constexpr (std::is_fundamental_v) { + if constexpr (is_fundamental) { return Policy::hash_bytes(arr.data(), sizeof(T) * N); } else { return dice_hash_ordered_container(arr); @@ -213,15 +217,28 @@ namespace dice::hash { */ template static std::size_t dice_hash(std::vector const &vec) noexcept { - if constexpr (std::is_fundamental_v) { + if constexpr (is_fundamental) { static_assert(!std::is_same_v, bool>, - "vector of booleans has a special implementation which results into errors!"); + "vector of booleans has a special implementation which results in errors!"); return Policy::hash_bytes(vec.data(), sizeof(T) * vec.size()); } else { return dice_hash_ordered_container(vec); } } + /** Implementation for byte spans + * @param bytes byte span to hash + * @return Hash value. + */ + template + static std::size_t dice_hash(std::span const &span) noexcept { + if constexpr (is_fundamental) { + return Policy::hash_bytes(span.data(), span.size_bytes()); + } else { + return dice_hash_ordered_container(span); + } + } + /** Implementation for tuples. * Will hash every entry and then combine the hashes. * @tparam TupleArgs The types of the tuple values. diff --git a/include/dice/hash/blake/Blake2Xb.hpp b/include/dice/hash/blake/Blake2Xb.hpp new file mode 100644 index 0000000..0262ad9 --- /dev/null +++ b/include/dice/hash/blake/Blake2Xb.hpp @@ -0,0 +1,335 @@ +#ifndef DICE_HASH_BLAKE2XB_HPP +#define DICE_HASH_BLAKE2XB_HPP + +/** + * @brief Implementation of Blake2Xb (https://www.blake2.net/blake2x.pdf) + * @note Implementation adapted from https://github.com/facebook/folly/blob/main/folly/experimental/crypto/Blake2Xb.h + */ + +#if __has_include() + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace dice::hash::blake2xb { + + namespace detail { + template + inline std::byte const *byte_iter(T const &value) noexcept { + return reinterpret_cast(&value); + } + + template + inline std::byte *byte_iter_mut(T &value) noexcept { + return reinterpret_cast(&value); + } + + template + inline T little_endian(T const &value) noexcept { + if constexpr (std::endian::native == std::endian::little) { + return value; + } else { + auto const *input_beg = byte_iter(value); + auto const *input_end = input_beg + sizeof(T); + T output; + + std::reverse_copy(input_beg, input_end, byte_iter_mut(output)); + return output; + } + } + } // namespace detail + + inline constexpr size_t unknown_output_extent = 0; + inline constexpr size_t min_output_extent = 1; + inline constexpr size_t max_output_extent = std::numeric_limits::max() - 1; + using ::dice::hash::blake2b::dynamic_output_extent; + + using ::dice::hash::blake2b::salt_extent; + using ::dice::hash::blake2b::default_salt; + + using ::dice::hash::blake2b::personality_extent; + using ::dice::hash::blake2b::default_personality; + + using ::dice::hash::blake2b::min_key_extent; + using ::dice::hash::blake2b::max_key_extent; + using ::dice::hash::blake2b::default_key_extent; + + using ::dice::hash::blake2b::generate_key; + + /** + * @brief Blake2Xb ported from folly::experimental::crypto + */ + template + requires (OutputExtent == dynamic_output_extent || (OutputExtent >= min_output_extent && OutputExtent <= max_output_extent)) + struct Blake2Xb { + /** + * @brief if known at compile time, the size of the resulting hash, otherwise dynamic_output_extent + */ + static constexpr size_t output_extent = OutputExtent; + + static constexpr size_t min_key_extent = ::dice::hash::blake2xb::min_key_extent; + static constexpr size_t max_key_extent = ::dice::hash::blake2xb::max_key_extent; + static constexpr size_t default_key_extent = ::dice::hash::blake2xb::default_key_extent; + + private: + static constexpr uint32_t unknown_output_extend_magic = std::numeric_limits::max(); + + struct ParamBlock { + uint8_t digest_len; + uint8_t key_len; + uint8_t fanout; + uint8_t depth; + uint32_t leaf_len; + uint32_t node_off; + uint32_t xof_digest_len; + uint8_t node_depth; + uint8_t inner_len; + uint8_t reserved[14]; + uint8_t salt[salt_extent]; + uint8_t personality[personality_extent]; + }; + + ParamBlock param_{}; + crypto_generichash_blake2b_state state_; + + void init_state(std::span key) { + static constexpr std::array init_vec{0x6a09e667f3bcc908ULL, + 0xbb67ae8584caa73bULL, + 0x3c6ef372fe94f82bULL, + 0xa54ff53a5f1d36f1ULL, + 0x510e527fade682d1ULL, + 0x9b05688c2b3e6c1fULL, + 0x1f83d9abfb41bd6bULL, + 0x5be0cd19137e2179ULL}; + +#if SODIUM_LIBRARY_VERSION_MAJOR > 10 || (SODIUM_LIBRARY_VERSION_MAJOR == 10 && SODIUM_LIBRARY_VERSION_MINOR >= 2) + // In libsodium 1.0.17, the crypto_generichash_blake2b_state struct was made + // opaque. We have to copy the internal definition of the real struct here + // so we can properly initialize it. + // see https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_generichash/blake2b/ref/blake2.h + struct Blake2bState { + uint64_t h[8]; + uint64_t t[2]; + uint64_t f[2]; + uint8_t buf[256]; + size_t buflen; + uint8_t last_node; + }; + auto *state = reinterpret_cast(&state_); +#else + auto *state = &state_; +#endif + + static_assert(sizeof(ParamBlock) == sizeof(init_vec)); + std::span param{*reinterpret_cast(¶m_)}; + + // state->h = init_vec xor param + for (size_t ix = 0; ix < init_vec.size(); ++ix) { + state->h[ix] = init_vec[ix] ^ detail::little_endian(param[ix]); + } + + // zero everything between state->t and state->last_node (inclusive) + std::fill(detail::byte_iter_mut(state->t), + detail::byte_iter_mut(state->last_node) + sizeof(state->last_node), + std::byte{}); + + if (!key.empty()) { + std::array block; { + auto write_end = std::copy(key.begin(), key.end(), block.begin()); + std::fill(write_end, block.end(), std::byte{}); + } + + digest(block); + sodium_memzero(block.data(), block.size());// erase key from stack + } + } + + struct PrivateTag {}; + static constexpr PrivateTag private_tag{}; + + Blake2Xb(PrivateTag, + size_t output_len, + std::span key, + std::span salt, + std::span personality) { + + if (output_len == 0) { + output_len = unknown_output_extend_magic; + } else if (output_len > max_output_extent) { + throw std::runtime_error{"Output length too large"}; + } + + if (!key.empty()) { + if (key.size() < min_key_extent || key.size() > max_key_extent) { + throw std::runtime_error{"Invalid blake2b key size"}; + } + } + + if (auto const res = sodium_init(); res == -1) { + throw std::runtime_error{"Could not initialize sodium"}; + } + + param_.digest_len = crypto_generichash_blake2b_BYTES_MAX; + param_.key_len = static_cast(key.size()); + param_.fanout = 1; + param_.depth = 1; + param_.xof_digest_len = detail::little_endian(output_len); + + std::copy(salt.begin(), salt.end(), detail::byte_iter_mut(param_.salt)); + std::copy(personality.begin(), personality.end(), detail::byte_iter_mut(param_.personality)); + + init_state(key); + } + + public: + /** + * @brief Construct a BLAKE2Xb instance + * @param output_len either unknown_output_extent for yet-unknown output length or a concrete length (>= min_output_extent && <= max_output_extent) + * @param key optionally a key with a length (>= min_key_length && <= max_key_length) + * @param salt BLAKE2b salt + * @param personality BLAKE2b personality + */ + explicit Blake2Xb(size_t output_len, + std::span key = {}, + std::span salt = default_salt, + std::span personality = default_personality) /*noexcept(sodium is initialized && output_len is within size constraints && key.size() is within size constaints)*/ + requires (output_extent == dynamic_output_extent) + : Blake2Xb{private_tag, output_len, key, salt, personality} { + } + + /** + * @brief Constructs a BLAKE2Xb instance either using an unknown output length (if output_extent == dynamic_output_extent) or a statically determined output length of output_extent + * @param key optionally a key with a length (>= min_key_length && <= max_key_length) + * @param salt BLAKE2b salt + * @param personality BLAKE2b personality + */ + explicit Blake2Xb(std::span key = {}, + std::span salt = default_salt, + std::span personality = default_personality) /*noexcept(sodium is initialized && key.size() is within size constraints)*/ + : Blake2Xb{private_tag, output_extent == dynamic_output_extent ? 0 : output_extent, key, salt, personality} { + } + + /** + * @brief digests data into the underlying BLAKE2Xb state + */ + void digest(std::span data) noexcept { + auto const res = crypto_generichash_blake2b_update(&state_, + reinterpret_cast(data.data()), + data.size()); + // cannot fail, see: https://github.com/jedisct1/libsodium/blob/8d9ab6cd764926d4bf1168b122f4a3ff4ea686a0/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c#L263 + assert(res == 0); + (void) res; + } + + /** + * @brief produces the hash corresponding to the previously digested bytes + * @param out location to write the hash to, if output_extent == dynamic_output_extent and the output length was specified on construction + * the length of the span has to match the previously provided length + */ + void finish(std::span out) && noexcept(output_extent != dynamic_output_extent) { + if constexpr (output_extent == dynamic_output_extent) { + auto const expected_out_len = detail::little_endian(param_.xof_digest_len); + if (expected_out_len != unknown_output_extend_magic && out.size() != expected_out_len) { + // was known in advance and does not match + throw std::runtime_error{"Buffer length must match output length"}; + } + } + + std::array h0; + auto res = crypto_generichash_blake2b_final(&state_, + reinterpret_cast(h0.data()), + h0.size()); + // cannot fail on proper use, see: https://github.com/jedisct1/libsodium/blob/8d9ab6cd764926d4bf1168b122f4a3ff4ea686a0/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c#L299 + assert(res == 0); + + param_.key_len = 0; + param_.fanout = 0; + param_.depth = 0; + param_.leaf_len = detail::little_endian(static_cast(crypto_generichash_blake2b_BYTES_MAX)); + param_.xof_digest_len = detail::little_endian(static_cast(out.size())); + param_.node_depth = 0; + param_.inner_len = crypto_generichash_blake2b_BYTES_MAX; + + size_t pos = 0; + size_t remaining = out.size(); + + while (remaining > 0) { + param_.node_off = detail::little_endian(static_cast(pos / crypto_generichash_blake2b_BYTES_MAX)); + + size_t const len = std::min(static_cast(crypto_generichash_blake2b_BYTES_MAX), remaining); + param_.digest_len = static_cast(len); + + init_state({}); + res = crypto_generichash_blake2b_update(&state_, + reinterpret_cast(h0.data()), + h0.size()); + assert(res == 0); + + res = crypto_generichash_blake2b_final(&state_, + reinterpret_cast(out.data()) + pos, + len); + assert(res == 0); + + pos += len; + remaining -= len; + } + + (void) res; + } + + /** + * @brief returns the possibly runtime-determined output length or the underlying BLAKE2b + * @note different to Blake2Xb::output_extent this also considers values provided to the constructor at runtime. + * @return required length for output buffer, if known otherwise unknown_output_extent + */ + [[nodiscard]] constexpr size_t concrete_output_extent() const noexcept { + if constexpr (output_extent == dynamic_output_extent) { + auto const expected_out_len = detail::little_endian(param_.xof_digest_len); + if (expected_out_len == unknown_output_extend_magic) { + return unknown_output_extent; + } else { + return expected_out_len; + } + } else { + return output_extent; + } + } + + /** + * @brief convenience function to hash a single byte-span + */ + static void hash_single(std::span data, + std::span out, + std::span key = {}, + std::span salt = default_salt, + std::span personality = default_personality) /*noexcept(sodium is initialized && key is within size constraints)*/ { + auto blake = [&]() { + if constexpr (output_extent == dynamic_output_extent) { + return Blake2Xb{out.size(), key, salt, personality}; + } else { + return Blake2Xb{key, salt, personality}; + } + }(); + + blake.digest(data); + std::move(blake).finish(out); + } + }; + +} // namespace dice::hash::blake2xb + +#else +#error "Cannot include Blake2Xb.hpp if sodium is not available" +#endif // __has_include() + +#endif//DICE_HASH_BLAKE2XB_HPP diff --git a/include/dice/hash/blake/Blake2b.hpp b/include/dice/hash/blake/Blake2b.hpp new file mode 100644 index 0000000..30977aa --- /dev/null +++ b/include/dice/hash/blake/Blake2b.hpp @@ -0,0 +1,218 @@ +#ifndef DICE_HASH_BLAKE2B_HPP +#define DICE_HASH_BLAKE2B_HPP + +/** + * @brief A thin wrapper around libsodium's Blake2b implementation + */ + +#if __has_include() + +#include + +#include +#include +#include +#include +#include + +namespace dice::hash::blake2b { + + inline constexpr size_t min_output_extent = crypto_generichash_blake2b_BYTES_MIN; + inline constexpr size_t max_output_extent = crypto_generichash_blake2b_BYTES_MAX; + inline constexpr size_t default_output_extent = crypto_generichash_blake2b_BYTES; + inline constexpr size_t dynamic_output_extent = std::dynamic_extent; + + inline constexpr size_t salt_extent = crypto_generichash_blake2b_SALTBYTES; + inline constexpr std::array default_salt{}; + + inline constexpr size_t personality_extent = crypto_generichash_blake2b_PERSONALBYTES; + inline constexpr std::array default_personality{}; + + inline constexpr size_t min_key_extent = crypto_generichash_blake2b_KEYBYTES_MIN; + inline constexpr size_t max_key_extent = crypto_generichash_blake2b_KEYBYTES_MAX; + inline constexpr size_t default_key_extent = crypto_generichash_blake2b_KEYBYTES; + + /** + * @brief Generates a random key by filling key_out using std::random_device + */ + template + requires (KeyExtent == std::dynamic_extent || (KeyExtent >= min_key_extent && KeyExtent <= max_key_extent)) + void generate_key(std::span key_out) { + if constexpr (KeyExtent == std::dynamic_extent) { + if (key_out.size() < min_key_extent || key_out.size() > max_key_extent) { + throw std::runtime_error{"Invalid blake2b key size"}; + } + } + + using byte_utype = std::underlying_type_t; + + std::random_device rng; + std::uniform_int_distribution dist{std::numeric_limits::min(), std::numeric_limits::max()}; + + std::generate(key_out.begin(), key_out.end(), [&]() { + return static_cast(dist(rng)); + }); + } + + namespace detail { + template + struct Blake2bInner { + crypto_generichash_blake2b_state state_; + }; + + template<> + struct Blake2bInner { + crypto_generichash_blake2b_state state_; + size_t specified_output_len_; + }; + } // namespace detail + + template + requires (OutputExtent == dynamic_output_extent || (OutputExtent >= min_output_extent && OutputExtent <= max_output_extent)) + struct Blake2b { + /** + * @brief if known at compile time, the size of the resulting hash, otherwise dynamic_output_extent + */ + static constexpr size_t output_extent = OutputExtent; + + private: + detail::Blake2bInner inner_; + + void init(size_t output_len, + std::span key, + std::span salt, + std::span personality) { + + if (output_len < min_output_extent || output_len > max_output_extent) { + throw std::runtime_error{"Invalid blake2b output size"}; + } + + if (!key.empty()) { + if (key.size() < min_key_extent || key.size() > max_key_extent) { + throw std::runtime_error{"Invalid blake2b key size"}; + } + } + + if (auto const res = sodium_init(); res == -1) { + throw std::runtime_error{"Could not initialize sodium"}; + } + + if constexpr (output_extent == dynamic_output_extent) { + inner_.specified_output_len_ = output_len; + } + + auto const res = crypto_generichash_blake2b_init_salt_personal(&inner_.state_, + reinterpret_cast(key.data()), + key.size(), + output_len, + reinterpret_cast(salt.data()), + reinterpret_cast(personality.data())); + // cannot fail here, all invariants have been checked, + // see: https://github.com/jedisct1/libsodium/blob/d787d2b1cf13ad2e69c3a7ebc3fb7b68b6430774/src/libsodium/crypto_generichash/blake2b/ref/generichash_blake2b.c#LL69C47-L69C47 + // and: https://github.com/jedisct1/libsodium/blob/d787d2b1cf13ad2e69c3a7ebc3fb7b68b6430774/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c#L148 + // and: https://github.com/jedisct1/libsodium/blob/d787d2b1cf13ad2e69c3a7ebc3fb7b68b6430774/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c#L216 + assert(res == 0); + } + + public: + /** + * @brief Construct a BLAKE2b instance + * @param output_len either a dynamically determined concrete length (>= min_output_extent && <= max_output_extent) + * @param key optionally a key with a length (>= min_key_length && <= max_key_length) + * @param salt BLAKE2b salt + * @param personality BLAKE2b personality + */ + explicit Blake2b(size_t output_len, + std::span key = {}, + std::span salt = default_salt, + std::span personality = default_personality) /*noexcept(sodium is initialized && output_len is within size constraints && key.size() is within size constaints)*/ + requires (output_extent == dynamic_output_extent) { + init(output_len, key, salt, personality); + } + + /** + * @brief Constructs a BLAKE2b instance either using a statically determined output length of output_extent + * @param key optionally a key with a length (>= min_key_length && <= max_key_length) + * @param salt BLAKE2b salt + * @param personality BLAKE2b personality + */ + explicit Blake2b(std::span key = {}, + std::span salt = default_salt, + std::span personality = default_personality) /*noexcept(sodium is initialized && key.size() is within size constraints)*/ + requires (output_extent != dynamic_output_extent) { + init(output_extent, key, salt, personality); + } + + /** + * @brief digests data into the underlying BLAKE2b state + */ + void digest(std::span data) noexcept { + auto const res = crypto_generichash_blake2b_update(&inner_.state_, + reinterpret_cast(data.data()), + data.size()); + // cannot fail, see: https://github.com/jedisct1/libsodium/blob/8d9ab6cd764926d4bf1168b122f4a3ff4ea686a0/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c#L263 + assert(res == 0); + } + + /** + * @brief produces the hash corresponding to the previously digested bytes + * @param out location to write the hash to, if output_extent == dynamic_output_extent and the output length was specified on construction + * the length of the span has to match the previously provided length + */ + void finish(std::span out) && noexcept(output_extent != dynamic_output_extent /*|| output is within size constraints*/) { + if constexpr (output_extent == dynamic_output_extent) { + auto const expected_out_len = inner_.specified_output_len_; + if (expected_out_len != 0 && out.size() != expected_out_len) { + // was known in advance and does not match + throw std::runtime_error{"Buffer length must match output length"}; + } + } + + auto const res = crypto_generichash_blake2b_final(&inner_.state_, + reinterpret_cast(out.data()), + out.size()); + // cannot fail, all invariants have been checked, see: https://github.com/jedisct1/libsodium/blob/d787d2b1cf13ad2e69c3a7ebc3fb7b68b6430774/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c#L292 + assert(res == 0); + } + + /** + * @brief returns the possibly runtime-determined output length or the underlying BLAKE2b + * @note different to Blake2b::output_extent this also considers values provided to the constructor at runtime. + * @return required length for output buffer, if known otherwise unknown_output_extent + */ + [[nodiscard]] constexpr size_t concrete_output_extent() const noexcept { + if constexpr (output_extent == dynamic_output_extent) { + return inner_.specified_output_len_; + } else { + return output_extent; + } + } + + /** + * @brief convenience function to hash a single byte-span + */ + static void hash_single(std::span data, + std::span out, + std::span key = {}, + std::span salt = default_salt, + std::span personality = default_personality) /*noexcept(sodium is initialized && output is within size constraints && key is within size constraints)*/ { + auto blake = [&]() { + if constexpr (output_extent == dynamic_output_extent) { + return Blake2b{out.size(), key, salt, personality}; + } else { + return Blake2b{key, salt, personality}; + } + }(); + + blake.digest(data); + std::move(blake).finish(out); + } + }; + +} // namespace dice::hash::blake2b + +#else +#error "Cannot include Blake2b.hpp if sodium is not available" +#endif + +#endif//DICE_HASH_BLAKE2B_HPP diff --git a/include/dice/hash/blake/Blake3.hpp b/include/dice/hash/blake/Blake3.hpp new file mode 100644 index 0000000..7e7f2ea --- /dev/null +++ b/include/dice/hash/blake/Blake3.hpp @@ -0,0 +1,100 @@ +#ifndef DICE_HASH_BLAKE3_HPP +#define DICE_HASH_BLAKE3_HPP + +#include +#include +#include +#include +#include + +#include + +namespace dice::hash::blake3 { + + inline constexpr size_t dynamic_output_extent = std::dynamic_extent; + inline constexpr size_t min_key_extent = BLAKE3_KEY_LEN; + inline constexpr size_t max_key_extent = BLAKE3_KEY_LEN; + inline constexpr size_t default_key_extent = BLAKE3_KEY_LEN; + + /** + * @brief Generates a random key by filling key_out using std::random_device + */ + inline void generate_key(std::span key_out) { + using byte_utype = std::underlying_type_t; + + std::random_device rng; + std::uniform_int_distribution dist{std::numeric_limits::min(), std::numeric_limits::max()}; + + std::generate(key_out.begin(), key_out.end(), [&]() { + return static_cast(dist(rng)); + }); + } + + template + struct Blake3 { + /** + * @brief if known at compile time, the size of the resulting hash, otherwise dynamic_output_extent + */ + static constexpr size_t output_extent = OutputExtent; + + static constexpr size_t min_key_extent = ::dice::hash::blake3::min_key_extent; + static constexpr size_t max_key_extent = ::dice::hash::blake3::max_key_extent; + static constexpr size_t default_key_extent = ::dice::hash::blake3::default_key_extent; + + private: + blake3_hasher state_; + + public: + Blake3() noexcept { + blake3_hasher_init(&state_); + } + + /** + * @brief Construct a BLAKE3 instance + * @param key a blake3 key + */ + explicit Blake3(std::span key) noexcept { + blake3_hasher_init_keyed(&state_, reinterpret_cast(key.data())); + } + + /** + * @brief digests data into the underlying BLAKE2Xb state + */ + void digest(std::span data) noexcept { + blake3_hasher_update(&state_, data.data(), data.size()); + } + + /** + * @brief produces the hash corresponding to the previously digested bytes + * @param out location to write the hash to, if output_extent == dynamic_output_extent and the output length was specified on construction + * the length of the span has to match the previously provided length + */ + void finish(std::span out) && noexcept { + blake3_hasher_finalize(&state_, reinterpret_cast(out.data()), out.size()); + } + + /** + * @brief convenience function to hash a single byte-span + */ + static void hash_single(std::span data, + std::span out) noexcept { + Blake3 blake; + blake.digest(data); + std::move(blake).finish(out); + } + + /** + * @brief convenience function to hash a single byte-span + */ + static void hash_single(std::span data, + std::span out, + std::span key) noexcept { + Blake3 blake{key}; + blake.digest(data); + std::move(blake).finish(out); + } + }; + +} // namespace dice::hash::blake3 + +#endif//DICE_HASH_BLAKE3_HPP diff --git a/include/dice/hash/blake/internal/blake3/CMakeLists.txt b/include/dice/hash/blake/internal/blake3/CMakeLists.txt new file mode 100644 index 0000000..f6e2d30 --- /dev/null +++ b/include/dice/hash/blake/internal/blake3/CMakeLists.txt @@ -0,0 +1,257 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +# the official Blake3 implementation does not contain a usable CMakeLists.txt +# (only one for running test cases) + +# respect C_EXTENSIONS OFF without explicitly setting C_STANDARD +if (POLICY CMP0128) + cmake_policy(SET CMP0128 NEW) +endif() + +project(libblake3 + VERSION 1.5.4 + DESCRIPTION "BLAKE3 C implementation" + LANGUAGES C ASM +) + +include(FeatureSummary) +include(GNUInstallDirs) + +set(blake3_rev 95e42b84fc4709974c7b23c7ae885989ab36c31e) +set(blake3_files + blake3.c + blake3.h + blake3-config.cmake.in + blake3_avx2.c + blake3_avx2_x86-64_unix.S + blake3_avx2_x86-64_windows_gnu.S + blake3_avx2_x86-64_windows_msvc.asm + blake3_avx512.c + blake3_avx512_x86-64_unix.S + blake3_avx512_x86-64_windows_gnu.S + blake3_avx512_x86-64_windows_msvc.asm + blake3_dispatch.c + blake3_impl.h + blake3_neon.c + blake3_portable.c + blake3_sse2.c + blake3_sse2_x86-64_unix.S + blake3_sse2_x86-64_windows_gnu.S + blake3_sse2_x86-64_windows_msvc.asm + blake3_sse41.c + blake3_sse41_x86-64_unix.S + blake3_sse41_x86-64_windows_gnu.S + blake3_sse41_x86-64_windows_msvc.asm + libblake3.pc.in +) +set(blake3_license_files + LICENSE_A2 + LICENSE_A2LLVM + LICENSE_CC0 +) + +foreach(file ${blake3_files}) + file(DOWNLOAD "https://raw.githubusercontent.com/BLAKE3-team/BLAKE3/${blake3_rev}/c/${file}" + "${CMAKE_CURRENT_BINARY_DIR}/src/blake3/${file}" + TLS_VERIFY ON) +endforeach() +foreach(file ${blake3_license_files}) + file(DOWNLOAD "https://raw.githubusercontent.com/BLAKE3-team/BLAKE3/${blake3_rev}/${file}" + "${CMAKE_CURRENT_BINARY_DIR}/src/blake3/${file}" + TLS_VERIFY ON) +endforeach() + +# architecture lists for which to enable assembly / SIMD sources +set(BLAKE3_AMD64_NAMES amd64 AMD64 x86_64) +set(BLAKE3_X86_NAMES i686 x86 X86) +set(BLAKE3_ARMv8_NAMES aarch64 AArch64 arm64 ARM64 armv8 armv8a) +# default SIMD compiler flag configuration (can be overriden by toolchains or CLI) +if(MSVC) + set(BLAKE3_CFLAGS_SSE2 "/arch:SSE2" CACHE STRING "the compiler flags to enable SSE2") + # MSVC has no dedicated sse4.1 flag (see https://learn.microsoft.com/en-us/cpp/build/reference/arch-x86?view=msvc-170) + set(BLAKE3_CFLAGS_SSE4.1 "/arch:AVX" CACHE STRING "the compiler flags to enable SSE4.1") + set(BLAKE3_CFLAGS_AVX2 "/arch:AVX2" CACHE STRING "the compiler flags to enable AVX2") + set(BLAKE3_CFLAGS_AVX512 "/arch:AVX512" CACHE STRING "the compiler flags to enable AVX512") + +elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU" + OR CMAKE_C_COMPILER_ID STREQUAL "Clang" + OR CMAKE_C_COMPILER_ID STREQUAL "AppleClang") + set(BLAKE3_CFLAGS_SSE2 "-msse2" CACHE STRING "the compiler flags to enable SSE2") + set(BLAKE3_CFLAGS_SSE4.1 "-msse4.1" CACHE STRING "the compiler flags to enable SSE4.1") + set(BLAKE3_CFLAGS_AVX2 "-mavx2" CACHE STRING "the compiler flags to enable AVX2") + set(BLAKE3_CFLAGS_AVX512 "-mavx512f -mavx512vl" CACHE STRING "the compiler flags to enable AVX512") + + if (CMAKE_SYSTEM_PROCESSOR IN_LIST BLAKE3_ARMv8_NAMES + AND NOT CMAKE_SIZEOF_VOID_P EQUAL 8) + # 32-bit ARMv8 needs NEON to be enabled explicitly + set(BLAKE3_CFLAGS_NEON "-mfpu=neon" CACHE STRING "the compiler flags to enable NEON") + endif() +endif() + +# library target +add_library(blake3 + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3.c + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_dispatch.c + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_portable.c +) +add_library(BLAKE3::blake3 ALIAS blake3) + +# library configuration +set(BLAKE3_PKGCONFIG_CFLAGS) +if (BUILD_SHARED_LIBS) + target_compile_definitions(blake3 + PUBLIC BLAKE3_DLL + PRIVATE BLAKE3_DLL_EXPORTS + ) + list(APPEND BLAKE3_PKGCONFIG_CFLAGS -DBLAKE3_DLL) +endif() +target_include_directories(blake3 PUBLIC + $ + $ +) +set_target_properties(blake3 PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION 0 + C_VISIBILITY_PRESET hidden + C_EXTENSIONS OFF +) +target_compile_features(blake3 PUBLIC c_std_99) +# ensure C_EXTENSIONS OFF is respected without overriding CMAKE_C_STANDARD +# which may be set by the user or toolchain file +if (NOT POLICY CMP0128 AND NOT DEFINED CMAKE_C_STANDARD) + set_target_properties(blake3 PROPERTIES C_STANDARD 99) +endif() + +# optional SIMD sources +macro(BLAKE3_DISABLE_SIMD) + set(BLAKE3_SIMD_AMD64_ASM OFF) + set(BLAKE3_SIMD_X86_INTRINSICS OFF) + set(BLAKE3_SIMD_NEON_INTRINSICS OFF) + target_compile_definitions(blake3 PRIVATE + BLAKE3_USE_NEON=0 + BLAKE3_NO_SSE2 + BLAKE3_NO_SSE41 + BLAKE3_NO_AVX2 + BLAKE3_NO_AVX512 + ) +endmacro() + +if(CMAKE_SYSTEM_PROCESSOR IN_LIST BLAKE3_AMD64_NAMES OR BLAKE3_USE_AMD64_ASM) + set(BLAKE3_SIMD_AMD64_ASM ON) + + if(MSVC) + enable_language(ASM_MASM) + target_sources(blake3 PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_avx2_x86-64_windows_msvc.asm + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_avx512_x86-64_windows_msvc.asm + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_sse2_x86-64_windows_msvc.asm + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_sse41_x86-64_windows_msvc.asm + ) + + elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU" + OR CMAKE_C_COMPILER_ID STREQUAL "Clang" + OR CMAKE_C_COMPILER_ID STREQUAL "AppleClang") + if (WIN32) + target_sources(blake3 PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_avx2_x86-64_windows_gnu.S + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_avx512_x86-64_windows_gnu.S + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_sse2_x86-64_windows_gnu.S + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_sse41_x86-64_windows_gnu.S + ) + + elseif(UNIX) + target_sources(blake3 PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_avx2_x86-64_unix.S + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_avx512_x86-64_unix.S + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_sse2_x86-64_unix.S + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_sse41_x86-64_unix.S + ) + + else() + BLAKE3_DISABLE_SIMD() + endif() + + else() + BLAKE3_DISABLE_SIMD() + endif() + +elseif((CMAKE_SYSTEM_PROCESSOR IN_LIST BLAKE3_X86_NAMES OR BLAKE3_USE_X86_INTRINSICS) + AND DEFINED BLAKE3_CFLAGS_SSE2 + AND DEFINED BLAKE3_CFLAGS_SSE4.1 + AND DEFINED BLAKE3_CFLAGS_AVX2 + AND DEFINED BLAKE3_CFLAGS_AVX512) + set(BLAKE3_SIMD_X86_INTRINSICS ON) + + target_sources(blake3 PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_avx2.c + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_avx512.c + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_sse2.c + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_sse41.c + ) + set_source_files_properties(blake3_avx2.c PROPERTIES COMPILE_FLAGS "${BLAKE3_CFLAGS_AVX2}") + set_source_files_properties(blake3_avx512.c PROPERTIES COMPILE_FLAGS "${BLAKE3_CFLAGS_AVX512}") + set_source_files_properties(blake3_sse2.c PROPERTIES COMPILE_FLAGS "${BLAKE3_CFLAGS_SSE2}") + set_source_files_properties(blake3_sse41.c PROPERTIES COMPILE_FLAGS "${BLAKE3_CFLAGS_SSE4.1}") + +elseif((CMAKE_SYSTEM_PROCESSOR IN_LIST BLAKE3_ARMv8_NAMES + OR ANDROID_ABI STREQUAL "armeabi-v7a" + OR BLAKE3_USE_NEON_INTRINSICS) + AND (DEFINED BLAKE3_CFLAGS_NEON + OR CMAKE_SIZEOF_VOID_P EQUAL 8)) + set(BLAKE3_SIMD_NEON_INTRINSICS ON) + + target_sources(blake3 PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3_neon.c + ) + target_compile_definitions(blake3 PRIVATE + BLAKE3_USE_NEON=1 + ) + + if (DEFINED BLAKE3_CFLAGS_NEON) + set_source_files_properties(blake3_neon.c PROPERTIES COMPILE_FLAGS "${BLAKE3_CFLAGS_NEON}") + endif() + +else() + BLAKE3_DISABLE_SIMD() +endif() + +# cmake install support +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3.h DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/LICENSE_A2 + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/LICENSE_A2LLVM + ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/LICENSE_CC0 + DESTINATION "${CMAKE_INSTALL_DATADIR}/licenses" +) +install(TARGETS blake3 EXPORT blake3-targets) +install(EXPORT blake3-targets + NAMESPACE BLAKE3:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/blake3" +) + +include(CMakePackageConfigHelpers) +configure_package_config_file(${CMAKE_CURRENT_BINARY_DIR}/src/blake3/blake3-config.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/blake3-config.cmake" + + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/blake3" +) +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/blake3-config-version.cmake" + VERSION ${libblake3_VERSION} + COMPATIBILITY SameMajorVersion +) +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/blake3-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/blake3-config-version.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/blake3" +) + +configure_file(${CMAKE_CURRENT_BINARY_DIR}/src/blake3/libblake3.pc.in ${CMAKE_CURRENT_BINARY_DIR}/src/blake3/libblake3.pc @ONLY) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/src/blake3/libblake3.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + +# print feature summary +add_feature_info("AMD64 assembly" BLAKE3_SIMD_AMD64_ASM "The library uses hand written amd64 SIMD assembly.") +add_feature_info("x86 SIMD intrinsics" BLAKE3_SIMD_X86_INTRINSICS "The library uses x86 SIMD intrinsics.") +add_feature_info("NEON SIMD intrinsics" BLAKE3_SIMD_NEON_INTRINSICS "The library uses NEON SIMD intrinsics.") +feature_summary(WHAT ENABLED_FEATURES) diff --git a/include/dice/hash/lthash/LtHash.hpp b/include/dice/hash/lthash/LtHash.hpp new file mode 100644 index 0000000..d1afcb1 --- /dev/null +++ b/include/dice/hash/lthash/LtHash.hpp @@ -0,0 +1,396 @@ +#ifndef DICE_HASH_LTHASH_HPP +#define DICE_HASH_LTHASH_HPP + +/** + * Implementation of LtHash from the following paper: + * Title: Securing Update Propagation with Homomorphic Hashing + * Authors: Kevin Lewi and Wonho Kim and Ilya Maykov and Stephen Weis + * Year: 2019 + * Url: https://eprint.iacr.org/2019/227 + * + * @note Implementation adapted from https://github.com/facebook/folly/blob/main/folly/experimental/crypto/LtHash.h + */ + +#if __has_include() + +#include +#include +#include +#include +#include + +#include + +#include "dice/hash/blake/Blake3.hpp" +#include "dice/hash/lthash/MathEngine.hpp" + +namespace dice::hash::lthash { + + namespace detail { + template + struct Bits; + + template<> + struct Bits<16> { + static constexpr bool needs_padding = false; + static constexpr size_t bits_per_element = 16; + }; + + template<> + struct Bits<20> { + // In binary this mask looks like: + // 00 <1 repeated 20 times> 0 <1 repeated 20 times> 0 <1 repeated 20 times> + static constexpr uint64_t data_mask = ~0xC000020000100000ULL; + static constexpr bool needs_padding = true; + static constexpr size_t bits_per_element = 20; + }; + + template<> + struct Bits<32> { + static constexpr bool needs_padding = false; + static constexpr size_t bits_per_element = 32; + }; + + template + struct Key { + std::array key_{}; + size_t key_len_{}; + + [[nodiscard]] constexpr std::span get() const noexcept { + return {key_.data(), key_len_}; + } + + constexpr void clear() noexcept { + if (!std::is_constant_evaluated()) { + sodium_memzero(key_.data(), key_.size()); + } else { + std::fill(key_.begin(), key_.end(), std::byte{0}); + } + + key_len_ = 0; + } + + template + requires (supplied_key_len == std::dynamic_extent || (supplied_key_len >= min_key_extent + && supplied_key_len <= max_key_extent)) + constexpr void set_unchecked(std::span new_key) noexcept { + assert(new_key.size() >= min_key_extent && new_key.size() <= max_key_extent); + + clear(); + std::copy(new_key.begin(), new_key.end(), key_.begin()); + key_len_ = new_key.size(); + } + }; + + template + struct Key { + std::array key_{}; + + [[nodiscard]] constexpr std::span get() const noexcept { + return key_; + } + + constexpr void clear() noexcept { + if (!std::is_constant_evaluated()) { + sodium_memzero(key_.data(), key_.size()); + } else { + std::fill(key_.begin(), key_.end(), std::byte{0}); + } + } + + constexpr void set_unchecked(std::span new_key) noexcept { + clear(); + std::copy(new_key.begin(), new_key.end(), key_.begin()); + } + }; + } // namespace detail + + /** + * @brief LtHash ported from folly::experimental::crypto + * @tparam n_bits_per_elem how many bits the individual state elements occupy + * @tparam n_elems how many individual state elements there are + * @tparam MathEngineT the math engine/instruction set to use for computations (defaults to the best your platform supports; in order (best to worst): AVX2, SSE2, x86_64) + */ + template typename HashT = blake3::Blake3, template typename MathEngineT = DefaultMathEngine> + struct LtHash { + static_assert((n_bits_per_elem == 16 && n_elems % 32 == 0) + || (n_bits_per_elem == 20 && n_elems % 24 == 0) + || (n_bits_per_elem == 32 && n_elems % 16 == 0)); + + static_assert(MathEngine>); + + private: + template typename, template typename> + friend struct LtHash; + + using Bits = detail::Bits; + using MathEngine = MathEngineT; + + public: + static constexpr bool needs_padding = Bits::needs_padding; + + static constexpr size_t element_bits = n_bits_per_elem; + static constexpr size_t element_count = n_elems; + + static constexpr size_t elements_per_uint64 = needs_padding ? (sizeof(uint64_t) * 8) / (element_bits + 1) + : (sizeof(uint64_t) * 8) / element_bits; + + static constexpr size_t checksum_len = (element_count / elements_per_uint64) * sizeof(uint64_t); + static constexpr size_t checksum_align = MathEngine::min_buffer_align; + + static constexpr std::array default_checksum{}; + + private: + using Hash = HashT; + + detail::Key key_; + alignas(checksum_align) std::array checksum_; + + + constexpr void set_checksum_unchecked(std::span new_checksum) noexcept { + std::copy(new_checksum.begin(), new_checksum.end(), checksum_.begin()); + } + + [[nodiscard]] constexpr std::span checksum_mut() noexcept { + return checksum_; + } + + void hash_object(std::span out, std::span obj) const noexcept { + Hash::hash_single(obj, out, key_.get()); + + if constexpr (needs_padding) { + MathEngine::clear_padding_bits(out); + } + } + + public: + /** + * @brief construct an LtHash using the (optionally) given initial_checksum + */ + explicit constexpr LtHash(std::span initial_checksum = default_checksum) noexcept { + set_checksum_unchecked(initial_checksum); + } + + constexpr LtHash(LtHash const &other) noexcept = default; + + template typename MathEngineT2> + constexpr LtHash(LtHash const &other) noexcept : key_{other.key_}, + checksum_{other.checksum_} { + } + + constexpr LtHash(LtHash &&other) noexcept : key_{other.key_}, + checksum_{other.checksum_} { + other.clear_key(); + } + + + template typename MathEngineT2> + constexpr LtHash(LtHash &&other) noexcept : key_{other.key_}, + checksum_{other.checksum_} { + other.clear_key(); + } + + constexpr LtHash &operator=(LtHash const &other) noexcept { + if (this == &other) [[unlikely]] { + return *this; + } + + clear_key(); + key_ = other.key_; + checksum_ = other.checksum_; + return *this; + } + + constexpr LtHash &operator=(LtHash &&other) noexcept { + assert(this != &other); + + clear_key(); + key_ = other.key_; + other.clear_key(); + checksum_ = other.checksum_; + return *this; + } + + constexpr ~LtHash() noexcept { + clear_key(); + } + + /** + * @brief Checks if the internal Blake2Xb key is equal to the given key + * @note this function is not secured against timing attacks + */ + [[nodiscard]] constexpr bool key_equal(std::span other_key) const noexcept { + auto const this_key = key_.get(); + return std::equal(this_key.begin(), this_key.end(), other_key.begin(), other_key.end()); + } + + /** + * @brief Checks if *this and other have the same key for their Blake2Xb instances + * @note this functions is not secured against timing attacks + */ + [[nodiscard]] constexpr bool key_equal(LtHash const &other) const noexcept { + return key_equal(other.key_.get()); + } + + /** + * @brief Sets the internal key for the Blake2Xb instance to the given key; securely erases the old key + * @throws std::invalid_argument if key.size() is not in blake2xb::min_key_extent..blake2xb::max_key_extent (inclusive); only if supplied_key_len == std::dynamic_extent + */ + template + requires (supplied_key_len == std::dynamic_extent || (supplied_key_len >= Hash::min_key_extent + && supplied_key_len <= Hash::max_key_extent)) + constexpr void set_key(std::span key) noexcept(supplied_key_len != std::dynamic_extent) { + if constexpr (supplied_key_len == std::dynamic_extent) { + if (key.size() < Hash::min_key_extent || key.size() > Hash::max_key_extent) [[unlikely]] { + throw std::invalid_argument{"Invalid key size for Blake2Xb"}; + } + } + + key_.set_unchecked(key); + } + + /** + * @brief Clears the internal key for the Blake2Xb instance by securely erasing it + */ + constexpr void clear_key() noexcept { + key_.clear(); + } + + [[nodiscard]] constexpr std::span checksum() const noexcept { + return checksum_; + } + + /** + * @brief Checks if this->checksum() is equal to other_checksum (i.e. represent the same multiset) + * @note this function is _not_ secured against timing attacks + */ + [[nodiscard]] constexpr bool checksum_equal(std::span other_checksum) const noexcept { + return std::equal(checksum_.begin(), checksum_.end(), other_checksum.begin()); + } + + /** + * @brief Checks if *this and other have the same checksum (i.e. represent the same multiset) + * @note this function is _not_ secured against timing attacks + */ + [[nodiscard]] constexpr bool checksum_equal(LtHash const &other) const noexcept { + return checksum_equal(other.checksum()); + } + + /** + * @brief Checks if this->checksum() is equal to other_checksum (i.e. represent the same multiset) + * @note this function is secured against timing attacks + */ + [[nodiscard]] bool checksum_equal_constant_time(std::span other_checksum) const noexcept { + return sodium_memcmp(checksum_, other_checksum.data(), checksum_len) == 0; + } + + /** + * @brief Checks if *this and other have the same checksum (i.e. represent the same multiset) + * @note this function is secured against timing attacks + */ + [[nodiscard]] bool checksum_equal_constant_time(LtHash const &other) const noexcept { + return checksum_equal_constant_time(other.checksum()); + } + + /** + * @brief Explicitly sets the current checksum to the given one + * @throws std::invalid_argument if new_checksum has invalid padding; only if needs_padding + */ + constexpr void set_checksum(std::span new_checksum) noexcept(!needs_padding) { + set_checksum_unchecked(new_checksum); + if constexpr (needs_padding) { + if (!MathEngine::check_padding_bits(checksum())) [[unlikely]] { + throw std::invalid_argument{"Invalid checksum: found non-zero padding bits"}; + } + } + } + + /** + * @brief Clears the current checksum + */ + constexpr void clear_checksum() noexcept { + std::fill(checksum_.begin(), checksum_.end(), std::byte{0}); + } + + /** + * @brief Adds another LtHash to *this (via multiset-union) + * @param other another LtHash instance with the same key as *this + * @return reference to *this + * @throws std::invalid_argument if !this->key_equal(other) + */ + LtHash &combine_add(LtHash const &other) { + if (!key_equal(other)) [[unlikely]] { + throw std::invalid_argument{"Cannot combine hashes with different keys"}; + } + + MathEngine::add(checksum_mut(), other.checksum()); + return *this; + } + + /** + * @brief Removes another LtHash from *this (via multiset-minus) + * @param other another LtHash instance with the same key as *this + * @return reference to *this + * @throws std::invalid_argument if !this->key_equal(other) + */ + LtHash &combine_remove(LtHash const &other) { + if (!key_equal(other)) [[unlikely]] { + throw std::invalid_argument{"Cannot combine hashes with different keys"}; + } + + MathEngine::sub(checksum_mut(), other.checksum()); + return *this; + } + + /** + * @brief Adds a single object to this LtHash instance + * @param obj object to add + * @return reference to *this + */ + LtHash &add(std::span obj) noexcept { + alignas(MathEngine::min_buffer_align) std::array obj_hash; + hash_object(obj_hash, obj); + MathEngine::add(checksum_mut(), std::span{obj_hash}); + return *this; + } + + /** + * @brief Removes a single object from this LtHash instance + * @param obj object to remove + * @return reference to *this + */ + LtHash &remove(std::span obj) noexcept { + alignas(MathEngine::min_buffer_align) std::array obj_hash; + hash_object(obj_hash, obj); + MathEngine::sub(checksum_mut(), std::span{obj_hash}); + return *this; + } + + /** + * @brief Checks if *this and other have the same checksum (i.e. represent the same multiset) + * @note this function is _not_ secured against timing attacks + */ + constexpr bool operator==(LtHash const &other) const noexcept { + return checksum_equal(other); + } + + /** + * @brief Checks if *this and other _do not_ have the same checksum (i.e. represent the same multiset) + * @note this function _is_ secured against timing attacks + */ + constexpr bool operator!=(LtHash const &other) const noexcept { + return !LtHash::operator==(other); + } + }; + + using LtHash16 = LtHash<16, 1024>; + using LtHash20 = LtHash<20, 1008>; + using LtHash32 = LtHash<32, 1024>; + +} // namespace dice::hash::lthash + +#else +#error "Cannot include LtHash.hpp if sodium is not available" +#endif + +#endif//DICE_HASH_LTHASH_HPP diff --git a/include/dice/hash/lthash/MathEngine.hpp b/include/dice/hash/lthash/MathEngine.hpp new file mode 100644 index 0000000..81d41c9 --- /dev/null +++ b/include/dice/hash/lthash/MathEngine.hpp @@ -0,0 +1,42 @@ +#ifndef DICE_HASH_MATHENGINE_HPP +#define DICE_HASH_MATHENGINE_HPP + +#include "dice/hash/lthash/MathEngine_Hwy.hpp" +#include "dice/hash/lthash/MathEngine_Simple.hpp" + +namespace dice::hash::lthash { + template + concept UnpaddedBits = requires { + { B::bits_per_element } -> std::convertible_to; + }; + + template + concept PaddedBits = requires { + requires UnpaddedBits; + { B::needs_padding } -> std::convertible_to; + { B::data_mask } -> std::convertible_to; + }; + + template typename ME, typename B> + concept UnpaddedMathEngine = requires (std::span dst, std::span src) { + requires UnpaddedBits; + { ME::min_buffer_align } -> std::convertible_to; + ME::add(dst, src); + ME::sub(dst, src); + }; + + template typename ME, typename B> + concept PaddedMathEngine = requires (std::span data, std::span out) { + requires PaddedBits; + { ME::check_padding_bits(data) } -> std::convertible_to; + ME::clear_padding_bits(out); + }; + + template typename ME, typename B> + concept MathEngine = UnpaddedMathEngine && (!PaddedBits || PaddedMathEngine); + + template + using DefaultMathEngine = MathEngine_Hwy; +} // namespace dice::hash::lthash + +#endif//DICE_HASH_MATHENGINE_HPP diff --git a/include/dice/hash/lthash/MathEngine_Hwy.cpp b/include/dice/hash/lthash/MathEngine_Hwy.cpp new file mode 100644 index 0000000..26d142a --- /dev/null +++ b/include/dice/hash/lthash/MathEngine_Hwy.cpp @@ -0,0 +1,163 @@ +#include "MathEngine_Hwy.hpp" + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "dice/hash/lthash/MathEngine_Hwy.cpp" +#include + +#include +#include + + +HWY_BEFORE_NAMESPACE(); // at file scope +namespace dice::hash::lthash::detail::HWY_NAMESPACE { + static hwy::HWY_NAMESPACE::Vec> HWY_INLINE little_endian(hwy::HWY_NAMESPACE::Vec> data) { + using namespace hwy::HWY_NAMESPACE; + + if constexpr (std::endian::native == std::endian::little) { + return data; + } + else { + return ReverseLaneBytes(data); + } + } + static void add_with_padding_impl(std::span dst, std::span src, uint64_t data_mask) { + using namespace hwy::HWY_NAMESPACE; + + using D = ScalableTag; + using V = Vec; + + V mask = Set(D{}, data_mask); + + Transform1(D{}, dst.data(), dst.size(), src.data(), [&](D, V d, V s) HWY_ATTR { + V a = little_endian(d); + V b = little_endian(s); + return little_endian(And(Add(a, b), mask)); + }); + } + static void add_no_padding_impl(std::span dst, std::span src, uint64_t mask_group1, uint64_t mask_group2) { + using namespace hwy::HWY_NAMESPACE; + + using D = ScalableTag; + using V = Vec; + + V mask1 = Set(D{}, mask_group1); + V mask2 = Set(D{}, mask_group2); + + Transform1(D{}, dst.data(), dst.size(), src.data(), [&](D, V d, V s) HWY_ATTR { + V a = little_endian(d); + V b = little_endian(s); + + V a1 = And(a, mask1); + V a2 = And(a, mask2); + V b1 = And(b, mask1); + V b2 = And(b, mask2); + + return little_endian(Or(And(Add(a1, b1), mask1), And(Add(a2, b2), mask2))); + }); + } + static void sub_with_padding_impl(std::span dst, std::span src, uint64_t data_mask) { + using namespace hwy::HWY_NAMESPACE; + + using D = ScalableTag; + using V = Vec; + + V mask = Set(D{}, data_mask); + + Transform1(D{}, dst.data(), dst.size(), src.data(), [&](D, V d, V s) HWY_ATTR { + V a = little_endian(d); + V b = little_endian(s); + + V n = And(Sub(Not(mask), b), mask); + return little_endian(And(Add(a, n), mask)); + }); + } + static void sub_no_padding_impl(std::span dst, std::span src, uint64_t mask_group1, uint64_t mask_group2) { + using namespace hwy::HWY_NAMESPACE; + + using D = ScalableTag; + using V = Vec; + + V mask1 = Set(D{}, mask_group1); + V mask2 = Set(D{}, mask_group2); + + Transform1(D{}, dst.data(), dst.size(), src.data(), [&](D, V d, V s) HWY_ATTR { + V a = little_endian(d); + V b = little_endian(s); + + V a1 = And(a, mask1); + V a2 = And(a, mask2); + V b1 = And(b, mask1); + V b2 = And(b, mask2); + + V oi1 = And(Add(a1, Sub(mask2, b1)), mask1); + V oi2 = And(Add(a2, Sub(mask1, b2)), mask2); + + return little_endian(Or(oi1, oi2)); + }); + } + static bool check_padding_bits_impl(std::span data, uint64_t data_mask) { + using namespace hwy::HWY_NAMESPACE; + + using D = ScalableTag; + using V = Vec; + + V mask = Set(D{}, data_mask); + V not_mask = Not(mask); + V zero = Zero(D{}); + + bool r = true; + + Foreach(D{}, data.data(), data.size(), mask, [&](D, V d) { + d = And(little_endian(d), not_mask); + auto m = Eq(d, zero); + if (!AllTrue(D{}, m)) { + r = false; + } + }); + + return r; + } + static void clear_padding_bits_impl(std::span data, uint64_t data_mask) { + using namespace hwy::HWY_NAMESPACE; + + using D = ScalableTag; + using V = Vec; + + V mask = Set(D{}, data_mask); + + Transform(D{}, data.data(), data.size(), [&](D, V d) { + return little_endian(And(little_endian(d), mask)); + }); + } +} +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE +namespace dice::hash::lthash::detail { + HWY_EXPORT(add_with_padding_impl); + HWY_EXPORT(add_no_padding_impl); + HWY_EXPORT(sub_with_padding_impl); + HWY_EXPORT(sub_no_padding_impl); + HWY_EXPORT(check_padding_bits_impl); + HWY_EXPORT(clear_padding_bits_impl); + + void add_with_padding(std::span dst, std::span src, uint64_t data_mask) { + HWY_DYNAMIC_DISPATCH(add_with_padding_impl)(dst, src, data_mask); + } + void add_no_padding(std::span dst, std::span src, uint64_t mask_group1, uint64_t mask_group2) { + HWY_DYNAMIC_DISPATCH(add_no_padding_impl)(dst, src, mask_group1, mask_group2); + } + void sub_with_padding(std::span dst, std::span src, uint64_t data_mask) { + HWY_DYNAMIC_DISPATCH(sub_with_padding_impl)(dst, src, data_mask); + } + void sub_no_padding(std::span dst, std::span src, uint64_t mask_group1, uint64_t mask_group2) { + HWY_DYNAMIC_DISPATCH(sub_no_padding_impl)(dst, src, mask_group1, mask_group2); + } + bool check_padding_bits(std::span data, uint64_t data_mask) { + return HWY_DYNAMIC_DISPATCH(check_padding_bits_impl)(data, data_mask); + } + void clear_padding_bits(std::span data, uint64_t data_mask) { + HWY_DYNAMIC_DISPATCH(clear_padding_bits_impl)(data, data_mask); + } +} +#endif diff --git a/include/dice/hash/lthash/MathEngine_Hwy.hpp b/include/dice/hash/lthash/MathEngine_Hwy.hpp new file mode 100644 index 0000000..4301c96 --- /dev/null +++ b/include/dice/hash/lthash/MathEngine_Hwy.hpp @@ -0,0 +1,98 @@ +#ifndef DICE_HASH_MATHENGINE_HWY_HPP +#define DICE_HASH_MATHENGINE_HWY_HPP + +#include +#include +#include +#include +#include +#include + +namespace dice::hash::lthash { + namespace detail { + void add_with_padding(std::span dst, std::span src, uint64_t data_mask); + void add_no_padding(std::span dst, std::span src, uint64_t mask_group1, uint64_t mask_group2); + void sub_with_padding(std::span dst, std::span src, uint64_t data_mask); + void sub_no_padding(std::span dst, std::span src, uint64_t mask_group1, uint64_t mask_group2); + bool check_padding_bits(std::span data, uint64_t data_mask); + void clear_padding_bits(std::span data, uint64_t data_mask); + } + + template + struct MathEngine_Hwy { + static constexpr size_t min_buffer_align = alignof(uint64_t); + + template + static void add(std::span dst, std::span src) noexcept { + assert(dst.size() == src.size()); + assert(dst.size() % sizeof(uint64_t) == 0); + assert(reinterpret_cast(dst.data()) % alignof(uint64_t) == 0); + assert(reinterpret_cast(src.data()) % alignof(uint64_t) == 0); + + std::span dst64{reinterpret_cast(dst.data()), dst.size() / sizeof(uint64_t)}; + std::span src64{reinterpret_cast(src.data()), src.size() / sizeof(uint64_t)}; + + if constexpr (!Bits::needs_padding) { + static_assert(Bits::bits_per_element == 16 || Bits::bits_per_element == 32, + "Only 16 and 32 bit elements are implemented for non-padded data"); + + static constexpr uint64_t mask_group_1 = Bits::bits_per_element == 16 + ? 0xffff0000ffff0000ull + : 0xffffffff00000000ull; + static constexpr uint64_t mask_group_2 = ~mask_group_1; + + detail::add_no_padding(dst64, src64, mask_group_1, mask_group_2); + } else { + detail::add_with_padding(dst64, src64, Bits::data_mask); + } + } + + template + static void sub(std::span dst, std::span src) noexcept { + assert(dst.size() == src.size()); + assert(dst.size() % sizeof(uint64_t) == 0); + assert(reinterpret_cast(dst.data()) % alignof(uint64_t) == 0); + assert(reinterpret_cast(src.data()) % alignof(uint64_t) == 0); + + std::span dst64{reinterpret_cast(dst.data()), dst.size() / sizeof(uint64_t)}; + std::span src64{reinterpret_cast(src.data()), src.size() / sizeof(uint64_t)}; + + if constexpr (!Bits::needs_padding) { + static_assert(Bits::bits_per_element == 16 || Bits::bits_per_element == 32, + "Only 16 and 32 bit elements are implemented for non-padded data"); + + static constexpr uint64_t mask_group_1 = Bits::bits_per_element == 16 + ? 0xffff0000ffff0000ull + : 0xffffffff00000000ull; + static constexpr uint64_t mask_group_2 = ~mask_group_1; + + detail::sub_no_padding(dst64, src64, mask_group_1, mask_group_2); + } else { + detail::sub_with_padding(dst64, src64, Bits::data_mask); + } + } + + template + requires(Bits::needs_padding) + static bool check_padding_bits(std::span data) noexcept { + assert(data.size() % sizeof(uint64_t) == 0); + assert(reinterpret_cast(data.data()) % alignof(uint64_t) == 0); + + std::span data64{reinterpret_cast(data.data()), data.size() / sizeof(uint64_t)}; + return detail::check_padding_bits(data64, Bits::data_mask); + } + + template + requires(Bits::needs_padding) + static void clear_padding_bits(std::span data) noexcept { + assert(data.size() % sizeof(uint64_t) == 0); + assert(reinterpret_cast(data.data()) % alignof(uint64_t) == 0); + + std::span data64{reinterpret_cast(data.data()), data.size() / sizeof(uint64_t)}; + detail::clear_padding_bits(data64, Bits::data_mask); + } + }; + +} // namespace dice::hash::lthash + +#endif//DICE_HASH_MATHENGINE_HWY_HPP diff --git a/include/dice/hash/lthash/MathEngine_Simple.hpp b/include/dice/hash/lthash/MathEngine_Simple.hpp new file mode 100644 index 0000000..db4a9bf --- /dev/null +++ b/include/dice/hash/lthash/MathEngine_Simple.hpp @@ -0,0 +1,143 @@ +#ifndef DICE_HASH_MATHENGINE_SIMPLE_HPP +#define DICE_HASH_MATHENGINE_SIMPLE_HPP + +#include +#include +#include +#include +#include + +namespace dice::hash::lthash { + + // todo deduplicate with other definitions + template + inline T little_endian(T const &value) noexcept { + if constexpr (std::endian::native == std::endian::little) { + return value; + } else { + auto const *input_beg = byte_iter(value); + auto const *input_end = input_beg + sizeof(T); + T output; + + std::reverse_copy(input_beg, input_end, byte_iter_mut(output)); + return output; + } + } + + template + struct MathEngine_Simple { + static constexpr size_t min_buffer_align = alignof(uint64_t); + + template + static void add(std::span dst, std::span src) noexcept { + assert(dst.size() == src.size()); + assert(dst.size() % sizeof(uint64_t) == 0); + assert(reinterpret_cast(dst.data()) % alignof(uint64_t) == 0); + assert(reinterpret_cast(src.data()) % alignof(uint64_t) == 0); + + std::span dst64{reinterpret_cast(dst.data()), dst.size() / sizeof(uint64_t)}; + std::span src64{reinterpret_cast(src.data()), src.size() / sizeof(uint64_t)}; + + if constexpr (!Bits::needs_padding) { + static_assert(Bits::bits_per_element == 16 || Bits::bits_per_element == 32, + "Only 16 and 32 bit elements are implemented for non-padded data"); + + static constexpr uint64_t mask_group_1 = Bits::bits_per_element == 16 + ? 0xffff0000ffff0000ull + : 0xffffffff00000000ull; + static constexpr uint64_t mask_group_2 = ~mask_group_1; + + for (size_t ix = 0; ix < dst64.size(); ++ix) { + auto const ai = little_endian(dst64[ix]); + auto const bi = little_endian(src64[ix]); + + auto const ai1 = ai & mask_group_1; + auto const ai2 = ai & mask_group_2; + auto const bi1 = bi & mask_group_1; + auto const bi2 = bi & mask_group_2; + + uint64_t oi1 = (ai1 + bi1) & mask_group_1; + uint64_t oi2 = (ai2 + bi2) & mask_group_2; + dst64[ix] = little_endian(oi1 | oi2); + } + } else { + for (size_t ix = 0; ix < dst64.size(); ++ix) { + auto const ai = little_endian(dst64[ix]); + auto const bi = little_endian(src64[ix]); + dst64[ix] = little_endian((ai + bi) & Bits::data_mask); + } + } + } + + template + static void sub(std::span dst, std::span src) noexcept { + assert(dst.size() == src.size()); + assert(dst.size() % sizeof(uint64_t) == 0); + assert(reinterpret_cast(dst.data()) % alignof(uint64_t) == 0); + assert(reinterpret_cast(src.data()) % alignof(uint64_t) == 0); + + std::span dst64{reinterpret_cast(dst.data()), dst.size() / sizeof(uint64_t)}; + std::span src64{reinterpret_cast(src.data()), src.size() / sizeof(uint64_t)}; + + if constexpr (!Bits::needs_padding) { + static_assert(Bits::bits_per_element == 16 || Bits::bits_per_element == 32, + "Only 16 and 32 bit elements are implemented for non-padded data"); + + static constexpr uint64_t mask_group_1 = Bits::bits_per_element == 16 + ? 0xffff0000ffff0000ull + : 0xffffffff00000000ull; + static constexpr uint64_t mask_group_2 = ~mask_group_1; + + for (size_t ix = 0; ix < dst64.size(); ++ix) { + auto const ai = little_endian(dst64[ix]); + auto const bi = little_endian(src64[ix]); + + auto const ai1 = ai & mask_group_1; + auto const ai2 = ai & mask_group_2; + auto const bi1 = bi & mask_group_1; + auto const bi2 = bi & mask_group_2; + + uint64_t oi1 = (ai1 + (mask_group_2 - bi1)) & mask_group_1; + uint64_t oi2 = (ai2 + (mask_group_1 - bi2)) & mask_group_2; + dst64[ix] = little_endian(oi1 | oi2); + } + } else { + for (size_t ix = 0; ix < dst64.size(); ++ix) { + auto const ai = little_endian(dst64[ix]); + auto const bi = little_endian(src64[ix]); + dst64[ix] = little_endian((ai + ((~Bits::data_mask - bi) & Bits::data_mask)) & Bits::data_mask); + } + } + } + + template + static bool check_padding_bits(std::span data) noexcept requires (Bits::needs_padding) { + assert(data.size() % sizeof(uint64_t) == 0); + assert(reinterpret_cast(data.data()) % alignof(uint64_t) == 0); + + std::span data64{reinterpret_cast(data.data()), data.size() / sizeof(uint64_t)}; + for (auto const val : data64) { + if ((little_endian(val) & ~Bits::data_mask) != 0) { + return false; + } + } + return true; + } + + template + static void clear_padding_bits(std::span data) noexcept requires (Bits::needs_padding) { + assert(data.size() % sizeof(uint64_t) == 0); + assert(reinterpret_cast(data.data()) % alignof(uint64_t) == 0); + + if constexpr (Bits::needs_padding) { + std::span data64{reinterpret_cast(data.data()), data.size() / sizeof(uint64_t)}; + for (auto &val : data64) { + val = little_endian(little_endian(val) & Bits::data_mask); + } + } + } + }; + +} // namespace dice::hash::lthash + +#endif//DICE_HASH_MATHENGINE_SIMPLE_HPP diff --git a/test_package/conanfile.py b/test_package/conanfile.py index 355d54e..180ec70 100644 --- a/test_package/conanfile.py +++ b/test_package/conanfile.py @@ -13,6 +13,9 @@ class TestPackageConan(ConanFile): def requirements(self): self.requires(self.tested_reference_str) + def configure(self): + self.options["dice-hash"].with_sodium = True + def layout(self): cmake_layout(self) diff --git a/test_package/example.cpp b/test_package/example.cpp index 23c4261..165e16a 100644 --- a/test_package/example.cpp +++ b/test_package/example.cpp @@ -1,8 +1,38 @@ #include +#include +#include #include +void print_bytes(std::span bytes) noexcept { + for (auto b : bytes) { + std::cout << std::hex << static_cast(b); + } + std::cout << "\n\n"; +} + int main() { std::cout << "wyhash(42): " << dice::hash::DiceHash()(42) << std::endl; + std::cout << "blake3(42): "; + { + std::vector output; + output.resize(58); + std::array data{static_cast(42)}; + dice::hash::blake3::Blake3<>::hash_single(data, output); + + print_bytes(output); + } + std::cout << std::endl; + std::cout << "lthash(42): "; + { + using namespace dice::hash::lthash; + LtHash16 lthash; + + std::array data{static_cast(42)}; + lthash.add(data); + std::vector const checksum1{lthash.checksum().begin(), lthash.checksum().end()}; + print_bytes(checksum1); + } + std::cout << std::endl; } \ No newline at end of file diff --git a/tests/BenchmarkBlake2xb.cpp b/tests/BenchmarkBlake2xb.cpp new file mode 100644 index 0000000..4cfd9e4 --- /dev/null +++ b/tests/BenchmarkBlake2xb.cpp @@ -0,0 +1,136 @@ +#include +#include + +/** +* @note Benchmarks adapted from https://github.com/facebook/folly/blob/main/folly/experimental/crypto/test/Blake2xbBenchmark.cpp +*/ + +using namespace dice::hash; + +void benchmark_blake2b(size_t input_size, size_t n) { + std::array result; + std::vector input; + + input.resize(input_size); + + for (size_t i = 0; i < static_cast(n); ++i) { + int res = crypto_generichash_blake2b(result.data(), sizeof(result), input.data(), input.size(), nullptr, 0); + if (res != 0) { + throw std::runtime_error("blake2b hash failed"); + } + } +} + +void benchmark_blake2b_multiple(size_t input_size, size_t m, size_t n) { + std::vector input; + std::vector output; + std::vector personalization; + std::array h0; + + output.resize(crypto_generichash_blake2b_BYTES_MAX * m); + input.resize(input_size); + personalization.resize(crypto_generichash_blake2b_PERSONALBYTES); + + for (size_t i = 0; i < static_cast(n); ++i) { + int res = crypto_generichash_blake2b( + h0.data(), sizeof(h0), input.data(), input.size(), nullptr, 0); + if (res != 0) { + throw std::runtime_error("blake2b hash failed"); + } + + for (size_t j = 0; j < m; j++) { + res = crypto_generichash_blake2b_salt_personal( + output.data() + (crypto_generichash_blake2b_BYTES_MAX * j), + crypto_generichash_blake2b_BYTES_MAX, + h0.data(), + h0.size(), + nullptr /* key */, + 0 /* keylen */, + nullptr /* salt */, + personalization.data()); + if (res != 0) { + throw std::runtime_error("blake2b hash failed"); + } + sodium_increment( + personalization.data(), crypto_generichash_blake2b_PERSONALBYTES); + } + } +} + +void benchmark_blake2xb(size_t input_size, size_t output_size, size_t n) { + std::vector input; + std::vector output; + + input.resize(input_size); + output.resize(output_size); + + for (size_t i = 0; i < static_cast(n); ++i) { + blake2xb::Blake2Xb<>::hash_single(input, output); + } +} + +TEST_CASE("Benchmark Blake2Xb") { + BENCHMARK("blake2b 100b in 64b out", n) { + benchmark_blake2b(100, n); + }; + + BENCHMARK("blake2xb 100b in 64b out", n) { + benchmark_blake2xb(100, 64, n); + }; + + BENCHMARK("blake2b 100b in 128b out", n) { + benchmark_blake2b_multiple(100, 128 / 64, n); + }; + + BENCHMARK("blake2xb 100b in 128b out", n) { + benchmark_blake2xb(100, 128, n); + }; + + BENCHMARK("blake2b 100b in 1024b out", n) { + benchmark_blake2b_multiple(100, 1024 / 64, n); + }; + + BENCHMARK("blake2xb 100b in 1024b out", n) { + benchmark_blake2xb(100, 1024, n); + }; + + BENCHMARK("blake2b 100b in 4096b out", n) { + benchmark_blake2b_multiple(100, 4096 / 64, n); + }; + + BENCHMARK("blake2xb 100b in 4096b out", n) { + benchmark_blake2xb(100, 4096, n); + }; + + BENCHMARK("blake2b 1000b in 64b out", n) { + benchmark_blake2b(1000, n); + }; + + BENCHMARK("blake2xb 1000b in 64b out", n) { + benchmark_blake2xb(1000, 64, n); + }; + + BENCHMARK("blake2b 1000b in 128b out", n) { + benchmark_blake2b_multiple(1000, 128 / 64, n); + }; + + BENCHMARK("blake2xb 1000b in 128b out", n) { + benchmark_blake2xb(1000, 128, n); + }; + + BENCHMARK("blake2b 1000b in 1024b out", n) { + benchmark_blake2b_multiple(1000, 1024 / 64, n); + }; + + BENCHMARK("blake2xb 1000b in 1024b out", n) { + benchmark_blake2xb(1000, 1024, n); + }; + + BENCHMARK("blake2b 1000b in 4096b out", n) { + benchmark_blake2b_multiple(1000, 4096 / 64, n); + }; + + BENCHMARK("blake2xb 1000b in 4096b out", n) { + benchmark_blake2xb(1000, 4096, n); + }; +} diff --git a/tests/BenchmarkLtHash_Hwy.cpp b/tests/BenchmarkLtHash_Hwy.cpp new file mode 100644 index 0000000..79caeba --- /dev/null +++ b/tests/BenchmarkLtHash_Hwy.cpp @@ -0,0 +1,3 @@ +#define DICE_HASH_BENCHMARK_LTHASH_MATH_ENGINE MathEngine_Hwy +#define DICE_HASH_BENCHMARK_LTHASH_INSTRUCTION_SET "Hwy" +#include "BenchmarkLtHash_template.hpp" diff --git a/tests/BenchmarkLtHash_simple.cpp b/tests/BenchmarkLtHash_simple.cpp new file mode 100644 index 0000000..d5bcddb --- /dev/null +++ b/tests/BenchmarkLtHash_simple.cpp @@ -0,0 +1,3 @@ +#define DICE_HASH_BENCHMARK_LTHASH_MATH_ENGINE MathEngine_Simple +#define DICE_HASH_BENCHMARK_LTHASH_INSTRUCTION_SET "x86_64" +#include "BenchmarkLtHash_template.hpp" // TODO figure out how to disable SSE2 for sodium \ No newline at end of file diff --git a/tests/BenchmarkLtHash_template.hpp b/tests/BenchmarkLtHash_template.hpp new file mode 100644 index 0000000..4e58fb4 --- /dev/null +++ b/tests/BenchmarkLtHash_template.hpp @@ -0,0 +1,136 @@ +#include +#include + +/** + * @note Benchmarks adapted from https://github.com/facebook/folly/blob/main/folly/experimental/crypto/test/LtHashBenchmark.cpp + */ + +std::vector make_random_data(size_t length) { + std::vector data; + data.resize(length); + + std::default_random_engine rng{std::random_device{}()}; + std::uniform_int_distribution dist{0, std::numeric_limits::max()}; + + for (size_t ix = 0; ix < length; ++ix) { + data[ix] = static_cast(dist(rng)); + } + + return data; +} + +std::vector> objects = []() { + std::vector> ret; + for (size_t i = 0; i < 1000; ++i) { + ret.push_back(make_random_data(i)); + } + return ret; +}(); + +using namespace dice::hash::lthash; +using namespace dice::hash::blake3; + +template +using H = LtHash; + +template +void run_benchmark(size_t n) { + H lt; + for (size_t i = 0; i < n; ++i) { + auto const &obj = objects[i % objects.size()]; + lt.add(obj); + } +} + +TEST_CASE("Benchmark LtHash using " DICE_HASH_BENCHMARK_LTHASH_INSTRUCTION_SET, "[DiceHash]") { + BENCHMARK("LtHash<16, 1024>", n) { + run_benchmark<16, 1024>(n); + }; + + BENCHMARK("LtHash<20, 1008>", n) { + run_benchmark<20, 1008>(n); + }; + + BENCHMARK("LtHash<32, 1024>", n) { + run_benchmark<32, 1024>(n); + }; + + BENCHMARK("LtHash<32, 2048>", n) { + run_benchmark<32, 2048>(n); + }; + + BENCHMARK("LtHash<20, 1008> add 100k elems") { + LtHash<20, 1008> lt; + for (auto i = 0; i < 100'000; ++i) { + auto const &obj = objects[i % objects.size()]; + lt.add(obj); + } + }; + + BENCHMARK("LtHash<16, 1024> add 100k elems") { + LtHash<16, 1024> lt; + for (auto i = 0; i < 100'000; ++i) { + auto const &obj = objects[i % objects.size()]; + lt.add(obj); + } + }; + + BENCHMARK("LtHash<32, 1024> add 100k elems") { + LtHash<32, 1024> lt; + for (auto i = 0; i < 100'000; ++i) { + auto const &obj = objects[i % objects.size()]; + lt.add(obj); + } + }; + + BENCHMARK("LtHash<20, 1008> remove 100k elems") { + LtHash<20, 1008> lt; + for (auto i = 0; i < 100'000; ++i) { + auto const &obj = objects[i % objects.size()]; + lt.remove(obj); + } + }; + + BENCHMARK("LtHash<16, 1024> remove 100k elems") { + LtHash<16, 1024> lt; + for (auto i = 0; i < 100'000; ++i) { + auto const &obj = objects[i % objects.size()]; + lt.remove(obj); + } + }; + + BENCHMARK("LtHash<32, 1024> remove 100k elems") { + LtHash<32, 1024> lt; + for (auto i = 0; i < 100'000; ++i) { + auto const &obj = objects[i % objects.size()]; + lt.remove(obj); + } + }; + + BENCHMARK_ADVANCED("LtHash<16, 1024> add 150B object")(Catch::Benchmark::Chronometer meter) { + LtHash<16, 1024> lt; + auto obj = make_random_data(150); + + meter.measure([&]() { + lt.add(obj); + }); + }; + + BENCHMARK_ADVANCED("LtHash<20, 1008> add 150B object")(Catch::Benchmark::Chronometer meter) { + LtHash<20, 1008> lt; + auto obj = make_random_data(150); + + meter.measure([&]() { + lt.add(obj); + }); + }; + + BENCHMARK_ADVANCED("LtHash<32, 1024> add 150B object")(Catch::Benchmark::Chronometer meter) { + LtHash<32, 1024> lt; + auto obj = make_random_data(150); + + meter.measure([&]() { + lt.add(obj); + }); + }; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d2a66af..ee2dca1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,13 +4,99 @@ set(CMAKE_CXX_EXTENSIONS OFF) find_package(Catch2 REQUIRED) +include(CTest) +list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/contrib) +include(Catch) + add_executable(tests_dice_hash TestDiceHash.cpp) target_link_libraries(tests_dice_hash PRIVATE - Catch2::Catch2 + Catch2::Catch2WithMain dice-hash::dice-hash ) - -include(CTest) -list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/contrib) -include(Catch) +set_target_properties(tests_dice_hash PROPERTIES CXX_STANDARD 20) catch_discover_tests(tests_dice_hash) + +if (WITH_SODIUM) + add_executable(tests_Blake2b TestBlake2b.cpp) + target_link_libraries(tests_Blake2b PRIVATE + Catch2::Catch2WithMain + dice-hash::dice-hash + ) + set_target_properties(tests_Blake2b PROPERTIES CXX_STANDARD 20) + catch_discover_tests(tests_Blake2b) + + add_executable(tests_Blake2Xb TestBlake2Xb.cpp) + target_link_libraries(tests_Blake2Xb PRIVATE + Catch2::Catch2WithMain + dice-hash::dice-hash + ) + set_target_properties(tests_Blake2Xb PROPERTIES CXX_STANDARD 20) + catch_discover_tests(tests_Blake2Xb) + + add_executable(tests_LtHash_simple TestLtHash_simple.cpp) + target_link_libraries(tests_LtHash_simple PRIVATE + Catch2::Catch2WithMain + dice-hash::dice-hash + ) + set_target_properties(tests_LtHash_simple PROPERTIES CXX_STANDARD 20) + catch_discover_tests(tests_LtHash_simple) + + add_executable(tests_LtHash_Hwy TestLtHash_Hwy.cpp) + target_link_libraries(tests_LtHash_Hwy PRIVATE + Catch2::Catch2WithMain + dice-hash::dice-hash + highway::highway + ) + set_target_properties(tests_LtHash_Hwy PROPERTIES CXX_STANDARD 20) + catch_discover_tests(tests_LtHash_Hwy) + + add_executable(benchmark_Blake2xb BenchmarkBlake2xb.cpp) + target_link_libraries(benchmark_Blake2xb PRIVATE + Catch2::Catch2WithMain + dice-hash::dice-hash + ) + set_target_properties(benchmark_Blake2xb PROPERTIES CXX_STANDARD 20) + catch_discover_tests(benchmark_Blake2xb) + + add_executable(benchmark_LtHash_simple BenchmarkLtHash_simple.cpp) + target_link_libraries(benchmark_LtHash_simple PRIVATE + Catch2::Catch2WithMain + dice-hash::dice-hash + ) + set_target_properties(benchmark_LtHash_simple PROPERTIES CXX_STANDARD 20) + catch_discover_tests(benchmark_LtHash_simple) + + add_executable(benchmark_LtHash_Hwy BenchmarkLtHash_Hwy.cpp) + target_link_libraries(benchmark_LtHash_Hwy PRIVATE + Catch2::Catch2WithMain + dice-hash::dice-hash + ) + set_target_properties(benchmark_LtHash_Hwy PROPERTIES CXX_STANDARD 20) + catch_discover_tests(benchmark_LtHash_Hwy) + + find_package(Metall REQUIRED) + + add_executable(TestLtHash_metall_phase1 TestLtHash_metall_phase1.cpp) + target_link_libraries(TestLtHash_metall_phase1 PRIVATE + dice-hash::dice-hash + Metall::Metall + ) + set_target_properties(TestLtHash_metall_phase1 PROPERTIES CXX_STANDARD 20) + + add_executable(TestLtHash_metall_phase2 TestLtHash_metall_phase2.cpp) + target_link_libraries(TestLtHash_metall_phase2 PRIVATE + dice-hash::dice-hash + Metall::Metall + ) + set_target_properties(TestLtHash_metall_phase2 PROPERTIES CXX_STANDARD 20) + + add_executable(tests_LtHash_metall TestLtHash_metall.cpp) + add_dependencies(tests_LtHash_metall TestLtHash_metall_phase1 TestLtHash_metall_phase2) + target_link_libraries(tests_LtHash_metall PRIVATE + Catch2::Catch2WithMain + dice-hash::dice-hash + Metall::Metall + ) + set_target_properties(tests_LtHash_metall PROPERTIES CXX_STANDARD 20) + catch_discover_tests(tests_LtHash_metall) +endif () diff --git a/tests/TestBlake2Xb.cpp b/tests/TestBlake2Xb.cpp new file mode 100644 index 0000000..97ac095 --- /dev/null +++ b/tests/TestBlake2Xb.cpp @@ -0,0 +1,61 @@ +#include +#include + +#include "TestBlake2xb_data.hpp" + +using namespace dice::hash; + +// integer value -> hexadecimal ascii representation (e.g 0 => '0', 10 => 'a') +static constexpr std::array encode_lut{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f'}; + +static char hex_encode(uint8_t const half_octet) noexcept { + assert(half_octet <= 15); + return encode_lut[half_octet]; +} + +std::string to_hex(std::span bytes) noexcept { + if (bytes.empty()) { + return ""; + } + + std::string buf; + for (auto const byte : bytes) { + auto const lower = static_cast(byte) & 0b1111; + auto const higher = (static_cast(byte) >> 4) & 0b1111; + + buf.push_back(hex_encode(higher)); + buf.push_back(hex_encode(lower)); + } + + return buf; +} + +void check(std::span k, std::vector const &data) { + auto const &input = hash_input; + + for (auto const &expected_hex : data) { + SECTION(std::string{k.empty() ? "keyless, " : "keyed, "} + "output len: " + std::to_string(expected_hex.size() / 2)) { + std::vector actual; + actual.resize(expected_hex.size() / 2); + + blake2xb::Blake2Xb<>::hash_single(input, actual, k); + std::string const actual_hex = to_hex(actual); + + CHECK(expected_hex == actual_hex); + } + } +} + +/** + * @note tests are adapted from: https://github.com/facebook/folly/blob/main/folly/experimental/crypto/test/Blake2xbTest.cpp + */ +TEST_CASE("Blake2Xb", "[DiceHash]") { + SECTION("keyless") { + check({}, test_data); + } + + SECTION("keyed") { + check(key, keyed_test_data); + } +} diff --git a/tests/TestBlake2b.cpp b/tests/TestBlake2b.cpp new file mode 100644 index 0000000..15049fd --- /dev/null +++ b/tests/TestBlake2b.cpp @@ -0,0 +1,56 @@ +#include +#include +#include + +#include + +using namespace dice::hash::blake2b; + +void print_span(std::span bytes) noexcept { + for (auto const b : bytes) { + std::cout << std::hex << static_cast(b); + } + std::cout << std::endl; +} + +TEST_CASE("Blake2b", "[DiceHash]") { + SECTION("keygen") { + SECTION("static length") { + std::array key; + generate_key(std::span{key}); + print_span(key); + } + + SECTION("dynamic length") { + std::vector key; + key.resize(45); + generate_key(std::span{key}); + print_span(key); + } + + SECTION("too small") { + std::array key; + CHECK_THROWS(generate_key(std::span{key})); + } + + SECTION("too big") { + std::array key; + CHECK_THROWS(generate_key(std::span{key})); + } + } + + SECTION("hash generation sanity check") { + auto const data = as_bytes(std::span{"spherical cow"}); + + std::array output1; + Blake2b<>::hash_single(data, output1); + + crypto_generichash_blake2b_state state; + crypto_generichash_blake2b_init(&state, nullptr, 0, max_output_extent); + crypto_generichash_blake2b_update(&state, reinterpret_cast(data.data()), data.size()); + std::array output2; + crypto_generichash_blake2b_final(&state, reinterpret_cast(output2.data()), output2.size()); + + CHECK(output1 == output2); + } +} diff --git a/tests/TestBlake2xb_data.hpp b/tests/TestBlake2xb_data.hpp new file mode 100644 index 0000000..17a0001 --- /dev/null +++ b/tests/TestBlake2xb_data.hpp @@ -0,0 +1,2780 @@ +#ifndef DICE_HASH_TESTBLAKE2XB_DATA_HPP +#define DICE_HASH_TESTBLAKE2XB_DATA_HPP + +#include +#include + +/** + * @note test data extracted from: https://github.com/facebook/folly/blob/main/folly/experimental/crypto/test/Blake2xbTest.cpp + */ + +static std::vector hash_input = []() { + std::vector b; + b.resize(256); + for (size_t i = 0; i < 256; ++i) { + b[i] = static_cast(i); + } + return b; +}(); + +static std::vector key = []() { + std::vector b; + b.resize(64); + for (size_t i = 0; i < b.size(); ++i) { + b[i] = static_cast(i); + } + return b; +}(); + +static std::vector test_data = []() { + std::vector v; + v.push_back(""); + v.push_back("f0"); + v.push_back("b5aa"); + v.push_back("bc38f1"); + v.push_back("57624fb2"); + v.push_back("ea9d54f5f2"); + v.push_back("2bcb84c09d35"); + v.push_back("2df3b0c53f2967"); + v.push_back("26de76fed412b6f1"); + v.push_back("b91f740750ffdb2aa9"); + v.push_back("e161ee158218cfd98f91"); + v.push_back("6bdaf88b0922b637274001"); + v.push_back("eff9f92cbd769c81a64c20f2"); + v.push_back("313f0d863f80e115d342afc286"); + v.push_back("9ac7c942487e8f48b1bec271562d"); + v.push_back("221d56ed5ac8b0936111c773f9a744"); + v.push_back("d0a446adfd1fe0cc61e42e70772584c2"); + v.push_back("a7248218b83af8ca2728d9aca773438100"); + v.push_back("7ada5a3cd09be192f152e36eb77a49228bd5"); + v.push_back("8969cf3e34b108498786f3c9807a54da29c5df"); + v.push_back("d6981306dc3865bebe4ee085ae81a11d43601bba"); + v.push_back("e1f7cd444fc960831929a1ba4c81316537e315dab1"); + v.push_back("dd675ed39403b1500a2a619889c2d9d91791fe15d177"); + v.push_back("d503e3a0261176767e318f079e9c4941cee791d3db3e03"); + v.push_back("4bcb663054528ad4cc568d12ee1ac0e33790e0635189e53e"); + v.push_back("ee8ad528ce57395cfc101654a2e205deb62429640755a6067c"); + v.push_back("8435fb101e96b9dbca75a444212a99211ccb35173e9f1c2b01c9"); + v.push_back("bb849abcb58a9e8a3a2becdf0550774cff0093d5ee5dab5e9db38a"); + v.push_back("0a74d45327c814c45abc41713680021f487be9a133455a7550fad2e2"); + v.push_back("f662e426340a253766ad9a1a13b7f60db4bdf953a04456789b5a261543"); + v.push_back( + "1a03271192bdc3981f166cb43945d4d78878cd2a2ab620f56337f4cf1206"); + v.push_back( + "e8cdc5a0f15392219c8ad35abf0c1a976fb430debe801887ac8000a7968fd5"); + v.push_back( + "b5d259e2e3a86c77cbf6d53f9dc78daddc2afd84dbb4ba7e9891227fec079d5a"); + v.push_back( + "d3818948294fdecac8411f860f7cad50469df5d1485524e059d4dd8cfb69c32b" + "bd"); + v.push_back( + "8e3ee191e4d30346f19ab6904b6e810d416a87a1da3c7f78445db72fe49f6157" + "05a6"); + v.push_back( + "c2622f680f4350aadb5dc35300e08197b1e968a1df8b3091cadc3abfe261a269" + "605319"); + v.push_back( + "7030a487b0c270d224a0f2eba284b876ac44576a119546af47627417ddce0f46" + "50bb8b56"); + v.push_back( + "1c09ce4bedf8ee67d19430f8a4708d73f0be22e19c55fd397471e7705ff99586" + "03911f6a38"); + v.push_back( + "84f7f7502bd140a07b57e0f69863874a635403c111836b3fdfd27a030582e6d8" + "e46162b62cf7"); + v.push_back( + "63971b351ba119eff6342fdfda6bd558edada5c56e65ec0648ba3455fca1a3d5" + "1b603c028ead8f"); + v.push_back( + "64e5a1f06e4d5cb5859ff01f3af1dcecdb89729d97aad3d4c8cd96eed4bd1048" + "7f918ea0ec6c96c0"); + v.push_back( + "d3cbcee509e8efa84f4c54f6eb097f17ba98c23024097f8ae5498d364d45afec" + "b6ec1654e2e3c073c1"); + v.push_back( + "c35631a7735d09551b02c64eb3edfcfdd515c12646bb51695ac51681b3384119" + "7e92f6c3a0a2691cfbd9"); + v.push_back( + "4835394320ff955c272fce2a6eefc8279aaf63492610912ccb525a9f3c788703" + "97c6dd35119900e88385d8"); + v.push_back( + "66c4227b1a29889d5cc7e2025fa4649c365e56d153eb4c4e1790b3f0a26a3553" + "c51f198b04851401af1c0acb"); + v.push_back( + "a1550d81d0e465b8e1bd228e0d3b71c29a23f8cf58d9c43361ecac7eff8698cd" + "68bca923c25d08be3c92bb9926"); + v.push_back( + "3ba25c21d16bb7c93ce16fc914b1bdb5b7ec249ba7dda6be1533c76e8dc20b70" + "4913cbe53201086b0e14e5901042"); + v.push_back( + "0c4c0363a32aa180de2d3aebad786dfa1612141ae77f2eefedda3fc6366f34ef" + "8d64e496a30e972ead3760f13553f5"); + v.push_back( + "60fea990c5efb33b64005694d1ec92c90ea86f434ffc603cc26393a6bcdaaf99" + "c8993f6f2fc5a3080a5cca5532697a08"); + v.push_back( + "67c9623f20ada31853c15e3d973b8a9c69643c3e328908bd138fa7d74789b4e5" + "408da66ca04f0a286cda823c738958665c"); + v.push_back( + "dcda6413fdbd760d59bcdf5d28000c099818c9237511acdc6e0ad40819d47c40" + "f0f883bb0b98d3caefb7fbd281db805d3aa8"); + v.push_back( + "120c123171c486c9a11af8d7a1aeef60f78d071c8edb55fd97959261e4c708ac" + "06eacee87e657b84a5072a7989b101c98b0415"); + v.push_back( + "434cb92246e474ce066de67de1fce06ab17416438598d3cff730faf1deb45748" + "12e877f6a3f2dbc30a3e48a2cf4e441da32c4ee9"); + v.push_back( + "dd4cde9f87ecd959a41c6454869e55342ce0e5d2ff306b3d4dd2263365e192ee" + "6781fe463175280d4682b397b8d6020699ed3a9611"); + v.push_back( + "df8471721ee7aae06d5aab20ac9ab5624797cd0311ee38116eec76748a42aa5a" + "d23d1e3dcadbfba6c296aceaa05512cf1f2a2b415c14"); + v.push_back( + "d226e59df92c995bd5fac8b9cdbd4bce4e11d6b2a2cc382b253a5188e7f41640" + "63d1daff2254b4cfc7ecca462b7c1e11080c1ae51dd908"); + v.push_back( + "0316b9cf375915d70a2c0a0f6560a609b3fd43bc8b26b8489caaece3c8cab25b" + "eecc3bb86d3860d6f2ca9297625fa2f2d5fcca5f6f0a32f6"); + v.push_back( + "828bdf033346520f262ac1383a5e7091640fb9df39c51f82de52bc61b7284f4d" + "7cfb1e90fa19d0ffe3f38dfd60fd05136d66c190cc47639634"); + v.push_back( + "9388577eb0bacab40bfc38e2333f82fe72b575392db914da2706bf2a30787d63" + "8b6b31343e8245e4dbb2c962cf3f940d8ed0945d2db04b902b0c"); + v.push_back( + "5a79d4de91c298c87bfba338f3f85efe00275f6e5463419af83c34129586f30a" + "3d36f57bdf68c9b5e16373c9f9921866c302bc75722c314fc57cf4"); + v.push_back( + "73068c5b623b14802a9eb899f6285ebb7e601c4d916b0255762b7cccc2161417" + "695818a605fed681fd4016e4cb1ad3a42cd88fe1a3f73367ae0aaff6"); + v.push_back( + "668d87bfd70619d877ee8d8f86b5d5ecc4df2aacbad00f2dcf7c80741be0b890" + "bf404bf62c1b4c8d1b0201ffb92d85fdc45149fa58f557a9c6a0190e7d"); + v.push_back( + "cbaab54fe4bfd9ed5d9b7fc97d8a4105af147e13f009514ddb083402ee941ecc" + "7dc3c286308d9555bee67cd73505142758db79fd819ed399e490b801623c"); + v.push_back( + "d9942e996573688a348aa0fd1a2951b11d7732103acc23f31f27b222d5103879" + "b9d3837f2571a7aebffd170ad03cfd89281f48fa70edb7c9f4103b5b8bb791"); + v.push_back( + "571be91037c15145e2ab4894a7bb8d8a3cab75e6e64ef296e760c15cf8f3f3ac" + "fa5c894ee56cb6ac2db9b32c39a1cc39f96c50dd333f1059230482f3ed2d9246"); + v.push_back( + "c6f0b1b66f22726cef3e4fca2325d2bb4e922b39f9df5ef548d321419c07391f" + "c311904407f98db7d7462db1e8576138baeac2a76400b2a2f72b4497c19e2394" + "30"); + v.push_back( + "43cb5507bcb6d7162f7b1d6b81a958b1e21ed2db9ae907a663819ae0d613ebb4" + "3c4b39359ff859ce90a9f65c369c6d30b83aa56b0107a5193a6fadced2d6c0ec" + "5be9"); + v.push_back( + "d412ca3d1b8fa9adcda589c5c422c295c3fe953ffd896e7a98f9262689bd1067" + "0047d9224b685c449b6daa5ff5d660c3ecbe5b3865652932d35cf15176de70e4" + "356ed9"); + v.push_back( + "54955d39b0806ec95897e117064c4b173f20fb248596ac8b00ce57de2d88c01a" + "12f42d3a6f0de1d1af8c41c0b2f8dcd613532314f773bb3ad0f470500466f0d8" + "ae599164"); + v.push_back( + "713b87f7f4f2edb42d279e9ca3bef61e8ceaad72e6ea90cfcae4f9638798e00e" + "8d4b464bf49ff26231dfca5a4dc8b3d47afb36708f494ea2c20cb9bd3d344901" + "a42a95ff82"); + v.push_back( + "c9421a7a80df6882010c6c4aff7ddf920924fc77246d1870da90f183c14dc326" + "4faeace4c76426020d8383efaca2abfbb0957f1cc8249a212699019d36afae81" + "1253e8bb3b26"); + v.push_back( + "2f28d45cdb35951f9e043335c0df22d53e238a7b2df3bfd74d5656bb7e65f24d" + "12c35fe0254669622edb9f76fe2672a7978dff201aecfd2605b2b326a73a43fd" + "470dff9d8d98bb"); + v.push_back( + "93694e4b1a7b15ac94963b9111f86a29266f4beabf1fce740ee44fd264ff44ea" + "dd8d0df5aafba8b8b65f48513a5920bcccd2e4d9c3a90b71fe51e11e2857df2e" + "0379debecb4ade7f"); + v.push_back( + "48887c63b6a5b7351632689a03b53cfba034c653cb65ba6756e0f816eb630663" + "b263ea897025b65703cac600e1a450d71c945f7063d1606f0950da744f47ce00" + "21d7a180e943ee9aef"); + v.push_back( + "44c7a8ed8751f54d0e5428d1ae063f1ec081d93acca64542d28d8c11aa0011ca" + "ec398a2897b3c3a15ed382610b23620e833ab295d9a0eb61afd2948b4093d9e5" + "df08d01d03dd6834742b"); + v.push_back( + "d625de2d8ee2ad2ef5a207af5092eb7965c4df09ac6b55ebe2bdaae799162a32" + "e576925129f32f02c00e42bb2ce5afd73e0c64b9fa8298fe1495f0c8f201ad5c" + "6780b83d58787cb2b4d8f2"); + v.push_back( + "36370178c82981799e8622265c63ecf2329875efec250e995a8de5064aa5f1dd" + "80d2854adb1d806f6bf2360c567c34e802d58fdd0ea15008b20492e09a6e11ff" + "340de57dd8b03aa319d61c41"); + v.push_back( + "554918ff5b98e3df9c43ab9559a75e8eeb8f2bcd5c4bc87b8a9c2329df8fc35c" + "241bbf9ad354fba00c318a2057e9eea6184260fb11072f57c6a587add3043c9f" + "2bbc162abc6e50d06c7c673c9f"); + v.push_back( + "72715c4ad8a633712074b9f133fea34cad5f7c44aa12ae7f1027b03500a9a132" + "a7ecc477c0d18cf4a1a794e064eeca6743534ce07dba5ca211251c903a6d2729" + "d02728161fb8812ff511e7e49c16"); + v.push_back( + "98a842b0fbce066ec6778cc378ca4b90c69ef7570247cf789dd5c9a502f5f7dc" + "b3ab02c32f06375c2e153237babe51d0cf3fee0cd75bf1e34095a98ba712a2e1" + "1c1017500abc9238dc1494c527c4d5"); + v.push_back( + "2e548c4d06fe63198b95a9629e5c68372f8c51b0f2689a2a0b7994a204dee4a5" + "669525786c5709e68ff35faa3d29be6f8902ff1bd742c4f1534fe3d6b5cf0b4f" + "6f1c0f9415bd9801ca0c33c8e11940d2"); + v.push_back( + "9ea0b2e651b1d1755a7f67e78d2333b658bd4dd49157795657a7ad8bbce158dc" + "0d4a002e5e737f52ab1e55abd0cfadac928a977949b264946af48045920b46da" + "19f63649dc116217f6ac67355edb94c46f"); + v.push_back( + "740fe0d1f4db6f6337d300089af7a166ce44da3c6e71e80ad6e92d604f80ac06" + "7eb077f2ac2ad76bc0604a8089ddbd22ed3373438e5ee0f25dc34ed466ff4b42" + "0d77db7e1e9d88a4ac919fc421116b62e673"); + v.push_back( + "c435ab1e8d4697596abec90384c47634689ec73e87fb2d360f30bc5b4b47268b" + "f3a1f31b271b800132f8a45dc85f82ee7620d50db5bdd400d36a36e1c87cb2f6" + "37c30afbd07ae34417e77ef2dd24e017deb7b9"); + v.push_back( + "7f32551c0b81ba6233553c8ce988da296fa2e345262950188cf6372aca5bcb8c" + "edbbec424978310adddec426551681f93a9b4cf6e15a06ac70650dd211386498" + "d45dbd6b70d66b843f73f07fdec611bbc5ee0440"); + v.push_back( + "d8e9839dd767f514a33125051022d7e50c05d6521d852fbaa635fed502e59554" + "bc9b8a1a31753f4fe90d2f270b27e73d65edcdecc18055d53fe1859744ca3d5f" + "39bfa6b23a4cbdb9c326d7b3be831ebf7c0abbe676"); + v.push_back( + "a1d587c939232585840b8f9ff27503efcbc1f59bca47f2dbae3fcf8ce26743de" + "bf6d67936f3d45bf2cf7474eb8f69b0765f362867be29a7ccfe41710e2c3c9fb" + "5ab8a0a860612461e7f4b52ef28c73a087ef0852116e"); + v.push_back( + "63dad275e477674191cf03436e979fbf16b1220e81ea8965fe53828e46f06a1d" + "bd1a6bb03cabfbca70261f63d5cf491e54e31c024e87394a3cbaa9ea1cab3a2f" + "3a6f5a015888c01150286460dabadc8d0af900bced9a64"); + v.push_back( + "bbe011c112d53f842c0cffe98d96855b8d775c8c1572d29ffcf3feb0bae18de3" + "17e1db03f847fcba90ed941095ce0b2b96c8b1c7d9dc2afda7f08d16ca6c0f32" + "d3a5cafca2fac92487c5a177af200c9adc866112629beb26"); + v.push_back( + "ddafad4d5b5fcbc07f2222f9765f750f7a526e5894a165bbc9ddd6de71e23775" + "246601393f488b61ca9f26aba2d3847de759f4082999e472e40c829e6923282b" + "4f6c1d3702071457c7fb2dbd0db54c5c6072159d1fdcdf90b0"); + v.push_back( + "275cd52846616d516fe77a6c7a4069ce46f120b66fe8043be79dd70906abb006" + "ce5ced75dee096277b8c26899323cb8567a8c389578fa0ddb0b0988ded7d96ca" + "69d8b78abd52663fd20e66a0ef4660e1f38460db06304479e421"); + v.push_back( + "39e75539fdefb2bd552b0009e7b2fa583afc8a2b011b505de62aba44c545e13b" + "86590c731fc2fd848a219a510c3f1184ba0149283668ba93dd5a056cf5b75220" + "0659991351a7db19f04fcc7f96b3d25de5b4a726c7dad1b7ec7768"); + v.push_back( + "cdb61379ab20f7975a61b52dfc3f218d5f803f08e8286881aecc94b92f469239" + "73f227b7a1637c1269cc87b9634bce578858f4e9b04fa60ee0516899d71573c8" + "e7560886dfea6d08b744010c0a9c236f3caabf523cf3a3d7a075e23b"); + v.push_back( + "a1b3588cc3742b70cf826f7af7c45ed5b4cba2559541e34cdd3562a216afdaa2" + "a4e39436183ece09c222bc77ed5cf7b806b7f67c703f5c273a7d5879a6300292" + "24140f9b33bf2f4243372e8f781851f7db7d3dd8c795d161605257aaf4"); + v.push_back( + "3443227e56b16df3f7e15deeb5c8c0713300348703503a82474f964612ae13a0" + "47925c3f5b6b364af3f5f89f3b8fbfd1814a42856ed777b90e702256d241938a" + "60b16d00a65143762ca29f577405301979aef51ac5c666247dc2f932ffe0"); + v.push_back( + "e0cde88552c1f5e0f9d3e7a97ccb49996aedc8d38093edd3930094002306f729" + "b8f55d1fdd54db364173ce2abfdb65a35e698f78ffa02686119217597b26216a" + "f81ce7c7701e9ecc74dda65feba3d63e3e7dfc1f0a2c7ca13c552fe16c2830"); + v.push_back( + "610a5ed42281e3b93a210848ebb8203b8eacafc2a19a502d8baaa2e604a573ba" + "c3acd16f265ea4befde07c5de8c0c5cd019877d90de6c3e93df1afb6930ae311" + "bd52b7c6e7c677aac72df9edd2657264d145755dc936193e1ecc44edb1246dfe"); + v.push_back( + "99f96987a955b931534b13d38fb49f383078112195ac492fd6cc44a6efb2e161" + "ab87caa50a594fc7f86328f374d4b21c7802a84f99fa498c22d62c461c294534" + "7e6abf1749afe2e22c0aae2f053b6a6bc7854f56503f3ea6d70193287bf9b82f" + "23"); + v.push_back( + "3ddc943c2a5e5d0786d8671083dd8893906b02a610db62bfe64c7e6ab0866464" + "74483193062f08f903866d6050b50ef55213e3935aaeccbc385f90ebd040f7e7" + "efca8be101824770d1e5e7ef92fd65d148e63fa627469c3f2b5fd5e5e4476159" + "665c"); + v.push_back( + "3df18ae1f63e037219787a95ad960e967671a8389c1ef07c17be3632d5bfb30d" + "dd86ccd7b53f191baa81dba189665407df6d3b1931c7a94c2ad62bb6ed9b7da1" + "dc9b2a5b98cc069abb2c7e58648ed4436d359eb60fe5425c16103d20c793ca66" + "bf847d"); + v.push_back( + "726ead16f67729ba596654a551eb126e99457962286fc54bb6baf50d93c28340" + "9694db0142264b697e6d9be81bd7f63e4965c784ef0af12529294ef7795e9d64" + "c371b15c1a5701c48dae9e2a3d908602c4a82bbeddb9a20eca30b591140f76fb" + "c11a3df2"); + v.push_back( + "bb100a9b0c720ffb4e57422af4017c7eee0396f9c1b1174e0248298d521ad171" + "ffac53c622b68f45c9482b3e520170f44b9ac4855f25874674afec56ba9f608c" + "6c7a6e8bc9b77dc7f5f48a148052e649ff31004a47dc1b3f15bc668b06086268" + "4bae6cc402"); + v.push_back( + "cde6c07be9135468c33b09e648fa0551578d8b7317fffa60add7e430838633f0" + "fc1efe2783468c96c74e2208ac947d726b139b5f5b682bf615910ea9a911195f" + "71ed8ed899f7b8ed126d6452cbcdbf6aee558662de0f689d766e69ab2244f5ca" + "70e6bbe9cc4d"); + v.push_back( + "591fbc984e4b79a372b2dc951afbc269dc51afcca0f131840335fd93275b40b3" + "2be13db09b36a8bbbe7644470093210e04ed4832e6aea478fb5028320ca8ef51" + "3f27d0b3d1c018fd7d2fe1bb1b8da6fab196dbebc33043b13469a114153267a3" + "d4668d062d109f"); + v.push_back( + "b14803314977b0e29e3ac469b17206b527b6e95e3a47f537bd7e5e18ce69e0e7" + "83737f8b1d993c48b0dc2078b0bacf2f752ade8a0a709f8b27bb5efac90177d6" + "bad0d946223992cab2bedd83f8e874f13839578bfbb1e283423616b9cc9c5a77" + "93ae921664338d4e"); + v.push_back( + "5b3f9b47a210b68bb40b96396fdfdc4b130cf0c4f7708277a6bce760837dd850" + "8f4e321e09cd36cde6fb6125ede04599064fd9a7675c0508b240b8352e711686" + "68681eb6a0aac08dd9145439ae2ceb9c7b0e575230c51e1f89a08fdd4590c3c1" + "bc2c397b64d098302c"); + v.push_back( + "dd31b6a184c00932da12262e6030c8045d45433e15b975eaad70144143c8b9ed" + "e3c71f4bc0324c04617ffd377362caed64ee57e40cbd952b559b54b59fc86832" + "b687d08931403f854f26297251e606c75f41717228a3a6eff683fad7528f2529" + "60a286e15edac01a5a59"); + v.push_back( + "872cba270f1ac791d7444b1222ba36706735ee0a2794cbb33eb0e7f1e091f061" + "24f61c1a1332e78f71290c8e9bd3f8f7b73d7619958a2d9a8ec9e7345e43c2b4" + "9868264ce15785577f4b7542b4dacbea045850d38c006e40f61a710b660ffb1b" + "e2a9697d0c50802c19fe2f"); + v.push_back( + "190acd9ec74d1e20da6d30b7c8ba4a8477d87cc700569017e74dbd1fde1e66fb" + "746d43cd115e7d4e4e960cf23a762325c2fe0a36fe5f9b1f5b3d100cd0427c97" + "47b4ed2fb8e4c1f8e86da805884c55333f5d8b29db7317699919f927b235aed2" + "6014b4bb0ccf02b6b3ee4ea6"); + v.push_back( + "d7b9dc5c89dc7a2f6caa59d66faf48c7d3cd85b40241ed5f839f7693a637e2f9" + "95300cdbb942dda736929ec84cbb4113a982666b9f49f7758b1dd8cfe1edf204" + "9f8f822afeb9d7b469839325e1a854a0a48fed747ccdecd01f1dec302899578a" + "27947eac0ecf4f07742b311053"); + v.push_back( + "1c8f939474416a28984e8e5edd261c73757210a84a070b8feff99a3395e5f61f" + "4eb5fc97b4a10015d5adc35fdc79be330112cb1133c9ec8362872029cff48c1a" + "ed3a4734d343208a809ecb1280e442ff80cdd793ffe3a0feb207de7ffdf685f5" + "95633b758f80f0e932464935e79d"); + v.push_back( + "bf442201603db1da2d8e8dbebad06c0856aa36008825fff03295f3e81219f070" + "8983414a8e584c2e40bc897a777a03923b3be75dd66b764863c67b7dcb18fa78" + "1e2543f8c1a901e9e7a50c125f7ed0202f5fe5ffa4e2ebb3242e36e2ffa25ac5" + "fa6d86ee556310a7cecc84a023b16a"); + v.push_back( + "33166d74ead94fd2ea667981edbe87ff5a7418098953bd4a293ceb01954e8399" + "1ac116ad990bd176a885ebec291a3b2385d78e7b2c1034849b413a66bfea9891" + "0e5aaf3c3a83b726ec63c94b8f36832235f5986eefa495e7e9e1320ad00ff57b" + "7898284a0f1550986cfb5ad938bc8e35"); + v.push_back( + "eca675d47a8c160371cfde83919fb31b65653792432281718d113780c0d0c1d8" + "eed4f5e0238606d66fa0a3b515716ba58535c7d36a2a3835f7599ed6601a7e14" + "67adc1720514d78946a1658139482d3ec38cf5d6aeb58f79ec51780b780a58df" + "316a05784764d791e3a8f37368137e8ce3"); + v.push_back( + "923bbbb27c5a11d4d5305a35646543efc0ff2c38b8602f306024b2b5954d9400" + "39720677ec0e873c8e0e83f9581a045867e9b2c02edb359249d9e006dcc6c79f" + "75c9cc5dee9c5f04ae43268d5a4a1da37122904b2750aa8aa43800b7ff90e070" + "41b9752cc7001928d9fca5e73874e4fd78eb"); + v.push_back( + "16353325821cbee3c476b1f872fc6822a902426f812affdffb50b5cb7c8b5c55" + "0133c9135e0ac6068c3f8f0709f1a720717da2833c3a83dd9e6faddc45502950" + "c33ac14d35dd05a96cc7a41158fbfbada5e5775668c6d0724a454446655f25e7" + "a212e6d6b6335df1c86d0db17332fb4d12698f"); + v.push_back( + "04a979bac8684f95894f1c4db8009a33bcc0c054858c7e8ec40f0820d9c98e09" + "758be2b492426333cdbb0606fa7981033aca5afe0d13c89bb51b8c3f5b655931" + "83c91eb23165a141b4ed8b9064469c71301541b8f7d087d5bdbf192b99c8f5cc" + "440f01c3e29631c5d10c88f9f3ef9236fb42a1e5"); + v.push_back( + "19352454e048610cb26842f57414f8f62285eac8944f6c448a09b2706c8b8532" + "67ef45d93a5056a89b2f28dece8475b4232f6206ca0c9090ce731b0dffa51303" + "83eaed7a81f06c457a4684e5ee1783d4792e0d47681b7262757ed3446f037e6a" + "9808972585cb2ef0074c07994d30caceecf9a9d66a"); + v.push_back( + "965ac58510f8f8f446dc09b91d3eda85b3a2de1350a4ede9aa95391bd116898f" + "d4e70c0311df9353e602b1d8f1060d69b8dd8672fcd6d1dd7249c804c5b4031d" + "22896019809434483beb3eceeca78f11415a71e101df3fe5eef09afa97a1d1c6" + "6ee3f3efc08cc35a5268e06521b1f0742a45d0fb4053"); + v.push_back( + "5f2215f843ba3949f5c68cdf5cac13ecc64a589ba0d752ef877a4928dc462d91" + "8395c83bd1c1f5dc3036621e75a51038f9467a8023800d6545b970abda4029ce" + "1fabf0887ae3721f3494dc15a6eaab704969a5b4670c9339f181ea91eb7085be" + "064154f6a359f12b6715e6a1190fe9fa2aac0b1a082f91"); + v.push_back( + "8e6a13e3d41197e3f8b897761594dd9ba97ea1166a281fa01e2804e92597dd59" + "6d25726df67d9d7fcef9607a6fd248ae1502ee743f6d0eb3a1a8efc621f86bca" + "cde2c53e091f6778eed63bcf5092ea732ed2ef7f71090f4c41d0b6567a4d7fa6" + "2c40ce14d7321f5fc18261a7c86fc06c764e94eb6b72f63f"); + v.push_back( + "13fe6282f3c1379432cba49fa10fa325d165fe17e7923620c4172759bc898939" + "5d16715cffd3bc719d72558a19cda78fca79477ae6342da459aec809692976ef" + "227fea180e4df795766883c4260320acfa8e8128c6bd616ded9714e9d5badb3a" + "22e93ee69fdf5496d9ca6c5c3a93dc524bc519861d80dea323"); + v.push_back( + "6812a934dde83d1b997082e980f7c4b01f12e354cd064131de1380d10627549f" + "cbd13db3405bef9fdc9bea482e72a29e727a233b0a5df6bafbf5512e30d58cee" + "5cb21cf351199251f5dd8d45bce9c868d562f6eb6898952a82082eb5d334c69f" + "c85543491d04c5bdfbf8b50337bff27a503563d8d7baedde9207"); + v.push_back( + "f8c6b86a31408a2278b8b6ceb60eab1254987587eadf7655ffe26389e3119319" + "ead76d4c1086ba5ca8c42aad07e607de1205594483184401ebd3fa5ac8bfdc32" + "76c84f78b9a2c3d52580c6e7ee439168c30720fde06738753140c64206902bb5" + "97a794bc3e359053716c7cf1ccfaf3916f79902358501b13f81498"); + v.push_back( + "81672d4663cb2ffb96f8cf0646a522b58dd0ceb087da631e24b57446345f7a2e" + "d9b684e1f22bc6f20a51004b58fc4cd3f575af5ac846aa777f9be473362fccdb" + "8155d24ea889cbfb418f774b96c8ad1c6e5b5da2af8722f74661691b56662ad5" + "fdba5022385717151d33e2d1f4d373c8260778881fcaf9efdf676a12"); + v.push_back( + "f7abea1397adedb382ef9efe62949b6b3a4358470937ec54c5e7df6d30ae6db3" + "8082bb2d56f56eceef44bc13e4372a2d6af84a671fb7fe007513e9d5f1161774" + "ebda4fd832184118cb7ac265c043be9c65f63c418ccd27a4c9da085b12e6c653" + "3db311755bd1e678d3934581af794c0587c8203822dbbe865653b2aef8"); + v.push_back( + "9c9b67c43ccff3b84ee5b83d17c2d8ae44dd079821967b2176336a1667c72490" + "99ece48abc047351fa6bf730c55c10823442350e164116fa4e0b290ea378bcde" + "454ac8ec4d6962462d63917321a5d509fd2bcdccd47ddf5302c5696815fb1cea" + "ca869dfa07285b1b43f19874c53793583f689bc3952f34272bb7da273c24"); + v.push_back( + "95edd838e7fe5a3916372ba59f6b58222f66552b6321066ac66159efa14cd7e0" + "6365c3430d325e9a8bc8945e595a0569de98ed571d340fa63f8ee506d9aa8070" + "f9b70757a8d31fa5d677cedb5909fdaf12cac56b4e138d1e072ffdb126dbc850" + "159bc581c98f3c26e27b8c79ca50d77dd622eeffe10a95882ab2d93d0c9a19"); + v.push_back( + "926f571626650610f95622628f738040814e59315fe7af85a8e346d18c28cfc6" + "f3cab985db9947917d0fc128b138af2ecb02fd840ed91c363f8d52608ea405e3" + "7e2a522d0f1bf185cf2c3199fd9f1957f7216f6f2e6ea661c6a3196e77608402" + "373dc9c36e35b2eff1fe17ae8f269e5241956088130f8e7b94cf042391482329"); + v.push_back( + "afc3dc4a953e845bc367f2930acf37a902e0b2fc61563119f41260c5d50bfed6" + "4951b127611789bab0e9679325a24c4642e0e80ff392c42c340e2bbb6d208c7e" + "28e833a0d8adee30f907afca672835acb7b41063d804cef1e8df7e2688d9803d" + "4d34b31200a4e2ef25280bace4e11266a1250653e89b2e9b350616dcc09bda92" + "41"); + v.push_back( + "0323b5622248d8ef0fc718e54c0296c99176043504f4f8739caf6078d17ddfb8" + "f738e35e8a2469e62c57fde5b3678b66dc3ffa8291251ed099340a6bb07987bb" + "47bb2bca76f58346d3ac254442ff6ed32712a80ad20b622c1e2a7e010b2a3091" + "5fcce91ad88c3eb6137c347cb2943970b2eb72b463209703c034c82bd22a302c" + "5527"); + v.push_back( + "6db9fb1727b40f3736250d908386e19f2329afd69389826073c2ad5eef09eb57" + "f2e3b7bc746b4b7d346dbbadfa4c3e368300f6c21535eb3f3b5cf400fdef2084" + "d38d1a042e3093cac8074a915ab7c8593f171ce6eaab28abb1b83786f0095be1" + "757c7a71a38fac667d16f9f7c4ed2629f1465fafe635f624ee946f8d08e0587b" + "62349b"); + v.push_back( + "0733913340ec863e87e9c0c29882a73aa820fd764130cbbeeca52c70b20b65a4" + "437af34cfcd220b22ffc1d7f7dd6c143653177035cd29dcf5a68834de1a6d1e5" + "17b381ad173a9dd31aa93c7bd57ebc58214c8106910df2b3879377686ca7aaa9" + "e39e8ee7fe65dc1c87749b475a24edb68b423135aa47c7f423034b4be5fa3eb0" + "6b1f67ec"); + v.push_back( + "d9e86e137b90bdfe911a9fe8181f733d6a1a1bf1bef0a6e8e21ecbc2b52cbdb3" + "3b00097d3a2329eea102266fc9a5828f20d8f79b0b38e6e46f832c4dd09f2022" + "eeb4de8a063cee2777b18f57e9184bcea014511c793f6ec65b2cb5b829cf02e3" + "2089663a7807f7f5f292fe2bf07a2a2efddcdbf0998e7511e0fb92ca96d2851d" + "e61ac1d92f"); + v.push_back( + "a802b116e08094afd366f0884b21917f20cffa2bbbc962f0338b75d0374ab095" + "7c42c4abef8fa2a0bd9f208b54ccd39b0dfbadd13a4f9a2e6b699ab8938112e3" + "fdb907de7dd3105388b137f998ceb943132aa97fc5b616d2a2f038e3eb8ca8b8" + "5abf0d74b70a5c64d8d39c5d01d6f653431f73e5ee74dbd12b770f87ede864d6" + "7a30942efbfe"); + v.push_back( + "f69081e0dcace4ce289806fb4fdeb0b48599dffb1ddf7f5f558e101fd1a0528d" + "534a5286db0f1e18cae824849ddd440a735801a24c84fff16ab92c4a09e091c3" + "316d72677c3dcec71a9bb412b8763858dd649f28150642b850e642a17923632b" + "e4bde995d01d43225f72d3ac91d7fb55d8bed4e8deb4a8e88ed71811933e6e4a" + "126e1a1e275633"); + v.push_back( + "eefbad10e1a20fb3a4747860dd0a5d1ae60a5dc9da7919f21b3aff8cf40f4652" + "21f1ded0584e73d02f1f3d598ccc1259b1a39f173ad03c4e3573528bb1e4aa41" + "0e5ac0702f16c53f71b041e06a631195066ddfb5c97ca6c6955ebfd9aa24f5ba" + "650f2a9fbb574e30a0b19ae4bb485b422e3a47fde01fd22fd72633c11e397bff" + "e55af45bef687673"); + v.push_back( + "97e9262370583e5abe378e45febbfb8738691395eab70550021a94c31a82069c" + "22beff6edbb9c65341c6eee246cef57f25ff864ad0eff66cc3b9a41fd3d82287" + "528dfaa12452f9bea39997c00552d45fea39a460ecc2f23d7a58673f93acc4bc" + "48513c0d01298af2195a2d0b692d5ce0b4ccc85c82b45a9a43f70a6e91800dac" + "fc022b27d535cbc147"); + v.push_back( + "ea995f5f197d938e1958a041708710e6632bf48f92a1ef5b1ba1fed9b0566e9c" + "d6b1fefbb77b2009e98fdf14d0f0e6d14dd33fd7ae1bc4d4de7ab1c614ecdbf5" + "651707f1386a6120651cfd2a561a31019f80b50b330d0e5d052a434d053b7659" + "4f93bba3ad7b2e048d2dba4fa7c3498fe8f310c0ceaae5c12e26d74aff0a6717" + "aa16850aa2b07115fc9f"); + v.push_back( + "d1cc5d7ec1035fcc4160d5cd7ff1a3c89194697ccb0a00cc3ed4b3d48ee71eb5" + "fcf228746e20d4b3e93dabc12427c15bbc3147f00b124d812437d19eb6f9ea52" + "36f87052a5fbb379e27091ad829199365115275061c79f20521053a88fca71cd" + "7b0afc377fe4fe34d9d56d21816d88a374f7df5de258123f35ee1ebbf9cb20f1" + "ae94705581f67f24f626f6"); + v.push_back( + "2fe0f366c5051ece560570f2c2783604c1bac4c84c2156c916fa5ef7839ba296" + "343eb2e26c9dc446441897c62a9fa56fcce2fb92af4db0ca6d16999514a1b63b" + "ee0f0b949cba08fa3e5aad137df5cf5656e7fc3b09ed8c698738618719110913" + "03f855d79e678f674fb74830b263e22be7ac7b89434fed87e0df401ade983a67" + "2ad919565cef1ed9403a41b7"); + v.push_back( + "e234ec499d037ff0ad5e3698ecdb7dae1e10dad50e4d5507545395913fb51831" + "e4f767e578a7e17bbbb77f57d1abf76bc1d419e6f38383b26fb639a5ae6e14a9" + "10a2b22ed2a41aa18437862ce6c2fe8d1206f21900d50f26b1f24024c8ff36e9" + "b662b3c4c0687364921d2fa6f6d0cba9e76d4b2b4b2a74f14dd8c2e1a752e99b" + "f1e5154ef64b095197b0e7da71"); + v.push_back( + "c5b8214c2baa3953871edac53f0513cfae89f14c99eb9119075430c8882f71f2" + "efdde2aee59b8395dd84db4dbd0c0dc0d2f248159d9e994799491aa75b02093c" + "ff37fc9a4e06a09ae5b6d2bb80bb46c21eebcc2a03ad0bfb1cdf86197af8b5cb" + "a960ba137fb9c3ff8656c4b38dba954944f05a921f98e19a19d89aad62db2ae7" + "c12804e0947970cdf30fbdc056bc"); + v.push_back( + "7de7a86206aed65ca62e28e8021707156d74cdf87e0de02acfdfb0fdb46de5a7" + "0a06b4907e3d90ce9aa016723adcab4186fba4dd054c10f715ddb95991b10a18" + "afedeb83746d17d3d3287645c00b4b9cdec703fdb4a802bf919514c605957865" + "b27c1b601d2a1a0010f9e5de3839a325b99e6b8bf6691b4c298221297250488f" + "c406878fecd9c6cd7319cc1bc8f869"); + v.push_back( + "6c441cb15bb438db10c972797d08b719aba3987c056800016fb542a3daa944c4" + "226b9b3c41260c8013721158b36f6aa3f3118524bc91b68b35def994a010d05e" + "35cb29a3c784968eb8ce322edd3c3d5f1fbe89970a1817d7d5b7359342c964e2" + "d4adc992cc27ac5322ba43c352ceebd88e08aeadc090a7a62983fdffa66002a8" + "62d24be79f20a408fdf051d302972e81"); + v.push_back( + "5e6e5eb22f30350dd71c5f3253d403d85471d2130967e049ed4294f7e137743b" + "b60c0b11b0f5818c0224bdb4ae1295458a98857b6a32ffbfd1f7d2863acce5c8" + "44e044bb314e34df2222721614d0d51e5bb2c04548228a1693d90783dd985818" + "d25bcc6c61ff875dc4b6fc0eaf6af89e58d981904b522a589ddb0178d6b3a1d1" + "c395922584b62c67e965e840589f658c63"); + v.push_back( + "e0cc757725180643bf8d08d9256aa7acea53a56dad9b49ff86e73792e721c96a" + "a5c496d2922665ce3ab27fcaaf596d2aeac7ccba1e5f56c1bb3aae070dd01a70" + "2dce11ba34fbe71b102c35df3420928e90e84671640279ede57748346a3bb864" + "3a37cffc092490760406146e7922e45680f6520b694f8e599b857074981be25e" + "89bbdf82f9b1af169936a2ac1b2eb1fb7513"); + v.push_back( + "f992da612169ab7b8184e28fc2fbbcb5006b3e92f084052dfbd89a74cc65dd0c" + "361a0e0c764a315f58ee5123ef8d48cd6c5d8421e8bcecf0fa1bc2933671d856" + "fe30dbd9e9492c4c3970804297df06f08336b05e5f5227b568b7d99570d9b7ee" + "54aef3a8bb236a736605403fe0945fd85cccb0ba083f20034d6c625bf5a75e09" + "0f42af954f444aad730ba13489e972bfcf0a15"); + v.push_back( + "762a04d1740f3a31150b0763b5b3b91d3e1203b9939e3d45a6bf21e96ba6c822" + "14f1b7481137084c234445406aebf30d7b2148afedb78c19e308ef49debdef5d" + "ca50926bf123d9be9f0a39d0f59e2de55f512075c2ff4d5b426168f31284e1aa" + "5385127dcd054ab144c26c351f5a70d9ffb7735c43b10a83e790df8da1a8311c" + "7175dc8e2a79f4bc7b47cba13a1d8af0440ef70b"); + v.push_back( + "f5b7e6dfe2febe4e8280667743680cb85ffa1c520ca8651046dadeb10d38e6a0" + "cbdd2abc9dbdf4e5c7f0d81497acdf291fa41848c30a6bee17330ec49bc440ba" + "92b4b5bf3515cb02e5675f7f09856041560fa38e4f26c6309f2c4be814138839" + "ed8ef64be1cc13d322bc9eac111090a24e0a7ac29fb7c9b9bc8f864f2dc96f86" + "2598026352530ab7d3120dffdcaca1560b7b52bbe8"); + v.push_back( + "ac6436d8b5bc121875027945b6ec42ac48bc7d37c81ab624851121e6f8938a67" + "f49efc5223205478e25ce51c6a802773be807b2e61a448a7656b7c9f22622e8e" + "9101486c8c6ea443ad17402f2f373123236137925cfbc5d8a154a55b9e7295f0" + "b0dc3e58c91dfef8eff278e770c9007d5247f481dbce8ec0c129e49a95fe4ae2" + "ebe9ec6a75dbe7c9c44d29218e1a69389da9783933ae"); + v.push_back( + "47a8cca3b77e63f270d2448200d9f3606374f7e708d3e60669896f159e2e8019" + "2c141210cbce44c06369f339d93f97c1107affef1722cd2238546dd69505bf7a" + "2f894bae87f13209d03fcf372413aedef8fef4583270c6bd787a452647e3534c" + "e8cfde89d03e3a4bf8100e4b57c04d6844492af0eaac44e1482814e038039d37" + "d41d7df47d7098254ae1fac3bc3b2af97b46eb2af9b8ca"); + v.push_back( + "a7f309a4215057b16b9084a95cae92e7b91526786b63acd8f8c5c13d7ed0ed69" + "6994f07b96d9cd2c416909529ac914a128634ccd9979edfe256205998569b395" + "a06095de53699bd1e9ffef2638432a4cbd4d02b53b600fd34e04d2032555d7cc" + "ee0a217e6d96c67c76467b62bd4cf4099210b8155f8ec0ebcf4336047c45d925" + "622e328be20b4966aa8706bc36fd222def584579decd3f59"); + v.push_back( + "39e0a9b4109ac94bc86adcc6f3b13e5b6bd12980f6b6203a6de641804791164f" + "ddcfc888db5cd5d26d9e7bdb8e2d1467f5870031a93b55b4b8e872adb1886c98" + "e698dbc19d6eac9c767ab2b562d3e4a726f2c8782db54b27b0ace7836dbf86ea" + "5dddc3ca95447c17b90f97a6d925c913b0df825135b93f32e7c845a0c40ec7ab" + "b07970c928b6e2153de1f5f927a872624a1a6329e3d675cdbc"); + v.push_back( + "34c075d9d6d050229c6f9575cd477ef976a83026b7979776c1a255382de75189" + "4a47e9905c16a596a6fdbb557825cfe194cce09d520009ec70b4d3e591c96130" + "c882a282334b9def2b0ec09714380a3437e8f0f568a00b91e5ec6617eb64db9a" + "0e5a631e089ba4cc3030b918def43d5e2d745362ec7caf4302dea3741686f423" + "df8904a03732968a16528a36b26acd4c6c677a724cc19181f040"); + v.push_back( + "b43e4a514c52415dfaa0e9d69e7a329520093e5760a1d79116d756c177518245" + "757f603d3f859a48ad27f7ef25c210eb6660a37fd27f8dd4dc29f16b9717507f" + "3cef8ee8c49b0cb44ca0cbe2cb2762d91ea3f49db133271212d7dcfdd6afddab" + "fa34c5bd3f6c5f57e12b6d4d13e1eabd96baa27da286b139e2fad4896ffb7701" + "d6bf57df16d2779b6b46aebf4d498d991d6387e5ed9cd23fd1c847"); + v.push_back( + "f132c18f218b14ab6add0c359f2c81638f9df0d11a951236818e81fd7d436b97" + "e18c45abd3307ccbc3bc93e0b17c1c66bd65d089d16e78236f557cefb1e62195" + "86d223c284144199e3fbd715c6d5adb5f5dffed926c8cb9fc825602b3f206b91" + "d4aaab5b868b6610bbabbfcb8b3c96400c4045e47951ccdaacd2d72a3c8f8bc2" + "65db7553eca4f53a7e816628ca70f1ed5943d33fefc7c4462dbe4c5a"); + v.push_back( + "5dff03f0b320ab343c4b63733b193bc2ac369c015ed55ed7217207b0cc865827" + "58cc59620e02abafd241c892f237130178186f97e50a90154a3f020a2bec33e4" + "9d5d06b17e13bc3ddcbbcfb6503c9eb14e64a10a9b1bde1aca7fa6f1af33c182" + "795c00c283d033b5f7420265ac8194e79327aa4817ef04d4e9991035e7fb5efb" + "bfe7426098392c3d5a33908ab6cdf7bca5354880e138d341853e5401ec"); + v.push_back( + "89e2b44833586822f237d1cf49e47479aea3a909bd703f2623faa889544032e5" + "2e7084670456375a58297f04e73cb6bb05a2af8e7d6f295972192f143001caee" + "5dcb15d93bf02133cb5056b94dfe3f64283f2f1c59ef9f8cf7732563d088a674" + "47fb92d13159b0950de9c4efee5cd4da5847830f62144b553803601e695960ad" + "04e3d37232056dd1cb8a90ff304b172dfb035226d29cbd0b59e9d5b21c3e"); + v.push_back( + "7bef5d050056bf05c4c735ca53f59a5db8ba571a09a16012c469552c5a2231a0" + "d8b52452051ccb79b11153f2afd3c9e5f2a491bc8d49a81f320b1dda084b3010" + "f86eaa22dc5cab4b179b2189556f09467013258e0f1afba6264af97bbcbc844d" + "7136103f403f56db79cdb997b7d0a20240852025648f7507101b81a6883ebfa4" + "9255ed6cc0318082fa48a3927a91ee6d73160bc7665faa67a5716005e4931b"); + v.push_back( + "750212ce39be39e573bf4a4787e969103a41dd9e2d0e9025026c5ff30c6a66e3" + "f42378e1ebfbcb193cc8b695ef90d94b1dd6b785fbc3010d95e9f4a91108d3fc" + "f97ab46ed7059839adec545599369703756a3939c23979e254671a1b3840953f" + "7a7b089cc33089e3da314a8bb1899d70efa47e9320b81ffaa3364c7e403351e2" + "6ab49d9a7e6f288cca67ed5c1120fb9c8f1d58557036dbecab75b0f40a9d9647"); + v.push_back( + "7d927ba14c4d09e95ced48ab6aa295b68262ec0ad054025de80da8cd73e4a38c" + "ede35ab2abfbc29bda89dc6e5185b313d9de1f21cd8020c1b45bfefca1372596" + "6603d3b0a19d906e76a1599eb6612edbcd98abec8278d1147f1cff473a626636" + "f75e0c2f691146ace47b4bea98e78b34c3aa0f2ea3df7f57a10d4cae3aba3f23" + "23fc44c0eb8db6c1b3fe0562328461eed1c3da8c2543150e0b535faa87273973" + "95"); + v.push_back( + "bf24edc1bbb0ba5f27a8bcb2c6c10fe342e7e3f05b47990dc118aa4afb459842" + "c91faca491e57c32a73b09ef42fbd00e1cab092a616523ce8392a8d65537c4db" + "ca23928d7c85df694d7cd7353adea0ba1f5b944d5396660003f394f9db0b75e7" + "f4188dfd1e4ed6bc0d6e651d3e0b51a576913c7bcd6b2e585f80f9b2c23f76d3" + "a756f2d905bcbc52290e73d29a1453b7555419cff091679d0accb3a0d687ad11" + "5020"); + v.push_back( + "f633b297ac617d6e4885ece567e1d25979f305be0a2f8d8f35cd48def39b9684" + "8d26419832cd6871126d862c7b00870116e23aac91d3ac7d428b61521f7dfd67" + "6459261e47b47b2e389960cf2925050266bfd09de6df95097c2978334d857790" + "36b82c4a934e29646bb076a9f9762d56fa18cb59f37c026267461e8ebf18bedb" + "565520f7b1f2dda53c026539f31b63e5b09166595cddf7f1a0812f23fdffffc6" + "3c169c"); + v.push_back( + "1abb663429f560454807260b09a5b7291f483127d168259872e964f0de5f885a" + "2280cd3f75ecbb7afe1fa4bf5edf058a3f591a37315fa132d3d18ca52c5ded50" + "48370f9717cd64e42a964a5d708a492f2bf7fed270e570fa493152d3b794ae44" + "0259fa0dfb56dafe068f40785272854b06d4bc022ef1815846f5389ffc3a48b1" + "5e40e69875586824f6efbc44669f0457afd3e69ab8437c0e594206430a8ca8f8" + "1d787ac0"); + v.push_back( + "5f81f7efaf96c3d6f2586b7ea870c287b8b4d9e3f785867ae56a8a93307c1369" + "5dd1300b423b5004f0a03b0ff3a84b012e47086da6a7700b1ace111c753de888" + "44af71217bbe4d0b8d905cca16a163999baa30e514d402e22b265ee33032e6e8" + "e69b7aa871130f779d40bd8d89f47c72623421f54c0de9138817a436ce2b3d86" + "45994427524dd26348b6caba28768e924b3faa468c4abf68b8a39da2b39aa843" + "1af99997d8"); + v.push_back( + "9f7fe6ca671658925daa4ab04f5cd68b9ab5e41b504f4f85a504affd2e3b8caa" + "d9d7a735640b348dd657a30fac592708803e31fc675e0dec7e344f4c55ffd707" + "b67f1c5f80af611ba923b9c2abd71294c2a29f75f3d686948abb2b5aba5c324a" + "f2ca5711342f7eee49be3e19e97fc59cf4a5edf82f7bc01a49ea90c94f3d549a" + "45ce01ab785f2174a0ba35e2bbb3738ed4bf4b8b708d94163e74faa108034fb8" + "defd5c506c62"); + v.push_back( + "e07157420910a5ffe21df9ee78671ce38984c83a89a3219a6e8873c569f378a2" + "afe4132e9b768a6a5391a6733897e642aac6f9b7020b2750ee9abe3d13ffd24b" + "e62b62f943420c503a68ab8cb6830762a59e42039f723b06667b6cc483dda771" + "05b65ee205de8b9452e8fb7c5009be1107d255b79a5bc5f2ed9bf8e6e92aa0f7" + "b5e70d676dd66fd445bd2583f225b5cec24e8c8d725b27b1ec218abb485490a6" + "96318ca6da50f6"); + v.push_back( + "50fbfaef285279077944e04de5c0ceb42d7fa25b9e40f4efb2730c605e9b868e" + "5fa3de3e5dc39a838eecc31bdfccc0e67587fbf9b2bfa8bb96f77a9ec3a0baf8" + "4014dbbedc288c4307c8648a97051b39bf30825766fab4974ebe3396dc4b9209" + "d6de68640cea6548d2e660d5cf375cdc2519ddc396769ee5aadf4cba872610fd" + "1f4322b3adf0b02f9437b28ed007beab1212e15fc5a854f9bb7d8b78d7f760f8" + "9f854675ee0e8b70"); + v.push_back( + "f650845cc5512c33490a9eddf7d940dabd432789a736a105e44737b1ec445970" + "6cedfddb4a1e6774a5c113d4195cc5073bf2b9e4e403bbbe349b687c5d9b9385" + "02568231b294a445c6e0cec07f4010ef5e88d700ad796b5488c9f26735e82fc5" + "56cf196759a6346130b6a103ecd89134c2b9a8763b5afaeac942c69cbd5e0f0b" + "05caf8460ac7adbb0af868e943874320888d2687299b0ece196e93fdad44f6b3" + "55264c6cbe233c4709"); + v.push_back( + "faeb1f08b867088b601a8d773405fba4fa28ada560c2e9e46a34eccde500b708" + "0a35bbbe108bfcdb0f28cfb0a6fa0ac50b80fa0917b65868439129707bb26eb1" + "3290fd2ad8c60061c20b3b75668d0d2ed539f1dd99076e58513b302004977f92" + "069c077c4e6332ee044c14d0cbb71d480a344080cd12f7f31e17245a55165cbc" + "6727053443a1264361f41a7784f6043d93cd8bf0fc0f2141ad1cdbf366f612e1" + "6d07f49ee8398142f1b9"); + v.push_back( + "0b703c4014d626e29fa067f138861ec42f3b71fce5cd19110f0dfcef1f50b3d0" + "7880cd07d2db8f6c2f4975bd674dd6b62c0b98bf97ea54eb541ffc66f73d6b2c" + "16d1fb08e208163289be8a8f05c423e05e68523f75baac9d61fa3b0f6e1bfb4a" + "7faad3007197c37ec3b0e34ceebcec9592501faabf812fff49ca8c2c5373bc7f" + "4d75de7b1a2e5e6bd32cb77c6c2d6fd58cd56326f33cd61ba0940b4c1086e606" + "de79ddb50f7cb182cc5742"); + v.push_back( + "a992d3932a5d000119aeb6933b815ede519b52c7176c6d62ae98a39b70bd52c6" + "02075ed884fd82bd0b2380df2f8f244bf759fbfc5cf99954ac5cb9adb70317b4" + "d52e1f982293e0d5a377753740f8f744ae4aa025fc66ed382002ba7f405a2f0c" + "bce92ae70b20660da3b3ac10abf4f179e02553c2520b8b7c997ed51233fce909" + "18547a6004a4f729711df06c8d2b29f65f24024459ea040a6bad1cc27fb1c0d8" + "ff3d756a6c9bc74dc0a9703f"); + v.push_back( + "152718090ecce8f70756546834cb6591fe853759c6eff8771c36d81e08c4458b" + "080041d2f3d3a2f5fdc5efade8144dca0176d68c61909ef985060b522cec9f8e" + "c6d54ee2453f1d670a75ebe7ab12c7de5a30d65c28fdf561599dc19c72c8f75e" + "54eaa2de391909a948aa47c9a76358ef46554791bc18c289f8535bb9d30101bb" + "c6d840347903c2b4f61cad5c2f6f04227df38108236a7a2f2bfef15ddcbc5972" + "57b48e8a5718c668d61872641f"); + v.push_back( + "63d02fc7a14e2881eb47db6c79104f866a15d26f9f84d2c55adaf26b3c010a69" + "ba973d586de5b12ad51e89a899c9b4743a60017dbf356b7c5a485da33047c028" + "d580bcfe8d1408a1dbce0194af2a84011a6ea16dd5efddd7073e8a0c024c5f5d" + "c4e71f36dea8229976962de385162896d0eedebb22ba35d7275b9ba8a5aede12" + "c78843ad540a28838728bc1d4ad24e53c91f9d025371cbc230032a836212ca45" + "aec4b611ebc14b5d353c54e06e6c"); + v.push_back( + "cad989474092fba2670873c9cebc67dec86eb823dde7b0b99f1178d298be0582" + "8e4aa3eb1dc369fe7c6058b8372184156600adb5624da2ad769c689a7cbcb5e5" + "c38e259a45d4b83ff0e93011e3fff285601fd209db19134883c1fd97e5979f97" + "f7da4df2f3ac489290494dfc6748008f96b98e92637d4eaca6953c2cae677dd6" + "395d2884ad59e632592c15df904cd7c9c8e481228e23667860dc3f5d2e6c4ea1" + "ce0c0a73076e6a747aee3cf3c3647e"); + v.push_back( + "36b5ba6d0fcb50ee37aaf66544d34b4106ff8f865c24b9c8ea769d6b16894ca0" + "592dd3d709124f18997a98aa2c88e0a45af0a5fcbc4cbfa7baf15b246c74a26e" + "c0e672bef688a9b619b081b63e7a30e09c0c8442de9fb071e73909f5d50b6c1d" + "0692004242d3750d793f8a767d28fbb8b4bd40b6fe7fefdace8ee530aca73f75" + "f5b0e000e242e1c6d31e3a3adde861668721439bf952edcdaab40560e30795c9" + "578436f0373f6316a66dee75f2a13fd7"); + v.push_back( + "b9d5f19e82dcd525aeca9808a2d76174a04574e48265396a5aef082e66c867a0" + "551bc30f9d1f044009db3c2d0d698678a9734ddeaed08d96df5e6efffb40c758" + "c81ea7e5924f5530d60efe3a983351f54388683b21fb08cbbcd95aab9306454d" + "d9104cd7d0b6b1cea85d7630d38b818082badf854af8104fdaa76e4c186b77fe" + "0047f3e3566cea7db732d893b3453ff52cef9d0e7cbc58a5417c547454a353cd" + "90dbcef06dda6a2643ee50f00dcdb9019a"); + v.push_back( + "742d6d0d638dada3fa15074f7e6ca29f861131e7784a46720687b3d4534db709" + "0d1312d1a215cbc5adad439e741f938e3cc31d2b92561e9302bc54ab4588ba4e" + "89d0d538437e11960a83a11a1e52a30dce185cf3bc3ea671b0e24d54f6561e50" + "2f6d987b6de7a49e057b38123acc7125fd68ebf3e8fda86b64baf026fa8ad53c" + "2ec32f0af41cd37c56d624f83611e0f10861b11f78b9999680f4aab8ec352988" + "97c206522e554cc032c8a1847d4112b40dee"); + v.push_back( + "aea024c695d12c1e8e5b8d181ea49771fa6941188fbe128216b65f20849e11d6" + "1855ed132c2458524ff7f7bf4bfbdb31a09a5e3f1257243553e35c8f78b64803" + "ac2c10db6dba6662caac0049aafdc627d65d040bdef334fcf5bdcb4e4aa25629" + "cfe86faec497d1bb7bb9c9c581fb89fb91f7898ff9f2f3ac3db4c8b58fcfc1fe" + "741a5ac6fd34c49cd058b48f39432345da0699bec367b04f4b5591a30097a451" + "a593d0df658e9a9e15e1f5481e23d137104f1d"); + v.push_back( + "d28db81040d09a6b5303b588f280411511fc5f8c32a8fd6739f38f5b633d0175" + "391c12e7f429cd387eec7b2bda428e56b93877da802354f5622a67ae458c37ac" + "9676d7ad065e2764adfcdf8082001f9a2b86f0f46162f4a5cb8007122fec5d38" + "38806a9758a6440433e808c8392f55e27c295f517ede674126739f7d32d923c6" + "c09003cdb701ddb53e2cb48545cf184a142f6916694c9d823366ea900b49bb20" + "fde261d55790160a41ff42f2b0a199c6272a6bd9"); + v.push_back( + "ace60c7ac074b101965aee3c36049cdf4b8a409f81e713771d519294819bcdd1" + "b36e2bc76c4b7559830ebd7dc838696def0e1aae649e0ceb583eeddd0b94a239" + "dfbf18b5dba8800898187b1c4c7eac811f43b8d8e6d9d250a35810c7171ecc79" + "b4967bcf73c016cbfe8aa7cbebbaff236abfde7135bc6e29fa9e2af007eb5e52" + "4c15a1008d5535309ef3209276f14f27d05955d92e0d7d3eb05e4bbd43016259" + "64129893abadb60f6f8b7d6c3c015b8570cbb4522c"); + v.push_back( + "5b5c19a7fb153284634acd3a98da6a66e31f66ffb581d71befe94f958105d814" + "aa2b370c245134b25f3547abd0101abde238110b7d7f25206cb8aa57a4e1415b" + "205c6cf3b46af23981d1cf48b6d6159040b279ba60ab78a14d08f6a3377b2889" + "2b5bb3d0e44f980290cceec226f90d5f4457a5bebe8d1a39e2e98c3b4e2010ef" + "9eb24438a23ae73d0386bc5c9f56b581ca358b164ac7c051933e2ca54648456a" + "f3bfde933fd090aa0a3d57c5cbc3b3df57ea4a31b5a8"); + v.push_back( + "dd7b1c4ad1d97d73a7f5b00c0f45c1cd33be706a31aa44b36ddf6704796da1eb" + "23d2195dd92740221b97bfc11a11fd0c5a1f8717ffd84bb5401e965a3e987e4a" + "6c91a5163a0d2860e3c96f0acdc30ae389048f5eead04606f8f2d313b7862396" + "2d55f5c81aafa9f4e6c754f9525b1fef34403ca08d2c0e20d0cd61f6957b2b09" + "6471130e4d1d714e4e270e4fc29d45c536c035642afad9bf17e893c4e37c1393" + "5d9055a926a9ff0d5460eb3f809646e18222fef84d28ae"); + v.push_back( + "195e390bc6f727cbc247c31f58dba36117921596afae5be4fa0f33d1a8d454ff" + "417bc95f03fdae775325ff64dc6918354adbb586844d66490814ee513700fe89" + "3d7640e81e24ab461ee79221308b245d5e54d99d1f7472a4262ee2ba759963a5" + "970c46153add4bc04328fda5983ebfe903e2b47e076b48d517f7f0a6cff9ada7" + "d9bb07d787c0acc11a2ebe22fc352f3517640e9dc5395b92ae769d00251dbae8" + "8a809d0673f08525494ee3ff7fb9956a23a6ab37dfe2b13b"); + v.push_back( + "2b18ec0134bd03907c3a81f39186adc4b025043d58deb0c327673d73a4d79b17" + "20d843fb4f7bab22fb4126f4378a801b9fdde70051a48c59a4dbfd094cfc8bd6" + "58855ce16af0e563750c5f7909c273a78815a55b30e019a5ee26752a0a25db50" + "32d1735f0df1c03c078a43ad190944cf2b6c89933466b49abc32f2e5242077e1" + "3b48c92d22e232e53a52c4bacee3b1e03d61c7fb8578cb8c58605fab06f86c01" + "0f5722f7dec13cba3931143f979269a4d7031068771cd7131a"); + v.push_back( + "2f9268871cd9a46480466df658d8ab1513de8ae18aea3175b00ebc92af48e363" + "384b24723c780371e1c6a45444dcf17182c0a66c315c73de24f430a49aa8f2f8" + "947ebc7bb8cc10fbf85fc8fbe134d2f6e9e11eea9dc79d0de6bc227ed831567d" + "55a939f388cd4b2ec2c057e5ed8eb583b4addc14f0f2a5842e974556426e6d45" + "10b56fb2bb0cb8518ce3a4e14dcdd3377329280364b0b1f602e72ba15e27e099" + "1255801983211917f26c196bef06c3cdd90291def8c677a257e5"); + v.push_back( + "d567627598873f4dec3f6236240abe5b6943ac8dfbf2774dd7f40efacebc50b0" + "7fe52e6b89595553ecd93bf9065db058163ae63552d2fe4f3d19a614715981b7" + "a503c6052c3e9e2747f6018b5275ffe078216c46f3820d964a2d11e85eb031f6" + "f314602f462dd3c3aecc8a4f77d4e73bc44505201fd3e8e580d2b04c3f4c885f" + "0d13e52b505067f3f605e9b637b5ad81d3d2cffb07f88f12ace18da209c10d0f" + "4d9aa38c5a17995c92c8fa28d55fc731ecdcafa65d956dd65ada03"); + v.push_back( + "e6f01a669f9e61cec57e3256c7a7c23b840b749fcc849b9e46d66f5903f770c7" + "bddde56e969a46228dd2d69a8e5bceb5bb06a0555375178e15cb9c5957b2f525" + "68a41778659a0841fa62cce468ba409bbc30e1a70facb45e0c748f08ad36ce11" + "3612f1217281f822546e29ac37466e32fbbf9fc878a12a75c5849c7efb6ccd2c" + "3163bd2fd9ca8349dfbbd234c15da524256ce20d150e54086cdb6a83d3ae83a0" + "b9c4a49cb5cd67ad91719dabc6179df90012b5193c120179c0b69987"); + v.push_back( + "f3d1c40217c3ed135e5e6afb91770819b1596034a0a183ceb9ba5a1050f4cdac" + "ea0c8ce35111abecda4a09615fcc0ca476531b24d67e94f11b30b15fdae2c31d" + "09995a2ee9f8db40667656dc197dc35dff1416d968a572424c7fea2de1f4c23b" + "f6ead4345c881cbcf22c4a98ba1d3d3c6100e4e4a21e9197d3d54634a5d3c18d" + "afcb9a8270f4550cfdd17cf77e06e1e72a6181d9342dbdbd1b656eaf735a07af" + "c9ca4e883ca545e041f6aabadff6b1ece06870a534aebd638db701ceb9"); + v.push_back( + "4e80407aad5316ba80492fde6cd6caa97b1eb853111cdf4909bb0ef9ca3828cf" + "94d059349f363e1c5afa16aa1f18c95e9b0b44b2ff348bcca79877e294beb740" + "5c88b05dec34b775947d0fae8ec1da26c02bd5035788d27305707181fa60327c" + "5825e2fc50e175e2922753307b994d27f902f0cc72b5f2e3b78ac3ea66973400" + "b8faff4e346e48405eb2bedf96f70fbbda6ab905dad86e766dc3db774a358f16" + "a1d416cdc0bc8a0d99a90fe23780c2da3ea7774aa976025cf784e46eda77"); + v.push_back( + "a01a2a35365e7f0b3349529bdafc41cf031feac97e6254182bbc6f78ccc97b91" + "8dd51ba1279c24f0ee5a257b8dfb3e838567da4fde3fa4b2b49d108b5e843f8e" + "a2453e2a5ba4cee6bfcb9e224d172369d7d8fa3e8fdac85aa257498b28b0af88" + "559213cb147b6116ec0f7fc872dd6a84f246ca1f41b10ca43fc19c8f20ea5d63" + "c4c39bc2c257ca5aaf7a89f2e50aba5eb6b069c200f733d7f68f2f11f4c430b9" + "32d40e7e62e84c22b75952cfd941dc505085f12869bc520dc645b00d0cdaa0"); + v.push_back( + "03c4b34be5d2a1891b10a0a74e4cccd5a0be17ae1f2388a972ad699db8c247c4" + "ec013ac22fe6c6c1a75751834101a17c930c90dd3805963235aa8909edd60211" + "cd97f2896332f606164a3ddb1aa9465fa8c994aab818768166828e3d7a81b9ae" + "b5dedf93555fc351782663167e2e36b618fb16abcb6d64de99971082ca76ed6e" + "c17d5d0cd8b45e0336ff3061a5e06c54793b8eb10a1b772c8cfe390e5d32ccf6" + "1c05a618f5130af24b33068ce35dde6e3a9acf7550797078294e69a9b6c10be1"); + v.push_back( + "52f245b0d61ee4f1b173511bd008d3970a25b5022250ec2b9f9a28b68b3b0c8d" + "274ead30fb9fc1f9b3b5f2c3e7125c4fad241dd3f5f4d0c186f64ebe09d87992" + "2a682f638c73c0419e7a729329809a7325a76851b1df2eb4cdb4eca2204779b8" + "acc052c62551e274b9137b1c50d822cca8d4cd0b8eb7554ba448b7ac6409eaa3" + "8093281c5017260ce2bba9bce09b3467178cba5bfa899101ea3d073cf778944a" + "fe12651ab713743218c28092e6d37b41721f191e006f29b5ac33f973d671e943" + "d9"); + v.push_back( + "24b7bb806303fb0581f5baaa960cbea9b2eaf6ad927d073237e4d77cc52c306a" + "407a4b1094c668061ecb445eb3de6f1880bd72db303bc05af8a5b72ab54014a0" + "32c28af1d71a62fed15f95b468557a28fbf06eb22caad469b20702b3e067e96e" + "be06ec31a61ffc2cd4edcb19c11abaeb5e303860869ec7ce19061bef3522a6c3" + "b0c64e11c7226bab5547ccf4042bf59b1bc0c2c41dd1a7db42418e835e7871bf" + "121bc9b1aa037c3796214e31b682f8393a1531d1734e2bf0237be24002f8c2a8" + "a7ca"); + v.push_back( + "21535b47e5d30e131aaa9572e94390d6466ea90f4daaa27b2211a9725ef1715b" + "e8805ca5dd95e01a649d23984d5e1dbd461ca6c6d9c9c4d62779bcd3c286103e" + "6d3a86d289a86c58cf84941e74d022cc75942d41af9da94602361e1839a4d823" + "2c3d0ad09f8db42d13e66f79bc22bf52950abad83a84fe6c071aabd718c243ce" + "9f11d84a266b172c08f0b17bb07d0032cc27d60fe21f29479474f52563b9eb42" + "e40a7c2188404019e02ecda1c588a3b9684191b19dd33bbde2fb3e9d5ecd1317" + "594127"); + v.push_back( + "f64e8a480d548be1e8dfbfc1a6c494b81e9c630d05c9e1c843d35c62109496e0" + "3c954da403b57249e6c3863f3f7289c47bd97bbfc927de8edd896c2dc4dd0297" + "1bec98624cfaa7244543c4bdc02c0ba6edcbe543cfe80a34245d5fc4abbb5a60" + "588df8a1783d655c65606d4fb3a3568b1b44c1ab7397ad8117c5d6d9033890e2" + "558ac2e2b9c8e262191cb35b2c7f77d4ab0c459473beea90eb8129a4cb4008fe" + "bac2bf51997ec1074acdb75b8c446803b8f0d4cdd24d411c7cdd58f21e587a98" + "a79a8562"); + v.push_back( + "ea65942ff43fa6092e4056100586228f2d44cd8f7020d7c9a0927af28fc4cfda" + "7d7f8202b1dec3ac153d186b97729508f8875bc46c5213bb3254717facf81fb1" + "b750f56b0e25923d428aee8f06ffa9f55bb9d06b7144c98926f9dc82cb7de678" + "d0d217816d73821b34e60ec41a64e4b9cbabfa8a88ba9559ded2ad1c2e5c3b54" + "654af840715d7de483c1844ed17e8d515d13016ad5dbb83e09d1eab459b68720" + "672ffe1d8ac982fb5ffebaf08b7b94fcdd9481ce3bc07df4d4aacdf06b4f1458" + "71133b8296"); + v.push_back( + "2cd24ad3e4a9f3b145eb0c899f4e9622724c3ee8afe865f8f1aa10003c584cc6" + "eaf3639154ba7ae2ceb4c4daad3b2e9712bdd50fcb8bb844a080ae9ae2565a56" + "2333b098ae9f56fcad5219cf37bd7a093191eee913cd46231ca9290ca858e8c0" + "57a4862700c701178a908795932a16d95d17e4000d71911ac1048d82cfaf6c80" + "07f3c50ba8b1eb87d07d66d62a19ed638079d4a5e813de2863362b2237b9c694" + "0708373ebf162fe5365cae6f43a535a73e6f49d6ca51e8ef3811bd395cb84fcb" + "7387db81d7fd"); + v.push_back( + "35d3281fcd49033ff7255c49ee4b084e90a34cabbba2984fb4ce4f66a62b5149" + "77b328050f0af3b9ec9b2907abca5413de2ca1aa05edeadd440d5a261c861cb3" + "e726488913917cc07e2c4763024aaad13d37158f1606bcda253d1332811f0fde" + "69d411bf8296d00b45830d300567dbaefa79ae5f152a7a6212f0c481838a9319" + "d042404dd3e64892b592fefd3b1127c300cb541388867dae011b749672008958" + "764dad93c13898a4b612e6a137bdfa4ccf0da58aa0c25c096ba79cfa49ec9af6" + "89e761855fd712"); + v.push_back( + "d055196d7bf4fbe53b8fac09d12e55f2401fe2dfdb423fc25c6e787a10ba2c19" + "2885c2ee5fedaa4d2cd1c880833bc32e2095246311d47f464629ad53c82cd0ec" + "a24de0801cc5d5f72c5f0d37733ca62b9dd47dfbbfb1f66ecbb1b710e342afbe" + "e3ba971c1fc735c9441e910ea7fd9669dd78d1fd4053dd06856744a122be93e5" + "f73ecf04606af47d49403e3e658849c3a76d38833d96271ed76b0ad924b5aea8" + "ee680b1da889991d52da6a4b7ea12c848e134fdbb1305e27c2fbce7233280c3b" + "3bea6a1219fcc3bc"); + v.push_back( + "5d3e88955c388dcf6177185f894fa7901bc5874a9e73d9596da159dd88b77fcc" + "cb3ad5fed768ee6d69c05d6e38df5a679eb433e0161b3464b4b8157cffec2c45" + "0a28eab12c11b18ccbb68f3ae14c71a233e114c4868ccbd1e9eca1a2b6ca4a63" + "779508099080d3de3396649344423a8b445d34e5902725627608e9b5ec920a82" + "02d82a5eefbb3b3360d5eacbec5d9817a64d111052e5f030622ffca610e1af69" + "beb2296825f2409a1042e4012daab54d649f5ae284ccfa665e6fe80fd910f39c" + "fe3860f3adee2912c6"); + v.push_back( + "392bfcad38f2938b8d7880f70e33c14070fe54843ce8622ebbd9c5fd9d7cca22" + "156dc73c1dd616449826ce33e4bfeb4d536c8b3a72aa23cdd512bd16a7c7ed5f" + "ebe356c8869c5db31d67b4fa34ceec7015a19391c4b5d8ff95dcf414aeac3e80" + "d261689275be9b70e336cb13d9255d05084c367f42d92c18939e89018e0b5e3a" + "b9a51bd9eaef72829e964e65c8d70e47ee0668af16d27a0307da66a9c4297da6" + "7963ac1bff76083e3a87ff12aa25aa5d042a744bc01157102cebe6521d7b2e59" + "32e81fe2a23341534823"); + v.push_back( + "97d63a07164ce86d6b413acfe23156e919e1c40e320ee6419b9aea50271506d7" + "39aafaa4829862b611786a772e7aeced9007e09bd7524309095f1643ac8d18af" + "3d3a95f9864b18d2e89df43a3a4597b0801f2ce05811ccfaa88c8e94373378bf" + "325fa7fb6f05cdd0c8ec6cbe8db438ae131f5097353eba012e18f5d1499e735f" + "f4bc951986390530998726e7a90b0ed71d16e8986074dde9d3770005a748fdcf" + "411ddf0b03615896d2e0cabeddb07c57d74ef262e1778016c8246625c237be90" + "1bb8a6c05cdb1ec2f3f4b7"); + v.push_back( + "5d14d28542ed0c9c21aa82de98c45157b83675341370700d01a9cdf62c3254ec" + "8e44bb1346f503b561ddcda6f1176816449993f99f870d774bf6610af93cf00c" + "5d36e08a6e006c4dc78c6605345c8abad4a8405f575cf40744b1c789f987cba4" + "4c31a022d98d20e79d214659653dc1d9812c7b7f82ed38b469e8c718a8f4a281" + "f71911929ed1b5d4e618c4250dcd6980bdc64cb34f57d0d4778511c38456c403" + "00ee6b0b2f50f64542a44a8c9b3b41d4c14bc06b4e166200c1a22bf0f11d51f0" + "7dd130ed482f6a5804c6ea11"); + v.push_back( + "b606c4c803672e40423f7b2017825cc6d87f7db31cb155458427d40824f4d8ef" + "0e77b8f2aa152a3938e1acdc8db298728ded23dd2eab091f91273c284b8f6443" + "28d16d7568c112f4f0d1209a857a6fcd9ed00fda2d8bf2409a01fe2cb771006f" + "ae826ae58d7f5d4af94415569395bddf575a116d6daebbca841469f06ca234ed" + "d6348e078506d5f3699e8fa74fbeb65e6e182e40af3b129bbfab140a287d95bc" + "ed6a4ddf4bc942eeccbb875c60aff88987642b499d6d50f2d37beb1b54d9a27d" + "c25350b324e13b4dbad157d18d"); + v.push_back( + "29606ee5ab59bb463bdb766a319af2085a36d5d5d92b83e60092c0f568ebc8bd" + "2c7139cc0042f7e248c0c8a89936a39f4655a78b66e668451562fc7c7f9127a7" + "254f4fa39fdb21528f21aacc04d86ca7d985056db91d70cf46ddd89a54a78cb2" + "f133ae1310ab830813637fddaad4d70118b68f50919476e81bae14010d8b5dbf" + "c88b2f92b048476139e7d47f6501ef8b0e8b52e1626924d6f90fa32a7ca62e1f" + "ceebd50dab14baae9e21a68a64af88962b6d8c55e0e7e6cc22233405e7a1d293" + "60058bfff24051db40ebed223271"); + v.push_back( + "63b44af02cda636a386591f0f2698336e2110a2552e43783ad6da06ded94e314" + "fd50adf6661f1caef42c42f60d9c4e50261a5eb45267fbb457deb03ad0317c4c" + "9ece21c6595d17c7c854a3589aa6e75e04a9865f821d3b8552acba9bac49c959" + "188de5fdf7f81a26e4f634ecfcf46ab5acac7233b697ef91b79a04dca30fc295" + "9bae72c0a9806c74a59c53f6eb322e00301b8c4858f6d554a43a4e2f24863067" + "04ae96b0b815802caaa96f4078b27e5bb7968da16b5a6c5b0168be405c95647b" + "d21b3055e6c849d65f0510d458ee25"); + v.push_back( + "d33b0bb08e56a3ba99a7bc02d236c884110bd637c293804ba2dc254473461ebc" + "307a311658232ebdd608177e1be7f9fb66d912a433ae6bd1500822d7becbe453" + "f01e1df8b7b3903d2b8dcffe1ac23e42b33d8d5b446e22f5dd220ab3f22217d1" + "c992b30d656cb56bd3c1199e8672328a8598599c6099bfe3d452c942a285f35f" + "96a85584e11e9e4586f726b721098294fd27e3b4ca3ecd127989e1202eeb218f" + "a5d74aa66fd5533a22b25b213eafc8dffbabef6e17362b9c1888e82b00108cbf" + "8ce096348bab79d7d53ce997a1b182e1"); + v.push_back( + "623b1417140ab573ca90ded7bd86f7fe01292df33d285d2a2ded9fc6ad130607" + "69d18cf5aff2e276231a172a9ff46800434ef60f8feed67e10058a6d32dbb111" + "aa286db0a8f0980a5e55c6498f4e380bf31b1a4af1332dbea6cc0add86f563f1" + "ba70df596b29eb9fc694201590a63e817cf455bdaf49ca1e5a4ee4250643e8f3" + "0389eca76e03251b41ef211ff1d17250ff7bf7a72993687f6cbd1e73015d4248" + "5ca36c995352e77b966c2f77a201ef57d5d3d8272bb87931077df73ea3937195" + "b4bc6c95cc7d975053c150c6f354a5cb6c"); + v.push_back( + "debdf7e34d1927d34002aeda057f7c56a5d2fc56ee130c91007432860e1da1a9" + "40a71293f371b2da670ecc5a7e3fbfe8779c1546cf4939a6f36dca6aec540187" + "70ec3c9945cba91a83edb3fd32ca6182c01d0e1b74c1d80a4e5f5537a17c2200" + "fbe0659dedbd4b3200ead90ed34a8549759eb3a21eaf6f8f9bb1b9525f11bb4e" + "10ea55b04174dec2a7fb6b5ba2dc212d4f4e45e6b948ab3d6600f51767ade133" + "9c26277cdf0b3627df43e227aff9a38800fc496f6c4b3cda3dcb5bb1c3dd03ff" + "916266d5f6f4bf1df0ed4024afe84ad1edc5"); + v.push_back( + "dcdc862adacdbdbb9b1d43ba399136029cd9901fd16f443311ce1009a17b2bbd" + "118a92db41f60bd9640be21488c671c8267b7ef10d94f001d94bc43cc783351e" + "b05a419c183a6abec9af39d91edfca281f0c53db8bba509140924327739f394a" + "f61b77352543530b1364fee4dec9a04bfcc3aa51373692087b4d3115a7295e54" + "9736abebaeb87c64066d3e1d5752988395bfe67c9b5fe9598e313a39766486fc" + "a2bc053c4ed09b5dee30b182cabda9395ab140809fae76ccd5851ca625c8ef0d" + "c8eed9308248aba77a06fe6d581aa103b43e00"); + v.push_back( + "2a94dc0ec9592004cb301aa3adc90da3d326794934c8c05e17915d31d3912b13" + "3b0508d16d47c77c03cc7097d68f1879a39139260d39a10ec407db9680048e8e" + "d06f3cb4ee9e53afd01ae78da4f18d0e7e298bdc882239b22c4e0790863cd88f" + "d3485b633adf21d279811c4eaee6f61a3b0b6146be2075c08a9c97883062b4ca" + "2a16c4f309406a3782fdb3646e51b8d52d25a15c7af7edbba28693906dc8497b" + "9240518b2457003a9c55c7a41601329ba7eb46e82af1db23d1ddbe1a67dd358a" + "9cfddd3497bd91cf0d4e41edaae8d2232e3acbf5"); + v.push_back( + "b390805562b9503677a1a5adefa7eb55ebb22acc2c7fd7032326c3f87900a6d4" + "29eda3663b77eed2a5df929e1b43763325bde8ed901092e4099aa96f89b42c26" + "20a1d8a2f20f772187c4b30d62dc49f12fa929396249c41936e2bc379474c8d8" + "ae0d71fef5307644893eaa14b46ebeb581bb139956e1ff4064301d03862cd358" + "eb156c9dfce5712b35b149e42b53be3097e813305b8a689d4a211259d73ed116" + "fed8fd6ed457f58665289c73799137aa57925037d2a4898e19609a576609a539" + "d640a6a0898d76e7d1170de06e98a87c0aecce67e7"); + v.push_back( + "d527df309ff5112d906def8c461e300942e10324f9ea54fc47ac4e81bc7f0e83" + "f9eb3c7f347ec1877340c78346f4618f52681eec7828b38583719def723ef4b4" + "05553373e280668c33d846ad90ce5e074f31cf6ea49b08e86cfe2ba6039a7259" + "ef9d73297d310c6db2e17491f524811edeff14383280dcd3a6ac23cf170bcae8" + "54b7bfd476195b3ff0762f1ef4bd0d5c1727968fb79c3dd15b256d6cd53ddd0d" + "df4e29eadf3f75013d9099a351c53e9c4e604516f050dc6b2883d07a28e69179" + "8aab696cabf607bdcb6f59fc32e1079d20424995d13c"); + v.push_back( + "58ced7f7d6ecfaeddf35b67823815da814b008028d25af3b79364d93ac4aa8c1" + "20a27745598f742a52a4dadc2298df0d7d0fbc23c250cd097a0076b89017c870" + "7963e0b90f06161dbb4df4822bfcd2656870aceb9a5adae5cae7de37c3df6aba" + "f2ec751cd163f03613e60409ddf579dd9b732ba3c429271f3200251c560b4010" + "e9310233426904f8e2418798373ece661646e8e511a75b0df17eadaedcc64259" + "cf8c4fea77d754eb09f378edc79259325ba9414865385e6347efd0f41de3c52c" + "6f27d6c8b92d97a29c1e06d37874e0c58c3d940f0f996c"); + v.push_back( + "6a1d428d191bb36d060f1263573118da568af27ed52b96c71dcfd8e4a61274c6" + "4bd3627ccc59825ac8f2325b2a7cd46be2fcd5c22f3ea1b7a8920ee8d150542f" + "08e3595b225404a125a96ba66f9ce1fd36d57f12bef1c66fbea22144d1353d65" + "a072d506d0187e2e8aaaa25d1c7c8695e3293f01fbddfd44307f687f6389c34a" + "2969ccdbdfc6237b382063f6f6a9aaca24e370e88ccec8e74972fcb6934c08dc" + "ded213830f6430b37a82b05f408c8209f95ea2bce17b712e73ec83acbf3bc51a" + "2b6881e3f3bdf02684b6b752e7abe723679191e26abe2cc7"); + v.push_back( + "acd7222469ae8767f7c949610852bb7f120a51bc6561fbf66cc7396b38dfbdf3" + "3049302b4f26caa93b2844c6c4d46b6ec0f5384c9767358751b7c148830d957e" + "68c08e11ef9a0fd7f381aaca2238c773f4d2f885fafa151d17a12746c7c28a57" + "b2ec7c575d88b9d98652ff9140c1a4c50f31ee4491e53572bf16a10b29efa94a" + "2c079046604c0715ff4fa1c4ea8fda3cf30fa8ce37e53740274e83f6dcc4a63d" + "24d34b3ed9393b671d3b9915dde6fdeda18ca5d670277c434d793090bed30966" + "dbaab252966afba1d426ae2d19b5c74b16d3bd36528cb42b4d"); + v.push_back( + "97ef05ca9a81c3ccb8e993d10943be011b8ca3e8307ff65b1ca259d70718f22b" + "ed4fe50de5e46d6abdfb3da2bf9669c6ade7746d44a40ae0655e9e8b4dec1f21" + "c41a9e907fb0b4beafe49ede427c7da456d9c9139530875ddcd9e6e1602480e6" + "3ab8426fcafa6eaa3f4a68e3e04d53b64312e25e3339d0084a987b53c11dae4c" + "ab7091141018f9f1780753e87aee6317b9e249135ca32d26289783ca2af99a2d" + "29ef35b92d4f6541e5e337b85716266441867d89f0af4b855ce0db3fcd0b7b71" + "d8491d43023ef06586070e167d2dcd90ce9aee71f9043913f459"); + v.push_back( + "faab0206e2bd10ec36f111531e9114f4ee7fa43deb60b268254c0e3d29a4cdf9" + "28497a097791816a8ee8220d8bcd6e5ae6d403ce64c7bac17104dfed8f56870f" + "067bbb210aad4b042654fdc7d5e7c1598eef1f307fe871d00e6d69d68067dd98" + "a7d91abb8040393455f606da8349beb2faa52bccec14c4f1f4d9609b3b23dc24" + "b031c65e7eb67ed4faf8e096511403c871a9f64e4b8dc3e557e9bb5d6716d158" + "924bc4e5b53d81138b2643c253fe9276110956e553790e0ea89a79366934198c" + "21f9532b43e3675552dad56b447f4bab67ce04d53101b7734a50b7"); + v.push_back( + "48d6d638ea797322909ec196c41ecd64717c3ef45a9c7cc24820524b1e20e430" + "c59a9fe3efd044c692a2b7fa0d476d546b35cb09e8144877c62ade19bfeaf598" + "d8087a97acb68f8add97862c1db10f0fc032a74ba3c8fe4fbd07a28bb9a3c107" + "ad0a2a0e0da23eb74ab55f8a2b7b511e1bdae340b1d8803b46edbcef3f537c8a" + "6ec2806b89dac13989b89186587792f42e5cc2f8d08f9bb989f00b770e4c4a29" + "e1c0689809b950c04dd34e7e7f74823b1bfcc4f855bc955ec7fa53d9a6d582a5" + "186ca1c282f030869fe5d7caee534b98ca7748c37476c6c69a277051"); + v.push_back( + "2894313e0e99da4a8d543ab6dd9803eeb369cd4b3a7c590e2e56b9f99487c16b" + "ef7eb190ff51fd2aa6b93723e712717cf721106115f10b2951141deb33b18ef7" + "ef1e7145ed9b5eff22fa30780f05948adc7195118e8411f853b3a26caf220e81" + "d241121dd431716a58994b7d97bf76b4acec5424818e545c4c334586efb63907" + "dd436e92bd04aee200bd7dcb7cc1ca5f39e67e355b9e1fce7ddf882e324bcf95" + "7500212461df00303eba46f538c6de2a1681d08432e3e28ed69767b1538b09ee" + "f75637da24d100ca8acbe418760edfa21a1125a8dcdb30762544405405"); + v.push_back( + "0a760b5a7b059f5f97b19552306d18463598a21ce5d01c9d228bdf61bc5eb4a6" + "820cefc9e3d59018f775e945e20518f9520ac91a7469b612d738a900c94e0ac4" + "2431aeae194040c02b6d628f1815e5270edd3bf616221b0238e779cfca37c303" + "4a0a747a0c0b25a60d9fc67abd1fbee5498355cde9821814edc8785b2f965d29" + "eccb4aa1b6c5c417150afe9e2537bad0b696228e073d73b0e6753fd165831b47" + "9c95adeeb2dea1466ab405ec85bf72a436a0764bda5581369fab7dc094cb0e85" + "56e3336bf1c6380c1f35cec4f38cb2e2ab03969ae62c7fa81b3a43869cdd"); + v.push_back( + "6f268254d6fcea73605bd2ce4d85df1c319e2ec84dcb204d46037e25d3acc810" + "51f9a32be04f687b642a6a18d506b26b0c6c8f2c00a6bf1726c628113069beed" + "e1020cfc255391be45cdf3ebda30042bb300c3053608716ecf5f8c435bb10d4f" + "5da66a8695788b034c28956d2fc6fe5dcf4b3285fab8fb650d3c4c6ee0ecaffa" + "47f8177eab9ebec5f8adc5a8cfaa9c3adbc94b91629413e6da4781a86525a3b2" + "7597c78b0642cce5f12e5bcb844d2439bf901c3934d66e17f2414b1b8a62b534" + "47203cdbb9299f928799a0701c58cd816afc73f0001f58b4097cad8e1412e5"); + v.push_back( + "bbdf17fb1bb249561642899623e7c7f5cd41a403171b675bbe59e5ede54a76eb" + "e2cddfe9eb77a4a66494a09748f25e1fc3f0bd744bc685ea2199196e0859d6a4" + "b6733f866b7b2df0ed69eb5c5ff6223a520c9ea99840c9c5ff0795d9ba45118d" + "491d4fd6ed8413dc22e0f1ecd64e64a01c7b93ef9a9ee7dba83bae239d116637" + "ccef80f25cca04acfa82eed665c46c98a9bc04121f70d781c73ab892f7982d0e" + "772ab37dfdc3b84d2f357efbd60542ade377ba416d9d5a595c96d17ed8dd5c8a" + "32f114ec99512dc2001227013eba20356120f0f712291c8da6df5681e2197ef4"); + v.push_back( + "439943e702aed09907d07e36c81f1fba89772351f4b60fd69e3058e88330e242" + "470c0bba6e42a3c16e1b89115eeb4226c2d9d2e49ffba7038b3bca20e0802894" + "7b166957ff2bd91d21bcc6377f105b3d49a91cae8eb6b9b701de96a423445dde" + "2472ba3eb001261c17ca50a955c0daf9832c7fe86f9434f88d2411d7a030389e" + "7d93f14b6568b300aab8f54865343ae1863852827c9f72e7102e92a1f6d67c55" + "ddc6a2b216241893d010bbe104d2229acb0282263979d5b0b86e2768ad7a59ed" + "51935d29bdb7989bc3b9900c6e7e2ca65d27b9673d2c8def797c3fa554a032b8" + "c9"); + v.push_back( + "4f66b96ecfb7dd7f1fe069e77f9a40ea372bac1f13c0c8b29e03a4384a928ddc" + "f6d0c7b29e429991d43a1d835878f4d597b59da447b448209788dc3cae8f7b3f" + "110490e1bd0e7d096d1d4b433b2acc70031b74daafee42f3ea8cfb12aa2a72bf" + "12217457e3ccd4660a9ce8c6b1adc002dd5e50faa748546920b61e27f1e6ae0f" + "cb4eda0336381d81833321eb8edef96ed046bb88416c95cfed95d30321ba5395" + "2c9b738ea3a6c8650ae31bcd1342016ec070e4527ac9509b4542d9983ad63ca2" + "26528448d46ffd6417f70c78dbc5160f546d92a4ab0854aa6abe37481824ea95" + "6792"); + v.push_back( + "e71efa0eae7d17b57212f0a1b9e9ce30e9442bcbe26312fe8dc1dc0e2b0b1ef0" + "28e0e98ac816aca2af4a725a0abce96c0907cca5c07c612707dc785eff79e759" + "393258f90b981d7f4d89833629d32507aeab8348d628484e67b4783c0bce6d81" + "0ccbffdc77ee2796553c9182f5ef9ef6d84774518c05374ea6cac33f720767d7" + "a8ed29c3c422a3667a692e0bb8cf9439d879ef90659636442bbe07438dcc1bba" + "764c6497433fc000a09b7eb5518b2c179364e829f7a1128c7504935503ebc7d1" + "d59166a843ce018f721e4d554fd27b731570ddda8482e67f03e6669ed4ef2511" + "aa7bd9"); + v.push_back( + "fc646e856c320f2b9caa33bd90bf08231db8740d7fd3ced036411aa80b7650b5" + "8ae100bc07195e88d8ccc460aa58557482a794f15204a51ee45adb7986bff620" + "03a32083e5bab62d66ac406dd74bfaa09cbfd21f2467457a51c3cd4988d40628" + "d65b6363e186f7be7195d110d772f3ae0a8c24be2b0d28ffbbe00b133cce4ecb" + "51651f0d8f6ed63ef5ed012c93bf58c221ee7837c6c7ea0c09302570cbf2316e" + "76474cf264633c5b28e71988ebf9bdc055f127e19b49a46d892291b76f70ac29" + "0f87c8534292d76c4c7bac67a2dc498a81c108e52b8c0db290628121882a067c" + "ffe235a2"); + v.push_back( + "c6f6b3eca36be502cfe65b1d4803854336969b65febede26d9513e83c6d55a38" + "948a85c54997c99f206fbef972f473a8aee5ab44d32eb75f38f03ecaa31223cb" + "c4bff215772061afb48a80705e1511d0cdd4ddf00a365a09d7e1e8daf0f32629" + "bde8576e2055e5fee04053f661224f96e28c3c3b56c8bcc6bfe14c7a224242dc" + "f0e3e7f002192655846037017acaf069c63a44b72a343a14cfed90ced833822d" + "e6118a5b5b257bbce56d24ae81bc731e0b4a318e45a84310bbcb569833dde17b" + "396f76b4b0f72f4e59239ab3738d028319765e3e79dc752f2aecf2a3ab5c5192" + "3d8d6bc58d"); + v.push_back( + "d11ae03c75a7b0bc1723d301b4bd2775085801d01ae5cccb9dec444e46e44f0f" + "413ab0ac34a005a4b7877cfcbc6d7db3b46071c0e73b90a430f4cd3a2a457676" + "3926df0894dfdaf47ecf18d2d4a9844e818ade7c11a993d11349e04a6b3da209" + "0889e0ac67fbb0b86817215505a728bacd2e3dd7be9f80ec92c591037d16fd1b" + "8f706c95c097b18f01aa4577437bb2a38c569a64fe262192fe00921df4a9d95f" + "3e481fcf422d7d35fccdfab474f633e17dc041285d6fd59831056846166cf8f9" + "5e56a6204239794125b1502376f1934ff62b35a2dcf1f51b53720a96f191d720" + "32138035cff2"); + v.push_back( + "6d243cfc8bc00b2def28def7543a0ca2b0d531c4be9cd1cef41d53bb2b84da4f" + "3e1f58c2fe89a49658dc0ff614beaec3949dbc673a45fce18e7bfaf7953e16b8" + "298c406e5013949e268aeed343a2abb4ffc1e740937f40fc5c99313209688929" + "a6fab1223ce62e924ec290c21702acc2627a1862098cf3eaed6ab08004eca710" + "8b1b02fd6188e04353012a5eac7bf17547ffa761cb7430fec5d21d576bafa3ae" + "e71be6787d6d210a72cda07bf8fefbbd49c3326826836698ba003f3482005907" + "d5fd7f4fc8d31ed92802b6ad28df0c174cdb525238dfe82cc324b628f3359ccb" + "57f4024c06c17e"); + v.push_back( + "579efb8aa51c50b13766d79a95712358ad522c2a1baa33b10df4b6817f8909e3" + "d855b037f9f382a18aed61fa776ceb53dcb9bd2adfddb7b69e417e3ec6740b36" + "3852625ad0182e686274b3556c1fd71b3cb5df25c64ed23ce194f247022ac398" + "408e804de1fe525046f6455c41122a3818f24b312c5db11714537f75d0f96d3c" + "6ce02e379046a7878514157398153f9187dc5ef160e9f3572dd7abe016fc710e" + "f0ab7670610305ec612f084026771e93274bed74cadeb5a6522076af6db38fd1" + "84c07c3721f281754119221cb49e1c35ad07838565f10f234e05bae1d88f66d8" + "e9ab5e51d838a151"); + v.push_back( + "a857f1dae5f4e7fc5b8035e82a3df1735dc0eb001c70da569d93e85efcb3ee64" + "bb58c553770ae07c9fd6bbc5cbc8b6743b7587f1a75d277eed7599f946d94944" + "aa668568dcb42fec6a3a7144f52c89731996207664ec0bd7aa0aae2dec262bbb" + "3a3f4edc902619e5e5e24656f98d5dec3b9ac6937b3a27a913e43782dddfa351" + "dc863b9b72465f653f59e1cc2cf32e04ead53cd231ec6f00603517b191bdc343" + "4b989ff9d8e83f4ecd0bd1a145593e245b8fff15bdbfdcbbd7e1696d28df5ac6" + "d285bff0eac38bb5342dd7ceb630e4f238019ca1235e13b8cef8f03b0945a3b1" + "f777cef905b15a1087"); + v.push_back( + "e0bdc65893480aab82ac4665e5e732a634619d7cb68fe5cfc25a4726c15ca1fd" + "604d45aff79387153e8466f724c902c2a1ada5c53d61daca9320722c47342fef" + "394645b5b8631dbf45046afd67b682ffca139ccf97f1f94dc0ee90155c4eed46" + "dc42e658b105d592d0a70eb43a68a0dd9f3b8eb6609355c8169cfa483956afa4" + "6ff9ea55eaf0e66a7c36ca0d19d6986175c034d4105976580ff9d9d4959d0002" + "5b5978ae7c76fde710f7d8c9161befb62f40179be1d072f43610709af18f4727" + "98e96586a11dea0b1e37ecb4254d9b0b376916ec412f5668e93f332f8a1ef883" + "f57f2fec44ada795286a"); + v.push_back( + "bc9fb80a5685dde8244f6f552a32794a8fe86ac9a3822fcc753484726513c7e1" + "29c5794b1055e1202f1cd91ebc5ee789d131c532c9efd2248beeea52cbe0eb96" + "287a6e3a4a8b763afb37f3176e11e2c4fd9c593c3246f51bb5092f94e7d6d63b" + "5ba5942dda975c01c3b4990a11a8571ce3494809584605d4b9d06b45d1a96046" + "16b10477caa617542c6a89f1e8a155a1ba4b0e31c63497a8fd48ed62b47ea098" + "f4850b9d386a2a0de0a1d793d20e720c4e1d63ab2e19133bcb2a379ca830bea3" + "2ac8103eb9105207eb10c812c0fe3dee657a357ecb13e405cb23bfbad572bee5" + "ca80fb5bc4b315c3821b28"); + v.push_back( + "f9ae35ffbbb49c533eb324cd02252de0aedaa3748c4c8884c389ca6abae2e953" + "e405212dd687237efc676f7a000235fb13d604e0481617839493bd10a2ccac9c" + "7d8d11186dd33134a41da716ee7a4a7e5085e48fea22b9b753709b9d86d264a5" + "21978955b2e4836573859f7124d6c9d89107f55914f33cd009fef23fd8f28c85" + "fc53d6a7ff331ab2df6899ea0565ae4fe2f0168830ff1c20f39f994f37a857d5" + "02002b1239f7809b117856bfb92eaff2e4d8c05c718fde83825431003c5c11e6" + "61ae40b516289e3e347957669a7f20ddc665dc3bcab5bd42f2e03bca3511d835" + "19f4a6cdb8c67e0f33b12dfd"); + v.push_back( + "664950c1caf4e5737671bb02158b45a938ba5aca3f7c153b64ef531c1d7e9e79" + "bf78678abc9480046286cbf03bcea3db6de2cc5663193198e8dfa9907f771289" + "2fc522ba644d46bd956bd8a8ce8a2d35266ef237e6c1a9fd0ec6e5c5ceccd7f7" + "26e4dad639eaa21cb475e9765671cf041f45b88840d60b22c1537112c72471f4" + "d2430b6ace85ae80eaf4da52fb2ae1ad15ba2c5e7754da94de5b00f6aab061c1" + "d96a7a524ffbc1ea526d3d1744d4682985e8a1427c72f90aee27505c73ae7e37" + "8e371c6000c4602007c2fc3be33936b15483a60e0838aea4834cf8d38325ad34" + "30a1614d56ddb0370e8c8ef748"); + v.push_back( + "3deef64b2abd079784b7dbaabe360aa3f6cb20763520eff7069ec1704c9789be" + "ea0fe3390ba5af842abde876b371d43a94771b513a081099e136d32f4f8a88f4" + "c9630db04f05ae6019b814489a5ecb7ace0c25476ae1decd59c6dda06de38e3e" + "06347cd2294aeaaf941f0e030a895c2f2b2bc88e2ca698dcf6b6f18f24479e38" + "3a36caa47224719e581a20002bf2a21d8650f031f7dd1870c3153693b6246080" + "69f30a0ba6cf5a9a1eb712d92bb97ad3a3327a41069e23a7445c02d6de1e46b3" + "5b4a8a44134ee19886afbef0a4834f7a7fda53c1f784aee2ffaeecd86e7df02b" + "e15b62ea204aa3a082637c4ea34a"); + v.push_back( + "fa80ca58dee32b10b4282f576ac3f88ea89530aa712ca01a708761cfbe2a14de" + "2fb4d5ffcc486ffab600ef97e79e4d734337b637947d04f1aa87e60020be8a26" + "937d0e701b39c2ef09b54cc1fc784931bcc5d6b58b01bf8f636c6d40545ef5a7" + "a5aff122f21d72e40fa1b3bea67c5a6c27127c55ccf61b601f4d59438a453c6e" + "8ef9f1904e5c209556c085393c4ea7152412090961dc0f406dd7c008d00c8bac" + "435b6f77ce8f26240d3ca3653d86a542240b34209ae9ab87086a539a10f9fa55" + "51b9d13ed9501877faa3708219a2b0b2678ec57bb1ad31a8d0462ee7b2cc38f2" + "644969b742c0da8aefe33c5185e088"); + v.push_back( + "d87dc4024a0266375b6e4ba966765b1f98a02c0b14ae969d3a00bcbcc2c1b741" + "dc96035fddc310d2b2f801e019252489084363589be8242dd4c5454cfec5cd68" + "858d519d9f1a2660d522a399638a3dce554fbb3b9c5956f8046f7e2c488739f6" + "fc399c208c8abb94bef1e057a4e64b8a2b4a1e71903a4ebb5540934919828696" + "f09fe0a2bf4560d9206f7bf7d5e78ac1ccf8e4650d05cffb71b20725249f82b6" + "2f94730e854c2e50cdab1bda0888ca1137b4bc32a7b469191ea7ee33a329fc5c" + "ef8c096934ccd6142f109163b4efb93f12e85307da35eb6562ef110d4eedd0ba" + "a1ed720aa77c2dafccb1a33c6f5d8a23"); + v.push_back( + "c0ce6af7494dff967497d120cf99bc0fdabdd04831ba57bd6fa5d7f5d1378b1f" + "ad4aa0c5638b3aeb34730aa782515e9a720ba112933adeeed13f5407959bad97" + "15057001402327698a8512af562b75bb70e0f9883df3726407edf3a6cfdd4107" + "18ed739969ddafa6b4e186b3ee77dbf47cad4ed5e7a458bb927b8efccdd63a5b" + "2399e49926e68c6d4dafaa639354e0ba349187a0cf4f3e92774a33bf95878ac5" + "85fd72b5544ae54295a3a0d8fd0d063b0e6e77feb7deb3e617e263de65531d60" + "d138eb2e54de5d50b12c47c23ba4bc91bc477556ac56b0706629a2a89657253c" + "cd36746918be8d0b57b9e97c6146466554"); + v.push_back( + "6df5132e38e7c63b5e09d42239bf16f6a53187733ba07287b51f2362196dcd93" + "47cf74d9f6be301ed993b63ceff5192e6f68966dee3277587bf4845bb345af79" + "07217cfd0f3c99c34a0d8b723f8c70c5648b998e22ad0c4612b778235f757755" + "b5fdc4f00684d5aed5c135fcf487f06cedf9f11934715b66589d6af2188a3b4e" + "885d28e6f223f60d98817415a2d47607ed5d5b43a7559cb2bde1021f168a9d4a" + "89d1cda0801e2c876e03208a841ed48ce86965b822039e99d56fa82d62bcd9f5" + "0deb810420e456e80f535be7baa5c1d3087f5145690a4dcf284a106ba6f5903f" + "c0f1ecb57a7b81485710c82edf7090cf382a"); + v.push_back( + "925437b13c121ef97e09a3ab9a90ccba96896302f81c52697109dfc0987eeef2" + "8b9f1062981c61076b7b2d028bb6547f50401c1268d192570bcd05d5003c9bcf" + "3845995f195339696e010981afa1adbc79857df2f72757eb4c72dd61944b68cf" + "d1805bc248ccef6a20874add8029a8b9768d632e74fd03698d959b71b3e9e801" + "280c022ba6d1b193cad60227a22fada2a0ff5f00b673e866127cc2da1c355cf5" + "8093fcc65580d2f1795c2ecae21ac5f0bb5737d748dbe3f83d26bc5194b00a50" + "250367fc687d813acb857acdd580aeef2637fa78c2a7ee2dd7543d4a40d37e49" + "673aa073932dfb75e9d79c087fe757db4414d6"); + v.push_back( + "a840502c7c8a93e3a9722b1baa51b553df591b2091a842678e4c68e34c92afcb" + "1099b3d3334e247aa2acc24e03a32b438dec4ffa644f114ff50e3683d6955627" + "54134c73ffe785f1a2c87591a50239402d6302c30c8365dd8f50dfeb5c2479f7" + "60eb119f31686e29ae973b46ce646463e1e56c0f8a6252b85d83bfd17fa22ce9" + "8a9dc2880db8fe277d6f92cd4cf7cf73cd930c9e33cf61395a36548b31ca1f8b" + "27dd43100fe9df2884a7b384f14bf7ccb69e8a8b21884012058c11e3e1078727" + "e452dbbc49c26db558c3d00032dffb21ae2841a186fc66d5bc5243ddad577727" + "fbee6bf8c2d0af778773a1b5250e875483bca9c0"); + v.push_back( + "8e474a9a84ca66665afbe283ea1dcc50e9a4e962a8c4a57aae5531047a062852" + "db6b2a0622fb46cd62be1be9136a41834ce55ea5676142415b7c3ad60901a365" + "df3197a375d9b2d78eaab078eaa1df2e0bce6e5f6c983a73f15d8275ebc31867" + "a1b85abad097742e6213841ea0f2c96ca9860d73a4908b8544de88c82e12a32f" + "38c8af1434c720a82dac08152ec7b3acae5482664a68ef92a5eb8e7c27a45f27" + "50c0b4e7f057d6fb3bc36b07ea16735e12c14d0c1ba4dc5f6788428f036b4e5e" + "4fe59766e80f864d11962f02805c0ddb7f9705faa0e2eae4d3c7f1b44af42bae" + "be8b079bc063bfe14638a126926c9984210a2b932d"); + v.push_back( + "c39669d1c430c3e2c1724f007eabc83ab5965414fbcb96c5496285529885cdc7" + "fe1e499d7a10f697b7d6b1d96720481ef33758100b1237aab8d204cdfecfc332" + "4ec5232c18c95e427a16ccfdd2755850f142e67f61b5cde4a4b17b2427ce216d" + "d0021edb094c78321a6e73a120da59e11188064db3432b30942b5caf3d8692d4" + "762fb64b0a725c097d747366cba193de4651e92de640911838c351a43e85a391" + "d85638b38a85c7083ee02e41bded091399a77851ddd026ac2d8cf11f8b07883d" + "238f7e1e19acb2ef215e1d4a033cca51d7d7ff132bb89cfcde2693b3a41efd51" + "23a0f17a64d7e0a6d2e5b77283e99ab1c69fcc6d20e0"); + v.push_back( + "7dbeac7fa7003c93db93ad5c10e1c5a7d1f3d25df52edc39192ad115a9aa1429" + "86803a35912edf5568ff4d35a8a68a2db44d5c2ae93c7239198c642dc0732e28" + "f703dbdf4b586a3ad2db363fe17c27c77e08344cd8fa36db95665ca974b5a061" + "3f3eb584eb6b371ef1432d39edbed3ef88104d0664a006b2b08ade648f90da57" + "661b267ec637c147bdfec665bb05e01e4d607070b8eeddbfce52ab461b4a54d4" + "c3c3eb33c6213eeb5581c7d752669d70ba1542c9f83a3e8e5445afc468306180" + "268083aa7c0c471929dc70150d3886e2fdd8ffa1821f956b3eb1cb5d8870c369" + "10bed17f32872a8c36e6df6a77d2b8ba67d0e367b71137"); + v.push_back( + "9e75cfd15638c15d60ea531b2806b51d3ae590e64d6aac611812992e870fba84" + "f76c367f78c8b26de7033f87896468edc89d88a5ee582429548b620ec67388ee" + "80888be6f513009777d9243dc6d71f3b3251418ff9d2aef57287d7e9d1a62437" + "f54d39dec07aa36bb28ee45d3c7f050b8f9a3e37e233e3aa91711287510dd511" + "1616c0fe19ce08390f6033408dfcc5ad37bb6af02e8ccf794e5609d5e16e971a" + "a36e21304dfcdb4368131db4acf38f7c911368e4df2b42fb02068509e3a15b9d" + "59b87292d684966e7492a1f46e2923a9a40324b0bec5f7d1751b41feb97def10" + "447a278a062150bba4129e6ba7206bad86d4b6d7d98b06b7"); + v.push_back( + "e6cd6a12f97317f3c1fc588b7a4f1afa8abde43821301514f2970b224af94a90" + "32efa0c6f97a8434dd37bba19471faeaff3b8a8f9cb5a9acc71f00917563a8c8" + "35bb97e4fda77aa709a4e88937b852e957b01f0f2385b82db6327185a131efec" + "a048f2a853ffe5b1cfc5310513efaef893f95360447acffc38cc409f7ab04857" + "22aab359a37918c52019c86689dbb9a4f0f38c9917d76b22910ed656ffee07ac" + "efb88ec7f0809e7f0203fd3cd4a1e7527cce0c029b7c80852b86455cb9d87a6f" + "0878f08b0d001afba2768f33334d81be572503b1cc3a0af7807ec41a4dfaad3a" + "50fc96476af744cd7c49d919454c187d156799e583a8c74d03"); + v.push_back( + "02608e76c626822f416e6deb6056eba09a6898fd696174e39620d960e47b78de" + "1fc006d8130521843c8f3e610a6295fe15950c8974b2f7b18f3850a257eae172" + "69a0268ce18b321f480d96e2750e923bd32d6c05fafb4ed3eb49d45c02f2c535" + "8baa0411743c96285bee23da543dd8e21ac15326f9d9eabd3c3feb98d91cc99c" + "0322d52622321946a688e28180c1212e75461d205eaa0080ca2667c670747f8b" + "b5b18ceaadcb4fbcf5ad8be2878030b510c6fcd564c848bb08b5b877da740e68" + "4d9d52654324067c8a32f90c8ef40a9ad0067b183d1c18f93d5437f08bf03e4a" + "04cebdbf8a075e88ce8b95669b71dff7e40d384d20d1c06a31af"); + v.push_back( + "2f4119c21c013098b20b8dbd84b47fd5011c72df62b939746b7c8d496ab4173c" + "dd2d8ff9952619fbcc86d1ef2777f17638de90c1644b17e27d7ed97da0074a2f" + "530b2441eb6d0eb56eb46ce882105bf2ae3d956c4d7e5be803c5dab0ce7c5554" + "8306cb9105ab5d098288d8aeedc03cca581721ef1cab2e04e315cd7f8bddae7c" + "9ac4afa865a15bbf558b8f4205a6fdf405d021b67a0326efda528149b1729c26" + "b3b4d3869425f324b5f0865a6be0ed9dd04893f1fa2da06b0e5665fc317e89b4" + "7cb71fe6e673878ae4839fbbdb26fae94cf37583985b642186afafa3c896c55e" + "9284ee2b7e5fde9596c42d5136a5024ce6f0c6ba5fed11928ef0ad"); + v.push_back( + "a270cd89ec091f4862974d10dca5a283b8c332f7b4a99527ad63fb86e1d4b64e" + "dc1281e5278d000c9c6f1bb5fca1d687f689ec64ab32d61b3f47a23c98ef7071" + "8cc1510b4785c2b56e3b619b3e5c184628e0c96255257b345a6c42a589fa245e" + "2fdbd7819b8f0460fc371d683c37a468a5eb61bfd5338fdedb66d70ac110949a" + "19b9e417b60d6fdf511eb41737c35ae15975f5a98125198f53214375ae8361f2" + "d1a4d9df67c21067a676301a040e2ff99b7f9f4b7f27a5a2db82c56f8fdb366a" + "eb3deaeff45d163c859ee2d60f11a16193a3b81f51ab9c268d53883c166fbf2a" + "f91f34735b170278a8d594c4489ef6fc530e2faa10e78c90274084b5"); + v.push_back( + "3e4cc5a816a4eb2e2c4a7fa626ed70a7dd08bc3d8b3fe70ed007c76db3fc62be" + "345d00107519a2f16c31479b9ab74553169b8a6a54c3e1bf5c142a946cba2d1c" + "ff48bfb4c4896209514a349c6367df2ade1d6b5848a4aad085db2e48ca933f92" + "17a11ffd55f1addc12f20abfbd71382836df2283e739bd003031acafb7331fbe" + "4baef9a166f45f504f6aae650e29733a3b8f15cf39c99506cfd1bf2bd7a70ad6" + "00fd27bf34a18a8b94be6e7ccd0d92fc004de9d3f06268878ff7af6c796d3503" + "88d28760e9930a8de562d4a99f5c7446520a186337389f3763305209212571f5" + "73d0cb26ab0cbddb0b09eec2112feffcde44dcc641d2396dbd1a31d965"); + v.push_back( + "f34675a5f4c344c1616dfffbe5ed963b6308a5409b7d0106a2a733117f9ad889" + "23d33478d4d0f52058f03bf7c2da3f26221cc0495fe9edb16bd32682965f992d" + "7e9a14daae5cd44f29d4dd92d0b4f1a893394c659c2a755231ab20e59a31aec6" + "451d3b301d6e7a41027fb8a2520177094b7422575803e72e647de294a04c4f34" + "f8e487036de84679f3c5f915608cfd15d565e24b8ae27acefcbf54b033a83882" + "745f6418a217ccd0f8ae4e10ff04e67f57b36d94dcd5b442f6e36e452ffbf6ec" + "7ba6490e079419252d54ab64c5afdde196d0b5c352ad70ce39b16791cccbb33d" + "498d5a7ffd2ae2174b34b23f78e8972a5fa04f7ebe66203d681bb163aa18"); + v.push_back( + "65caea398636380c6955c7549491c91157776fa1a6514355837e51fc6bbc35b7" + "bb8b44fe019c1be93ce474e810305e36e5cd445b417001cb2b8bba78af6fdc1c" + "12b83e326a5d323752930c5fe879629d5f5772f872b3db4ddb1cbf43ef3115e3" + "44327b3dcba6a7d8c82511c74a70b12b405481e66dbd1b8a7a9cdab1d52bdcde" + "972aba064915ceee02e7901e757d1470fabc32f9ab873508c6e243b956cac2d6" + "3aeb32b179f2cfab3cb4c2345dfb6a18c05b97f9e659c0020de22f85b5ceef47" + "0a5ad6e8597c8570a85be25d48d60151577f9a4fbe2c09862dd57ff734e156f6" + "6fd7107ccfe0e46193d2272ce6d6c0dfc0a81cef52cbd61d2964aea53922bb"); + return v; +}(); + +static std::vector keyed_test_data = []() { + std::vector v; + v.push_back("64"); + v.push_back("f457"); + v.push_back("e8c045"); + v.push_back("a74c6d0d"); + v.push_back("eb02ae482a"); + v.push_back("be65b981275e"); + v.push_back("8540ccd083a455"); + v.push_back("074a02fa58d7c7c0"); + v.push_back("da6da05e10db3022b6"); + v.push_back("542a5aae2f28f2c3b68c"); + v.push_back("ca3af2afc4afe891da78b1"); + v.push_back("e0f66b8dcebf4edc85f12c85"); + v.push_back("744224d383733b3fa2c53bfcf5"); + v.push_back("b09b653e85b72ef5cdf8fcfa95f3"); + v.push_back("dd51877f31f1cf7b9f68bbb09064a3"); + v.push_back("f5ebf68e7ebed6ad445ffc0c47e82650"); + v.push_back("ebdcfe03bcb7e21a9091202c5938c0a1bb"); + v.push_back("860fa5a72ff92efafc48a89df1632a4e2809"); + v.push_back("0d6d49daa26ae2818041108df3ce0a4db48c8d"); + v.push_back("e5d7e1bc5715f5ae991e4043e39533af5d53e47f"); + v.push_back("5232028a43b9d4dfa7f37439b49495926481ab8a29"); + v.push_back("c118803c922f9ae2397fb676a2ab7603dd9c29c21fe4"); + v.push_back("2af924f48b9bd7076bfd68794bba6402e2a7ae048de3ea"); + v.push_back("61255ac38231087c79ea1a0fa14538c26be1c851b6f318c0"); + v.push_back("f9712b8e42f0532162822f142cb946c40369f2f0e77b6b186e"); + v.push_back("76da0b89558df66f9b1e66a61d1e795b178ce77a359087793ff2"); + v.push_back("9036fd1eb32061bdecebc4a32aa524b343b8098a16768ee774d93c"); + v.push_back("f4ce5a05934e125d159678bea521f585574bcf9572629f155f63efcc"); + v.push_back("5e1c0d9fae56393445d3024d6b82692d1339f7b5936f68b062c691d3bf"); + v.push_back( + "538e35f3e11111d7c4bab69f83b30ade4f67addf1f45cdd2ac74bf299509"); + v.push_back( + "17572c4dcbb17faf8785f3bba9f6903895394352eae79b01ebd758377694cc"); + v.push_back( + "29f6bb55de7f8868e053176c878c9fe6c2055c4c5413b51ab0386c277fdbac75"); + v.push_back( + "bad026c8b2bd3d294907f2280a7145253ec2117d76e3800357be6d431b16366e" + "41"); + v.push_back( + "386b7cb6e0fd4b27783125cbe80065af8eb9981fafc3ed18d8120863d972fa74" + "27d9"); + v.push_back( + "06e8e6e26e756fff0b83b226dce974c21f970e44fb5b3e5bbada6e4b12f81cca" + "666f48"); + v.push_back( + "2f9bd300244f5bc093ba6dcdb4a89fa29da22b1de9d2c9762af919b5fedf6998" + "fbda305b"); + v.push_back( + "cf6bdcc46d788074511f9e8f0a4b86704365b2d3f98340b8db53920c385b959a" + "38c8869ae7"); + v.push_back( + "1171e603e5cdeb4cda8fd7890222dd8390ede87b6f3284cac0f0d832d8250c92" + "00715af7913d"); + v.push_back( + "bda7b2ad5d02bd35ffb009bdd72b7d7bc9c28b3a32f32b0ba31d6cbd3ee87c60" + "b7b98c03404621"); + v.push_back( + "2001455324e748503aa08eff2fb2e52ae0170e81a6e9368ada054a36ca340fb7" + "79393fb045ac72b3"); + v.push_back( + "45f0761aefafbf87a68f9f1f801148d9bba52616ad5ee8e8ac9207e9846a782f" + "487d5cca8b20355a18"); + v.push_back( + "3a7e05708be62f087f17b41ac9f20e4ef8115c5ab6d08e84d46af8c273fb46d3" + "ce1aabebae5eea14e018"); + v.push_back( + "ea318da9d042ca337ccdfb2bee3e96ecb8f907876c8d143e8e44569178353c2e" + "593e4a82c265931ba1dd79"); + v.push_back( + "e0f7c08f5bd712f87094b04528fadb283d83c9ceb82a3e39ec31c19a42a1a1c3" + "bee5613b5640abe069b0d690"); + v.push_back( + "d35e63fb1f3f52ab8f7c6cd7c8247e9799042e53922fbaea808ab979fa0c0965" + "88cfea3009181d2f93002dfc11"); + v.push_back( + "b8b0ab69e3ae55a8699eb481dd665b6a2424c89bc6b7cca02d15fdf1b9854139" + "cab49d34de498b50b2c7e8b910cf"); + v.push_back( + "fb65e3222a2950eae1701d4cdd4736266f65bf2c0d2e77968996eadb60ef74fb" + "786f6234973a2524bdfe32d100aa0e"); + v.push_back( + "f28b4bb3a2e2c4d5c01a23ff134558559a2d3d704b75402983ee4e0f71d273ae" + "056842c4153b18ee5c47e2bfa54313d4"); + v.push_back( + "7bb78794e58a53c3e4b1aeb161e756af051583d14e0a5a3205e094b7c9a8cf62" + "d098fa9ea1db12f330a51ab9852c17f983"); + v.push_back( + "a879a8ebae4d0987789bcc58ec3448e35ba1fa1ee58c668d8295aba4eaeaf276" + "2b053a677e25404f635a53037996974d418a"); + v.push_back( + "695865b353ec701ecc1cb38f3154489eed0d39829fc192bb68db286d20fa0a64" + "235cde5639137819f7e99f86bd89afcef84a0f"); + v.push_back( + "a6ec25f369f71176952fb9b33305dc768589a6070463ee4c35996e1ced4964a8" + "65a5c3dc8f0d809eab71366450de702318e4834d"); + v.push_back( + "604749f7bfadb069a036409ffac5ba291fa05be8cba2f141554132f56d9bcb88" + "d1ce12f2004cd3ade1aa66a26e6ef64e327514096d"); + v.push_back( + "daf9fa7dc2464a899533594e7916fc9bc585bd29dd60c930f3bfa78bc47f6c84" + "39448043a45119fc9228c15bce5fd24f46baf9de736b"); + v.push_back( + "943ea5647a8666763084da6a6f15dcf0e8dc24f27fd0d9194805d25180fe3a6d" + "98f4b2b5e0d6a04e9b41869817030f16ae975dd41fc35c"); + v.push_back( + "af4f73cbfc093760dfeb52d57ef45207bbd1a515f5523404e5d95a73c237d97a" + "e65bd195b472de6d514c2c448b12fafc282166da132258e9"); + v.push_back( + "605f4ed72ed7f5046a342fe4cf6808100d4632e610d59f7ebb016e367d0ff0a9" + "5cf45b02c727ba71f147e95212f52046804d376c918cadd260"); + v.push_back( + "3750d8ab0a6b13f78e51d321dfd1aa801680e958de45b7b977d05732ee39f856" + "b27cb2bcce8fbf3db6666d35e21244c2881fdcc27fbfea6b1672"); + v.push_back( + "8f1b929e80ab752b58abe9731b7b34eb61369536995abef1c0980d93903c1880" + "da3637d367456895f0cb4769d6de3a979e38ed6f5f6ac4d48e9b32"); + v.push_back( + "d8469b7aa538b36cdc711a591d60dafecca22bd421973a70e2deef72f69d8014" + "a6f0064eabfbebf5383cbb90f452c6e113d2110e4b1092c54a38b857"); + v.push_back( + "7d1f1ad2029f4880e1898af8289c23bc933a40863cc4ab697fead79c58b6b8e2" + "5b68cf5324579b0fe879fe7a12e6d03907f0140dfe7b29d33d6109ecf1"); + v.push_back( + "87a77aca6d551642288a0dff66078225ae39d288801607429d6725ca949eed7a" + "6f199dd8a65523b4ee7cfa4187400e96597bfffc3e38ade0ae0ab88536a9"); + v.push_back( + "e101f43179d8e8546e5ce6a96d7556b7e6b9d4a7d00e7aade5579d085d527ce3" + "4a9329551ebcaf6ba946949bbe38e30a62ae344c1950b4bde55306b3bac432"); + v.push_back( + "4324561d76c370ef35ac36a4adf8f3773a50d86504bd284f71f7ce9e2bc4c1f1" + "d34a7fb2d67561d101955d448b67577eb30dfee96a95c7f921ef53e20be8bc44"); + v.push_back( + "78f0ed6e220b3da3cc9381563b2f72c8dc830cb0f39a48c6ae479a6a78dcfa94" + "002631dec467e9e9b47cc8f0887eb680e340aec3ec009d4a33d241533c76c8ca" + "8c"); + v.push_back( + "9f6589c31a472e0a736f4eb22b6c70a9d332cc15304ccb66a6b97cd051b6ed82" + "f8990e1d9bee2e4bb1c3c45e550ae0e7b96e93ae23f2fb8f63b309131e72b36c" + "ba6a"); + v.push_back( + "c138077ee4ed3d7ffa85ba851dfdf6e9843fc1dc00889d117237bfaad9aa7571" + "92f73556b959f98e6d24886ce48869f2a01a48c371785f12b6484eb2078f08c2" + "2066e1"); + v.push_back( + "f83e7c9e0954a500576ea1fc90a3db2cbd7994eaef647dab5b34e88ab9dc0b47" + "addbc807b21c8e6dd3d0bd357f008471d4f3e0abb18450e1d4919e03a34545b9" + "643f870e"); + v.push_back( + "3277a11f2628544fc66f50428f1ad56bcba6ee36ba2ca6ecdf7e255effc0c302" + "35c039d13e01f04cf1efe95b5c2033ab72adda30994b62f2851d17c9920eadca" + "9a251752dc"); + v.push_back( + "c2a834281a06fe7b730d3a03f90761daf02714c066e33fc07e1f59ac801ec2f4" + "433486b5a2da8faa51a0cf3c34e29b2960cd0013378938dbd47c3a3d12d70db0" + "1d7d06c3e91e"); + v.push_back( + "47680182924a51cabe142a6175c9253e8ba7ea579ece8d9bcb78b1e9ca00db84" + "4fa08abcf41702bd758ee2c608d9612fed50e85854469cb4ef3038acf1e35b6b" + "a4390561d8ae82"); + v.push_back( + "cec45830cd71869e83b109a99a3cd7d935f83a95de7c582f3adbd34e4938fa2f" + "3f922f52f14f169c38cc6618d3f306a8a4d607b345b8a9c48017136fbf825aec" + "f7b620e85f837fae"); + v.push_back( + "46fb53c70ab105079d5d78dc60eaa30d938f26e4d0b9df122e21ec85deda9474" + "4c1daf8038b8a6652d1ff3e7e15376f5abd30e564784a999f665078340d66b0e" + "939e0c2ef03f9c08bb"); + v.push_back( + "7b0dcb52791a170cc52f2e8b95d8956f325c3751d3ef3b2b83b41d82d4496b46" + "228a750d02b71a96012e56b0720949ca77dc68be9b1ef1ad6d6a5ceb86bf565c" + "b972279039e209dddcdc"); + v.push_back( + "7153fd43e6b05f5e1a4401e0fef954a737ed142ec2f60bc4daeef9ce73ea1b40" + "a0fcaf1a1e03a3513f930dd5335723632f59f7297fe3a98b68e125eadf478eb0" + "45ed9fc4ee566d13f537f5"); + v.push_back( + "c7f569c79c801dab50e9d9ca6542f25774b3841e49c83efe0b89109f569509ce" + "7887bc0d2b57b50320eb81fab9017f16c4c870e59edb6c26620d93748500231d" + "70a36f48a7c60747ca2d5986"); + v.push_back( + "0a81e0c547648595adca65623ce783411aac7f7d30c3ad269efafab288e7186f" + "6895261972f5137877669c550f34f5128850ebb50e1884814ea1055ee29a866a" + "fd04b2087abed02d9592573428"); + v.push_back( + "6a7b6769e1f1c95314b0c7fe77013567891bd23416374f23e4f43e27bc4c55cf" + "ada13b53b1581948e07fb96a50676baa2756db0988077b0f27d36ac088e0ff0f" + "e72eda1e8eb4b8facff3218d9af0"); + v.push_back( + "a399474595cb1ccab6107f18e80f03b1707745c7bf769fc9f260094dc9f8bc6f" + "e09271cb0b131ebb2acd073de4a6521c8368e664278be86be216d1622393f234" + "35fae4fbc6a2e7c961282a777c2d75"); + v.push_back( + "4f0fc590b2755a515ae6b46e9628092369d9c8e589e3239320639aa8f7aa44f8" + "111c7c4b3fdbe6e55e036fbf5ebc9c0aa87a4e66851c11e86f6cbf0bd9eb1c98" + "a378c7a7d3af900f55ee108b59bc9e5c"); + v.push_back( + "ed96a046f08dd675107331d267379c6fce3c352a9f8d7b243008a74cb4e94108" + "36afaabe871dab6038ca94ce5f6d41fa922ce08aba58169f94cfc86d9f688f39" + "6abd24c11a6a9b0830572105a477c33e92"); + v.push_back( + "379955f539abf0eb2972ee99ed9546c4bbee363403991833005dc27904c271ef" + "22a799bc32cb39f08d2e4ba6717d55153feb692d7c5efae70890bf29d96df023" + "33c7b05ccc314e4835b018fec9141a82c745"); + v.push_back( + "e16cc8d41b96547ede0d0cf4d908c5fa393399daa4a9696e76a4c1f6a2a9fef7" + "0f17fb53551a8145ed88f18db8fe780a079d94732437023f7c1d1849ef69ad53" + "6a76204239e8ba5d97e507c36c7d042f87fe0e"); + v.push_back( + "a81de50750ece3f84536728f227208bf01ec5b7721579d007de72c88ee206633" + "18332efe5bc7c09ad1fa8342be51f0609046ccf760a7957a7d8dc88941adb936" + "66a4521ebe76618e5ddc2dd3261493d400b50073"); + v.push_back( + "b72c5fb7c7f60d243928fa41a2d711157b96aef290185c64b4de3dcfa3d644da" + "67a8f37c2ac55caad79ec695a473e8b481f658c497edb8a191526592b11a4122" + "82d2a4010c90ef4647bd6ce745ebc9244a71d4876b"); + v.push_back( + "9550703877079c90e200e830f277b605624954c549e729c359ee01ee2b07741e" + "cc4255cb37f96682dafcdbaade1063e2c5ccbd1918fb669926a67744101fb6de" + "3ac016be4c74165a1e5a696b704ba2ebf4a953d44b95"); + v.push_back( + "a17eb44d4de502dc04a80d5a5e9507d17f27c96467f24c79b06bc98a4c410741" + "d4ac2db98ec02c2a976d788531f1a4451b6c6204cef6dae1b6ebbcd0bde23e6f" + "ffb02754043c8fd3c783d90a670b16879ce68b5554fe1c"); + v.push_back( + "41d3ea1eaba5be4a206732dbb5b70b79b66a6e5908795ad4fb7cf9e67efb13f0" + "6fef8f90acb080ce082aadec6a1b543af759ab63fa6f1d3941186482b0c2b312" + "f1151ea8386253a13ed3708093279b8eb04185636488b226"); + v.push_back( + "5e7cdd8373dc42a243c96013cd29df9283b5f28bb50453a903c85e2ce57f3586" + "1bf93f03029072b70dac0804e7d51fd0c578c8d9fa619f1e9ce3d8044f65d556" + "34dba611280c1d5cfb59c836a595c803124f696b07ddfac718"); + v.push_back( + "26a14c4aa168907cb5de0d12a82e1373a128fb21f2ed11feba108b1bebce934a" + "d63ed89f4ed7ea5e0bc8846e4fc10142f82de0bebd39d68f7874f615c3a9c896" + "bab34190e85df05aaa316e14820b5e478d838fa89dfc94a7fc1e"); + v.push_back( + "0211dfc3c35881adc170e4ba6daab1b702dff88933db9a6829a76b8f4a7c2a6d" + "658117132a974f0a0b3a38ceea1efc2488da21905345909e1d859921dc2b5054" + "f09bce8eeb91fa2fc6d048ce00b9cd655e6aafbdaa3a2f19270a16"); + v.push_back( + "ddf015b01b68c4f5f72c3145d54049867d99ee6bef24282abf0eecdb506e295b" + "acf8f23ffa65a4cd891f76a046b9dd82cae43a8d01e18a8dff3b50aeb92672be" + "69d7c087ec1fa2d3b2a39196ea5b49b7baede37a586fea71aded587f"); + v.push_back( + "6ee721f71ca4dd5c9ce7873c5c04c6ce76a2c824b984251c15535afc96adc9a4" + "d48ca314bfeb6b8ee65092f14cf2a7ca9614e1dcf24c2a7f0f0c11207d3d8aed" + "4af92873b56e8b9ba2fbd659c3f4ca90fa24f113f74a37181bf0fdf758"); + v.push_back( + "689bd150e65ac123612524f720f54def78c095eaab8a87b8bcc72b443408e322" + "7f5c8e2bd5af9bcac684d497bc3e41b7a022c28fb5458b95e8dfa2e8caccde04" + "92936ff1902476bb7b4ef2125b19aca2cd3384d922d9f36dddbcd96ae0d6"); + v.push_back( + "3a3c0ef066fa4390ec76ad6be1dc9c31ddf45fef43fbfa1f49b439caa2eb9f30" + "42253a9853e96a9cf86b4f873785a5d2c5d3b05f6501bc876e09031188e05f48" + "937bf3c9b667d14800db62437590b84ce96aa70bb5141ee2ea41b55a6fd944"); + v.push_back( + "741ce384e5e0edaebb136701ce38b3d33215415197758ae81235307a4115777d" + "4dab23891db530c6d28f63a957428391421f742789a0e04c99c828373d9903b6" + "4dd57f26b3a38b67df829ae243feef731ead0abfca049924667fdec49d40f665"); + v.push_back( + "a513f450d66cd5a48a115aee862c65b26e836f35a5eb6894a80519e2cd96cc4c" + "ad8ed7eb922b4fc9bbc55c973089d627b1da9c3a95f6c019ef1d47143cc545b1" + "5e4244424be28199c51a5efc7234dcd94e72d229897c392af85f523c26334278" + "25"); + v.push_back( + "71f1554d2d49bb7bd9e62e71fa049fb54a2c097032f61ebda669b3e1d4593962" + "e47fc62a0ab5d85706aebd6a2f9a192c88aa1ee2f6a46710cf4af6d3c25b7e68" + "ad5c3db23ac009c8f13625ff85dc8e50a9a1b2682d3329330b973ec8cbb7bb73" + "b2bd"); + v.push_back( + "167cc1067bc08a8d2c1a0c10041ebe1fc327b37043f6bd8f1c63569e9d36ded5" + "8519e66b162f34b6d8f1107ef1e3de199d97b36b44141a1fc4f49b883f40507f" + "f11f909a017869dc8a2357fc7336ae68703d25f75710b0ff5f9765321c0fa53a" + "51675c"); + v.push_back( + "cb859b35dc70e264efaad2a809fea1e71cd4a3f924be3b5a13f8687a1166b538" + "c40b2ad51d5c3e47b0de482497382673140f547068ff0b3b0fb7501209e1bf36" + "082509ae85f60bb98fd02ac50d883a1a8daa704952d83c1f6da60c9624bc7c99" + "912930bf"); + v.push_back( + "afb1f0c6b7125b04fa2578dd40f60cb411b35ebc7026c702e25b3f0ae3d4695d" + "44cfdf37cb755691dd9c365edadf21ee44245620e6a24d4c2497135b37cd7ac6" + "7e3bd0aaee9f63f107746f9b88859ea902bc7d6895406aa2161f480cad56327d" + "0a5bba2836"); + v.push_back( + "13e9c0522587460d90c7cb354604de8f1bf850e75b4b176bda92862d35ec8108" + "61f7d5e7ff6ba9302f2c2c8642ff8b7776a2f53665790f570fcef3cac069a90d" + "50db42227331c4affb33d6c040d75b9aeafc9086eb83ced38bb02c759e95ba08" + "c92b17031288"); + v.push_back( + "0549812d62d3ed497307673a4806a21060987a4dbbf43d352b9b170a29240954" + "cf04bc3e1e250476e6800b79e843a8bd8253b7d743de01ab336e978d4bea384e" + "aff700ce020691647411b10a60acacb6f8837fb08ad666b8dcc9eaa87ccb42ae" + "f6914a3f3bc30a"); + v.push_back( + "3a263efbe1f2d463f20526e1d0fd735035fd3f808925f058b32c4d8788aeeab9" + "b8ce233b3c34894731cd73361f465bd350395aebcabd2fb63010298ca025d849" + "c1fa3cd573309b74d7f824bbfe383f09db24bcc565f636b877333206a6ad7081" + "5c3bef5574c5fc1c"); + v.push_back( + "3c6a7d8a84ef7e3eaa812fc1eb8e85105467230d2c9e4562edbfd808f4d1ac15" + "d16b786cc6a02959c2bc17149c2ce74c6f85ee5ef22a8a96b9be1f197cffd214" + "c1ab02a06a9227f37cd432579f8c28ff2b5ac91cca8ffe6240932739d56788c3" + "54e92c591e1dd76499"); + v.push_back( + "b571859294b02af17541a0b5e899a5f67d6f5e36d38255bc417486e69240db56" + "b09cf2607fbf4f95d085a779358a8a8b41f36503438c1860c8f361ce0f2783a0" + "8b21bd7232b50ca6d35428335272a5c05b436b2631d8d5c84d60e8040083768c" + "e56a250727fb0579dd5c"); + v.push_back( + "98ee1b7269d2a0dd490ca38d447279870ea55326571a1b430adbb2cf65c49213" + "1136f504145df3ab113a13abfb72c33663266b8bc9c458db4bf5d7ef03e1d3b8" + "a99d5de0c024be8fabc8dc4f5dac82a0342d8ed65c329e7018d6997e69e29a01" + "350516c86beaf153da65ac"); + v.push_back( + "41c5c95f088df320d35269e5bf86d10248f17aec6776f0fe653f1c356aae4097" + "88c938befeb67c86d1c8870e8099ca0ce61a80fbb5a6654c44529368f70fc9b9" + "c2f912f5092047d0ffc339577d24142300e34948e086f62e23ecaca410d24f8a" + "36b5c8c5a80e0926bc8aa16a"); + v.push_back( + "9f93c41f533b2a82a4df893c78faaaa793c1506974ba2a604cd33101713ca4ad" + "fd30819ffd8403402b8d40aff78106f3357f3e2c24312c0d3603a17184d7b999" + "fc9908d14d50192aebabd90d05073da7af4be37dd3d81c90acc80e8333df546f" + "17ab6874f1ec204392d1c0571e"); + v.push_back( + "3da5207245ac270a915fc91cdb314e5a2577c4f8e269c4e701f0d7493ba716de" + "79935918b917a2bd5db98050dbd1eb3894b65fac5abf13e075abebc011e651c0" + "3cafb6127147771a5c8418223e1548137a89206635c26ca9c235ccc108dc25cf" + "846e4732444bd0c2782b197b262b"); + v.push_back( + "96011af3965bb941dc8f749932ea484eccb9ba94e34b39f24c1e80410f96ce1d" + "4f6e0aa5be606def4f54301e930493d4b55d484d93ab9dd4dc2c9cfb79345363" + "af31ad42f4bd1aa6c77b8afc9f0d551bef7570b13b927afe3e7ac4de7603a087" + "6d5edb1ad9be05e9ee8b53941e8f59"); + v.push_back( + "51dbbf2a7ca224e524e3454fe82ddc901fafd2120fa8603bc343f129484e9600" + "f688586e040566de0351d1693829045232d04ff31aa6b80125c763faab2a9b23" + "3313d931903dcfaba490538b06e4688a35886dc24cdd32a13875e6acf45454a8" + "eb8a315ab95e608ad8b6a49aef0e299a"); + v.push_back( + "5a6a422529e22104681e8b18d64bc0463a45df19ae2633751c7aae412c250f8f" + "b2cd5e1270d3d0cf009c8aa69688ccd4e2b6536f5747a5bc479b20c135bf4e89" + "d33a26118705a614c6be7ecfe766932471ad4ba01c4f045b1abb5070f90ec784" + "39a27a1788db9327d1c32f939e5fb1d5ba"); + v.push_back( + "5d26c983642093cb12ff0afabd87b7c56e211d01844ad6da3f623b9f20a0c968" + "034299f2a65e6673530c5980a532beb831c7d0697d12760445986681076dfb6f" + "ae5f3a4d8f17a0db5008ce8619f566d2cfe4cf2a6d6f9c3664e3a48564a351c0" + "b3c945c5ee24587521e4112c57e318be1b6a"); + v.push_back( + "52641dbc6e36be4d905d8d60311e303e8e859cc47901ce30d6f67f152343e3c4" + "030e3a33463793c19effd81fb7c4d631a9479a7505a983a052b1e948ce093b30" + "efa595fab3a00f4cef9a2f664ceeb07ec61719212d58966bca9f00a7d7a8cb40" + "24cf6476bab7fbccee5fd4e7c3f5e2b2975aa2"); + v.push_back( + "a34ce135b37bf3db1c4aaa4878b4499bd2ee17b85578fcaf605d41e1826b45fd" + "aa1b083d8235dc642787f11469a5493e36806504fe2a2063905e821475e2d5ee" + "217057950370492f5024995e77b82aa51b4f5bd8ea24dc71e0a8a640b0592c0d" + "80c24a726169cf0a10b40944747113d03b52708c"); + v.push_back( + "46b3cdf4946e15a5334fc3244d6680f5fc132afa67bf43bfade23d0c9e0ec64e" + "7dab76faaeca1870c05f96b7d019411d8b0873d9fed04fa5057c039d5949a4d5" + "92827f619471359d6171691cfa8a5d7cb07ef2804f6ccad4821c56d4988bea77" + "65f660f09ef87405f0a80bcf8559efa111f2a0b419"); + v.push_back( + "8b9fc21691477f11252fca050b121c5334eb4280aa11659e267297de1fec2b22" + "94c7ccee9b59a149b9930b08bd320d3943130930a7d931b71d2f10234f4480c6" + "7f1de883d9894ada5ed5071660e221d78ae402f1f05af47761e13fec979f2671" + "e3c63fb0ae7aa1327cf9b8313adab90794a52686bbc4"); + v.push_back( + "cd6598924ce847de7ff45b20ac940aa6292a8a99b56a74eddc24f2cfb4579718" + "8614a21d4e8867e23ff75afd7cd324248d58fcf1ddc73fbd115dfa8c09e62022" + "fab540a59f87c989c12a86ded05130939f00cd2f3b512963dfe0289f0e54acad" + "881c1027d2a0292138fdee902d67d9669c0ca1034a9456"); + v.push_back( + "594e1cd7337248704e691854af0fdb021067ddf7832b049ba7b684438c32b029" + "eded2df2c89a6ff5f2f2c311522ae2dc6db5a815afc60637b15ec24ef9541f15" + "50409db2a006da3affffe548a1eaee7bd114e9b805d0756c8e90c4dc33cb0522" + "6bc2b393b18d953f8730d4c7ae693159cdba758ad28964e2"); + v.push_back( + "1f0d292453f04406ada8be4c161b82e3cdd69099a8637659e0ee40b8f6da4600" + "5cfc6085db9804852decfbe9f7b4dda019a7112612895a144ed430a960c8b2f5" + "458d3d56b7f427cee6358915aee7146278aed2a0296cdd929e4d21ef95a3adf8" + "b7a6beba673cdccdbdcfb2474711732d972ad054b2dc64f38d"); + v.push_back( + "b65a72d4e1f9f9f75911cc46ad0806b9b18c87d105332a3fe183f45f063a746c" + "892dc6c4b9181b1485b3e3a2cc3b453eba2d4c39d6905a774ed3fb755468beb1" + "90925ecd8e57ecb0d985125741650c6b6a1b2a3a50e93e3892c21d47ed5884ee" + "d83aa94e1602288f2f49fe286624de9d01fcb54433a0dc4ad70b"); + v.push_back( + "705ce0ffa469250782aff725248fc88fe98eb76659e8407edc1c4842c9867d61" + "fe64fb86f74e980598b92bc213d06f337bd5654fc28643c7ba769a4c31563427" + "543c00808b627a19c90d86c322f33566ce020121cc322229c3337943d46f68ef" + "939d613dcef0077269f88151d6398b6b009abb763410b154ad76a3"); + v.push_back( + "7fa881ce87498440ab6af13854f0d851a7e0404de33896999a9b3292a5d2f5b3" + "ad033530c558168fe5d2fdb9b89a2354c46cf32a0e612afc6c6485d789511bfe" + "f26800c74bf1a4cfbe30bda310d5f6029c3dccdedb6149e4971274e276dccfab" + "d63bc4b9955e8303feb57f8a688db55ecb4b33d1f9fe1b3a8ba7ac32"); + v.push_back( + "23a98f71c01c0408ae16843dc03be7db0aeaf055f951709d4e0dfdf64fffbffa" + "f900ee592ee10929648e56f6c1e9f5be5793f7df66453eb56502c7c56c0f0c88" + "da77abc8fa371e434104627ef7c663c49f40998dbad63fa6c7aa4fac17ae138d" + "8bbe081f9bd168cd33c1fbc92fa35ed687679f48a64b87db1fe5bae675"); + v.push_back( + "7b8970b6a33237e5a7bcb39272703edb92285c55842b30b9a48834b1b507cc02" + "a6764739f2f7ee6ae02a7b715a1c455e59e8c77a1ae98abb10161853f1234d20" + "da99016588cd8602d6b7ec7e177d4011edfa61e6b3766a3c6f8d6e9eac893c56" + "8903eb6e6aba9c4725774f6b4343b7acaa6c031593a36eef6c72806ff309"); + v.push_back( + "f7f4d328ba108b7b1de4443e889a985ed52f485f3ca4e0c246aa5526590cbed3" + "44e9f4fe53e4eea0e761c82324649206ca8c2b45152157d4115e68c818644b03" + "b65bb47ad79f94d37cb03c1d953b74c2b8adfa0e1c418bda9c518ddcd7050e0f" + "149044740a2b16479413b63fc13c36144f80c73687513dca761ba8642a8ae0"); + v.push_back( + "2d7dc80c19a1d12d5fe3963569547a5d1d3e821e6f06c5d5e2c09401f946c9f7" + "e13cd019f2f9a878b62dd850453b6294b99ccaa068e542993524b0f63832d48e" + "865be31e8ec1ee103c718340c904b32efb69170b67f038d50a3252794b1b4076" + "c0620621ab3d91215d55ffea99f23d54e161a90d8d4902fda5931d9f6a27146a"); + v.push_back( + "77dff4c7ad30c954338c4b23639dae4b275086cbe654d401a2343528065e4c9f" + "1f2eca22aa025d49ca823e76fdbb35df78b1e5075ff2c82b680bca385c6d57f7" + "ea7d1030bb392527b25dd73e9eeff97bea397cf3b9dda0c817a9c870ed12c006" + "cc054968c64000e0da874e9b7d7d621b0679866912243ea096c7b38a1344e98f" + "74"); + v.push_back( + "83bed0d556798f2b419f7056e6d3ffada06e939b95a688d0ec8c6ac5ea45ab73" + "a4cf01043e0a170766e21395f27ab4b78c435f5f0dfe6e93ab80df38610e4115" + "8429ddf20296f53a06a017723359fe22dc08b5da33f0800a4fe50118e8d7eab2" + "f83a85cd764bf8a166903bd0e9dcfeeceba44ff4ca4439846458d31ea2bb5646" + "45d1"); + v.push_back( + "ea12cf5a113543e39504123036f15a5bafa9c555562469f99cd29996a4dfaaab" + "2a34b00557ccf15f37fc0cc1b3be427e725f2cd952e50af7970dda9200cd5ce2" + "52b1f29c40067fea3027ed686190803b59d834179d1b8f5b55abe55ad174b2a1" + "188f7753ec0ae2fc01316e7d498b68ee3598a0e9baaaa664a60f7fb4f90edbed" + "494ad7"); + v.push_back( + "55266358332d8d9e68bd13432088beadf95833aab67a0eb3b10650414255f299" + "e2670c3e1a5b2976159a46c72a7ce57d59b7be14c15798e09ed50fa312a431b0" + "264d7a1396aa6168bde897e208ece53d2cfc83786113b1e6eac5e9bb98984abb" + "6c8d64eebb991903254abc650c999bb9958a5d7937434b869bc940e21b9dc1cc" + "8982f2ba"); + v.push_back( + "4d6104ded730aefe02873f4c741232c8234a6d66d85393aff57fbf56ba634766" + "6988dfc4d58f3cc895a0da598822edeee4533d24ec0ee292fd5e1ad04898ffbc" + "1ff4bef14dec220babcb0f28fffe32a6e2c28aaaac16442bf4feb02917d18bb3" + "a415d84fa9358d5a9852688d846c92271911f934181c30f82434d915f93f155a" + "1ffbf0b125"); + v.push_back( + "eb5f579a4c476af554aac11e5719d378549497e613b35a929d6f36bb8831d7a4" + "66aa76de9be24ebb55543f1c13924f64cfd648a5b3fa90387315c16174dbf1e9" + "a183c196d9bb8f84af65f1f8212429aadc11ef2426d07d4716062b85c8d5d2df" + "f8e21b9e62b7fa7dbd57d72633054b464fb28583a56ca13ccc5ddc74dae94249" + "2f31731e7046"); + v.push_back( + "ebddec3dcaf18063e45a76ebeac39af85a1adc2818881ccce48c106288f59883" + "65cca2b4b1d7f037322da46840f42bebdcbc7193838d426e101087d8cea03aaf" + "f743d573eb4f4e9a71a2c884390769a6503874125d194bee8d46a3a0d5e4fcf2" + "8ff8465887d8e9df771d70157e75df3642b331d2778ceb32ceba868640171ab7" + "a5d22eede1ee44"); + v.push_back( + "26d87ec70b57691e3bb359633d3ddba17f029d62cdfe977f5fd42274d79b444a" + "32494d1c01e9f72d03cce78c806df96e93ea78da3a054209924ed765edc4d570" + "f66168dc25ee3114e4017e387440349c8f0a94804761c3055f88e4fda2a49b86" + "0b1486a9609095f6250f268b6a4d1aecc03a505632ebf0b9dc22d0755a736faf" + "7ad7000858b5864b"); + v.push_back( + "3880f5cc2d08fa70ef44b1f263fcf534d062a298c1bd5ee2eee8c3265806c4ce" + "50b004f3a1fc1fa5b024aaac7f528c023c8181f67c6e1c357425dc4d573bd46b" + "93a542afa3a19bdb140a2ce666e1a01f5c4d2dcd681fa9f5839b797813c39473" + "8d5ee4971386c12c7c117d17c7bec324b760aa30cda9ab2aa850284ba6fa9794" + "6f710f02449d1883c6"); + v.push_back( + "3317d2f452105dd3f4a96f9257af8285a80be58066b50f6f54bd633749b49f6a" + "b9d57d45652d2ae852a2f6940cd5ec3159dd7f333358b12f502325df38843508" + "faf7e246352d201280babd90b14fbf7722641c3601d0e458474439973c611bb5" + "502fd0eb3078f87124ca7e1a016fcb6cfeff65f6a565985aca7122cfa8c5a11d" + "a0cb47797c5132333179"); + v.push_back( + "f2c5c955d0224e784a46b9125f8fef8a5e1271e145eb08bbbd07ca8e1cfc848c" + "ef14fa3b36221ac62006403dbb7f7d77958ccc54a8566c837858b809f3e310ac" + "e8ca682515bc655d2a397cab238a663b464d511f02dc5d033dad4cb5e0e519e9" + "4a54b62a3896e460ec70e5716b5921bf8396aa86a60123e6287e34570bb01bdc" + "602e113670bf498af2ff10"); + v.push_back( + "180e275205691a83630cf4b0c7b80e6df8fad6ef1c23ba8013d2f09aef7abade" + "1827f23af230de90676240b4b3b0673f8afdea0327330055041741f65560d903" + "48de696d34ca80dfe8afae582fe4879d4594b80e9408fb53e800e01ca58552b9" + "05c365e7f1416e51c080f517d6bbd30e64ae1535d59decdc76c6624d737868f4" + "9f2f719da39ba1344d59eab9"); + v.push_back( + "c517a84e4631a7f65ace170d1e5c2fdb259841535d88da323e68c0883e6af7b0" + "41cfe05908815a5a9d1b14fa712c2c16fadcf1ca54d3aa954d411240df331b2a" + "ebdfb65aced84d0b8aace56ec0aa7c13ec7d75ca883b6bcf6db74c9e98463c48" + "4a8262684f29910373430651f90ecffe18b072170e61ee58de20e2a6ff67b3ab" + "00fccbb80af943f20b56b98107"); + v.push_back( + "d1a56a5ee990e02b84b5862fde62f69ec07567be2d7ccb769a461c4989d11fdd" + "a6c945d942fb8b2da795ed97e43a5b7dbdde7f8fd2ff7154544336d5c50fb738" + "0341e660d4898c7fbc39b2b782f28defac6873523c7c1de8e52c65e4395c686b" + "a483c35a220b0416d46357a063fa4c33fa9c52d5c207a1304ae141c791e62ba6" + "a7374ed922b8dd94079b72b69302"); + v.push_back( + "4720b88d6bfb1ab43958e26827730d852d9ec30173ebd0fe0d273edcece2e788" + "558984cd9306fe5978086a5cb6d37975755d2a3daeb16f99a8a11544b8247a8b" + "7ed5587afc5bea1daf85dcea5703c5905cf56ae7cc76408ccabb8fcc25cacc5f" + "f456db3f62fa559c45b9c71505eb5073df1f10fc4c9060843f0cd68bbb4e8edf" + "b48d0fd81d9c21e53b28a2aae4f7ba"); + v.push_back( + "f4639b511db9e092823d47d2947efacbaae0e5b912dec3b284d2350b9262f3a5" + "1796a0cd9f8bc5a65879d6578ec24a060e293100c2e12ad82d5b2a0e9d229658" + "58030e7cdf2ab3562bfa8ac084c6e8237aa22f54b94c4e92d69f22169ced6c85" + "a293f5e16bfc326153bf629cdd6393675c6627cd949cd367eef02e0f54779f4d" + "5210197698e4754a5fe490a3a7521c1c"); + v.push_back( + "3d9e7a860a718565e3670c29079ce80e381969fea91017cfd5952e0d8a4a79bb" + "08e2cd1e26161f30ee03a24891d1bfa8c212861b51618d07429fb48000ff87ef" + "09c6fca526567777e9c076d58a642d5c521b1caa5fb0fb3a4b8982dc14a44473" + "2b72b239b8f01fc8ba8ee86b3013b5d3e98a92b2aeaecd4879fca5d5e9e0bd88" + "0dbfffa6f96f94f3998812aac6a714f331"); + v.push_back( + "4d9bf551d7fd531e7482e2ec875c0651b0bcc6caa738f7497befd11e67ae0e03" + "6c9d7ae4301cc3c7906f0d0e1ed4738753f414f9b3cd9b8a71176e325c4c74ce" + "020680ecbfb146889597f5b40487e93f974cd866817fb9fb24c7c7c16177e6e1" + "20bfe349e83aa82ba40e59e917565788658a2b254f25cf99bc65070b3794cea2" + "259eb10e42bb54852cba3110baa773dcd70c"); + v.push_back( + "b91f65ab5bc059bfa5b43b6ebae243b1c46826f3da061338b5af02b2da76bb5e" + "bad2b426de3c3134a633499c7c36a120369727cb48a0c6cbab0acecdda137057" + "159aa117a5d687c4286868f561a272e0c18966b2fec3e55d75abea818ce2d339" + "e26adc005c2658493fe06271ad0cc33fcb25065e6a2a286af45a518aee5e2532" + "f81ec9256f93ff2d0d41c9b9a2efdb1a2af899"); + v.push_back( + "736f6e387acb9acbee026a6080f8a9eb8dbb5d7c54ac7053ce75dd184b2cb7b9" + "42e22a3497419ddb3a04cf9e4eb9340a1a6f9474c06ee1dcfc8513979fee1fc4" + "768087617fd424f4d65f54782c787a1d2de6efc81534343e855f20b3f3589027" + "a5436201eee747d45b9b8375e4294d72ab6a52e04dfbb2914db92ee58f134b02" + "6527ed52d4f794459e02a43a17b0d51ea69bd7f3"); + v.push_back( + "9242d3eb31d26d923b99d66954cfade94f25a18912e6356810b63b971ae74bb5" + "3bc58b3c01424208ea1e0b1499936daea27e63d904f9ed65fdf69de40780a302" + "7b2e89d94bdf214f585472613ce328f628f4f0d56217dfb53db5f7a07f54c8d7" + "1db16e27de7cdb8d23988837b49b65c12f1771d979e8b192c9f4a16b8d9fba91" + "7bcf74ce5a82aac2075608ba6c2d485fa59864b9de"); + v.push_back( + "5da68704f4b592d41f08aca08f62d85e2e2466e5f3be010315d11d113db674c4" + "b98764a509a2f5aacc7ae72c9deff2bcc42810b47f64d429b35745b9efff0b18" + "c58653461e968aaa3c2c7fc455bc5771a8f10cd184be831040df767201ab8d32" + "cb9a58c89afbebecb524502c9b940c1b838f8361bbcde90d272715017f67609e" + "a39b20fac985332d82daaa023999e3f8bfa5f3758bb8"); + v.push_back( + "71ea2af9c8ac2e5ae44a176662882e01027ca3cdb41ec2c6785606a07d7231cd" + "4a2bded7155c2feef3d44d8fd42afa73265cef826f6e03aa761c5c51d5b1f129" + "ddc27503ff50d9c2d748322df4b13dd5cdc7d46381528ab22b79b0049011e4d2" + "e57fe2735e0d58d8d56e92c75dbeac8c76c4239d7f3f24fb56697593b3e4afa6" + "671d5bbc96c079a1c154fe20212ade67b05d49ceaa7a84"); + v.push_back( + "1d133170582fa4bff59a21953ebbc01bc202d43cd79c083d1f5c02fa15a43a0f" + "519e36acb710bdabac880f04bc003800641c2487930de9c03c0e0deb347fa815" + "efca0a38c6c5de694db698743bc955581f6a945deec4ae988ef7cdf40498b777" + "96ddea3fae0ea844891ab751c7ee20917c5a4af53cd4ebd82170078f41ada279" + "5e6eea17593fa90cbf5290a1095e299fc7f507f360f187cd"); + v.push_back( + "5ec4ac45d48fc15c72471d795066bdf8e99a483d5fdd599511b9cdc408de7c06" + "16491b73924d0266da34a495331a935c4b8884f57d7ad8cce4cbe586875aa524" + "82215ed39d7626cce55d50349c7767981c8bd6890f132a196184247343566fc9" + "72b86fe3c5369d6a6519e9f07942f0522b77ad01c751dcf7defe31e471a0ec00" + "963765dd8518144a3b8c3c978ad108056516a25dbe3092e73c"); + v.push_back( + "0d5e74b78290c689f2b3cfea45fc9b6a84c822639cd438a7f05c07c374adced4" + "2cdc12d2a9233a4ffe80307efc1ac13cb04300e165f8d90dd01c0ea955e76573" + "32c6e86ad6b43e78ba4c13c675aed83192d8427866fb6484e6a3071b2369a46f" + "ba9005f31232da7ffec7952f831aaaddf63e225263531c2cf387f8cc14fa856c" + "8795137142c3a52ffa69b8e30ebc88ce3bbc227597bcc8dddd89"); + v.push_back( + "a0fe36f983259921dc2fa7d89002b3066241d63bfc2448caf7e10522a35562be" + "0bfedc3dce49cfce2e614a04d4c64cfc0ab898873a7fc26928dc1927c009d12f" + "6f9b7a278205d3d0057604f4ac746f8b9287c3bc6b929832bf253b6586192ac4" + "3fdd29ba585dbd9059aab9c6ff6000a7867c67fec1457b733f6b620881166b8f" + "ed92bc8d84f0426002e7be7fcd6ee0abf3755e2babfe5636ca0b37"); + v.push_back( + "1d29b6d8eca793bb801becf90b7d7de215b17618ec32340da4bac707cdbb58b9" + "51d5036ec02e105d83b5960e2a72002d19b7fa8e1128cc7c5049ed1f76b82a59" + "eac6ed09e56eb73d9ade38a6739f0e07155afa6ec0d9f5cf13c4b30f5f9a465b" + "162a9c3ba04b5a0b3363c2a63f13f2a3b57c590ec6aa7f64f4dcf7f1582d0ca1" + "57eb3b3e53b20e306b1f24e9bda87397d413f01b453ceffeca1fb1e7"); + v.push_back( + "6a2860c110cd0fc5a19bcaafcd30762ee10242d34739638e716bd89fd537ea4d" + "c630e6f85d1bd88a25ad3892ca554c232c9830bd56980c9f08d378d28f7fa6fa" + "7df4fcbf6ad98b1adfff3ec1f63310e50f920c99a5200b8e64c2c2ca249399a1" + "49942261f737d5d72da949e914c024d57c4b639cb89990fed2b38a37e5bcd24d" + "17ca12dfcd36ce04691fd03c32f6ed5de2a2191ed7c826375ba81f78d0"); + v.push_back( + "7132aa291ddc9210c60dbe7eb3c19f9053f2dd74742cf57fdc5df98312adbf47" + "10a73245de4a0c3b24e21ab8b466a77ae29d15500d5142555ef3088cbccbe685" + "ed9119a10755148f0b9f0dbcf02b2b9bcadc8517c88346ea4e78285e9cbab122" + "f824cc18faf53b742a87c008bb6aa47eed8e1c8709b8c2b9adb4cc4f07fb423e" + "5830a8e503ab4f7945a2a02ab0a019b65d4fd71dc364d07bdc6e637990e3"); + v.push_back( + "3e664da330f2c6007bff0d5101d88288aaacd3c07913c09e871cce16e55a39fd" + "e1ce4db6b8379977c46cce08983ca686778afe0a77a41baf447854b9aa286c39" + "8c2b83c95a127b053101b6799c1638e5efd67273b2618df6ec0b96d8d040e8c1" + "ee01a99b9b5c8fe63fea2f749e6c90d31f6fae4e1469ac09884c4fe1a8539acb" + "313f42c941224a0e79c059e18affc2bcb6724975c436f7bf949ebdd8aef51c"); + v.push_back( + "7a6ea63a271eb49470f5ce77519ed61ae9b2f1be07a96855726bc3df1d0723af" + "3a703fdfc2e739c9d31d25814daf661a23558b50982e66ee37ad880f5c8f11c8" + "130fac8a5d0250583700d5a324894fae6d61993f6bf9327214f8674649f355b2" + "3fd634940b2c467973a839e659169c773119919f5b81ee171edb2e5f6940d755" + "1f9e5a70625d9ea88711ad0ed8ab2da720ad358bef954456cb2d5636425717c2"); + v.push_back( + "c5106bbda114168c449172e49590c7eeb827fa4e1a2a7a87a3c1f721a9047d0c" + "0a50fbf244731be1b7eb1a2ef30f5ae846a9f38f0df44f32af61b68dbdcd0226" + "e741dfb6ef81a2503691af5e4b3171f48c59ba4ef91eba344b5b697f261df7bb" + "bb734ca6e6daebaa4a179feb17002823281b8534d55a6531c59305f6e3fd3fa6" + "3b747bcf0deb654c392a02fe687a269effb1238f38bcaea6b208b221c45fe7fb" + "e7"); + v.push_back( + "597716a5ebeebc4bf524c15518816f0b5dcda39cc833c3d66b6368ce39f3fd02" + "ceba8d12072bfe6137c68d3acd50c849873150928b320b4fbc31c1456679ea1d" + "0acaeeabf666d1f1bad3e6b9312c5cbdecf9b799d3e30b0316bed5f41245107b" + "693366accc8b2bcef2a6be54209ffabc0bb6f93377abdcd57d1b25a89e046f16" + "d8fd00f99d1c0cd247aafa72234386ae484510c084ee609f08aad32a005a0a57" + "10cb"); + v.push_back( + "0771ffe789f4135704b6970b617bae41666bc9a6939d47bd04282e140d5a861c" + "44cf05e0aa57190f5b02e298f1431265a365d29e3127d6fccd86ec0df600e26b" + "cdda2d8f487d2e4b38fbb20f1667591f9b5730930788f2691b9ee1564829d1ad" + "a15fffc53e785e0c5e5dd11705a5a71e390ca66f4a592785be188fefe89b4bd0" + "85b2024b22a210cb7f4a71c2ad215f082ec63746c7367c22aedb5601f513d9f1" + "ffc1f3"); + v.push_back( + "be6556c94313739c115895a7bad2b620c0708e24f0390daa55521c31d2c6782a" + "cf41156271238885c367a57c72b4fe999c160e804ad58d8e565edbce14a2dd90" + "e443eb80626b3eab9d7ab75d6f8a062d7ca89b7af8eb292c98eaf87ad1dfd0db" + "103d1bb6188bd7e7a63502153cf3ce23d43b60c5782602bac8ad92fb2324f5a7" + "9453898c5de18415639ecc5c7974d3077f76fc1df5b956723bb19a624d7ea3ec" + "13ba3d86"); + v.push_back( + "4bc33729f14cd2f1dc2ff459abee8f6860dda1062845e4adab78b53c835d106b" + "dfa35dd9e77219eaef403d4e80488ca6bd1c93dd76ef9d543fbb7c8904dccc5f" + "71509a6214f73d0f4e467c3e038ea639b29e7fc442ee29f57117740576188ada" + "15a739827c647a46b0271817ab235c023c30c90f2115e5c90cd8501e7b286962" + "fc66ffc3fe7e8978746168314908a41998bd83a1eeffda9d714b864f4d490fde" + "b9c7a6edfa"); + v.push_back( + "ab12faea205b3d3a803cf6cb32b9698c32301a1e7f7c6c23a20174c95e98b7c3" + "cfe93fffb3c970face8f5751312a261741141b948d777b8a2ea286fe69fc8ac8" + "4d34116a4674bb09a1a0b6af90a748e511749de4697908f4acb22be08e96ebc5" + "8ab1690acf73914286c198a2b57f1dd70ea8a52325d3045b8bdfe9a097925215" + "26b7564a2a5fcd01e291f1f8894017ce7d3e8a5dba15332fb410fcfc8d62195a" + "48a9e7c86fc4"); + v.push_back( + "7d421e59a567af70594757a49809a9c22e07fe14061090b9a041875bb77933de" + "ae36c823a9b47044fa0599187c75426b6b5ed94982ab1af7882d9e952eca399e" + "e80a8903c4bc8ebe7a0fb035b6b26a2a013536e57fa9c94b16f8c2753c9dd79f" + "b568f638966b06da81ce87cd77ac0793b7a36c45b8687c995bf4414d28289dbe" + "e977e77bf05d931b4feaa359a397ca41be529910077c8d498e0e8fb06e8e660c" + "c6ebf07b77a02f"); + v.push_back( + "0c18ab727725d62fd3a2714b7185c09faca130438eff1675b38beca7f93a6962" + "d7b98cb300ea33067a2035cdd694348784aa2eda2f16c731eca119a050d3b3ce" + "7d5c0fd6c234354a1da98c0642451922f670984d035f8c6f35031d6188bbeb31" + "a95e99e21b26f6eb5e2af3c7f8eea426357b3b5f83e0029f4c4732bca366c9aa" + "625748297f039327c276cd8d9c9bf692a47af098aa50ca97b99961bef8bc2a7a" + "802e0b8cfdb84319"); + v.push_back( + "92d5909d18a8b2b9971cd1627b461e98a74ba377186a6a9df5bd133635250b30" + "0abccb2254cacb775df6d99f7c7d0952653c28e6909b9f9a45adce691f7adc1a" + "fffcd9b06e49f775364cc2c62825b9c1a86089080e26b57e732aac98d80d009b" + "fe50df01b95205aa07ed8ec5c873da3b92d00d53af825aa64b3c634c5ece40bf" + "f152c331222d3453fd92e0ca17cef19ecb96a6eed4961b627aca48b12fecd091" + "754f770d52ba861546"); + v.push_back( + "802f22e4a388e874927fef24c797408254e03910bab5bf372320207f8067f2b1" + "ea543917d4a27df89f5bf936ba12e04302bde23119533d0976beca9e20cc16b4" + "dbf17a2ddc44b66aba76c61ad59d5e90de02a88327ead0a8b75463a1a68e307a" + "6e2e53ecc1986274b9ee80bc9f3140671d5285bc5fb57b281042a8978a117590" + "0c6073fd7bd740122956602c1aa773dd2896674d0a6beab24454b107f7c847ac" + "b31a0d332b4dfc5e3f2f"); + v.push_back( + "3844fe65db11c92fb90bf15e2e0cd216b5b5be91604baf3b84a0ca480e41ecfa" + "ca3709b32f8c6e8761406a635b88eec91e075c48799a16ca08f295d9766d7447" + "5c47f3f2a274eae8a6ee1d191a7f37ee413a4bf42cad52acd5564a651715ae42" + "ac2cddd52f819c692ecdef52ecb763270322cdca7bd5aef71428fa73e844568b" + "96b43c89bf1ed42a0abf209ffad0eeec286c6f141e8af073ba4adfbbdeda2537" + "52ae36c9957dfc905b4c49"); + v.push_back( + "329377f7bf3c8d74991a7d61b0cf39baff5d485d79751b0d5ad017d23bec570f" + "b19810105bab79ab5acb102ab972165224d4ec888ec7de5148077fa9c1bb6820" + "e0d91ae4e2591a21fec2f820606ce4bafc1e377f8dc3a5bd1a9e2772a57abccd" + "0b757164d768872c91d02789545ab5b203f688d71dd08522a3fd2f5bcd7df507" + "aebf1ca27ddff0a82afb7aa9c180008f49d1325adf97d047e77238fc75f56356" + "de4e87d8c961575c9f6362c9"); + v.push_back( + "f7f269929b0d71ea8eef7120e55ccba691c582dd534692abef35c0fe9dec7dae" + "973cd9702e5ad420d278fe0e653fdcb22fdcb63148109ec7e94f2d0750b28157" + "dd1764376ae10fdb0a4aef3b304bd82793e0595f941226a2d72abbc929f53134" + "dc495b0d65ced409914f94c2523f3dfbbdeeac84ae247ab5d1b9ea33dce1a808" + "885a55be1f3683b46f4be73d9b62eec2585f690056858dfc427aabf591cd2767" + "24885bcd4c00b93bb51fb7484d"); + v.push_back( + "ac022309aa2c4d7fb628255b8b7fb4c3e3ae64b1cb65e0de711a6def1653d95d" + "8088871cb8905fe8ae76423604988a8f77589f3f776dc1e4b30dbe9dd262b218" + "7db02518a132d219bd1a06ebac13132b5164b6c420b37dd2ccee7d69b3b7fa12" + "e54f0a53b853d490a68379ea1fa2d79762830ffb71bf86aab506b51f85c4b6a4" + "1b69325c7d0c7aa85b93b7144489d213e8f33dbb879fce22849865337b620b15" + "5cb2d2d36a68832889e30194d36d"); + v.push_back( + "d009c2b78a8f02e5e5dbb586ef71fc324b375092e15913ca1a5bfd22d516baad" + "b96867bee3562e77c4a4852344a1a76c30728be5e22400b4cc41711f66754c24" + "6a520498d8c24f0205b9c873748dbeb67fe1ad099ad04cf89f4b517f0aa48113" + "6d9f6de2d727df01c6aa4099da59d4382b51e25fd47c33d9842c32b62331e507" + "94bfe8b61b3ba9de1b8b704779c6d65edff3af00f121ab4a7ea384edabe47c6d" + "0098a48991f387ca4444135ec59d46"); + v.push_back( + "c00bab36cce69899817d1425016d222d7303197ed3e3fdcac744705e7f178a1a" + "c745968900f69299163e19b3161f3e0a4cc55aa2e4e71e0ee6ac427d1f4d14e0" + "63f68d303ddfbb18118335cfa7a6a90d99c38319ee76f7a884846a9e0b68030b" + "f28e78bfbd56359b9368842814da42b04cb0e307d5d846dc22f049147bae31b9" + "a956d17676a8cc348dafa3cabc2007a30e730e3894dddf9999fb8819086311f0" + "703e141613ed6dcd7af8510e2dc435b0"); + v.push_back( + "c9789152a9fc29698d49ed95f09bd11b75f18a8c5615a73dbe54ae5e550027fd" + "0ae6a8b60667040c1b12de3d1ee3f6bf061c78c951a3210effc912e19f482dd4" + "de152063c588c44903bc11761706fd935afa040df085b08144d83d0dde32b46a" + "b52f4fae98ac116c7ff11d7f553450c2e37b9c5f0b1dd9e0b8640a24cba6f2a5" + "246c41f197f46e3dc8a29131c79bef3351c6e277a0a34442274d546ccd058891" + "277473d668420f121750d19cd684267405"); + v.push_back( + "06a15a0731ce52557e368bcbaa11ef3399299e36fb9f2eda6e5726907c1d29c5" + "c6fc581405ba48c7e2e522206a8f128d7c1c939d1132a00bd7d6366aa82724e9" + "68964eb2e373563f607dfa649590dcf5589114df69da5547fef8d1604cc4c6de" + "1ed5783c8746918a4dd31168d6bc8784cd0c769206bd803d6ca8557b66748770" + "402b075ef44b38157d4c0da7c6281725a2065d087b1f7b23455fa673bdeeba45" + "b983311c44eabe9ef4b7bde3420ae9881863"); + v.push_back( + "d08aacef2d7a41aec09473bd8a44f628e15addb7b9e5b77a1e09c8ab4942f379" + "a0bfcb324d580b774666f18ae78dd36710824ff12393f059068fe4b559c53662" + "c2b0e6c69e23785c8f32554e837ec1714bee902e60737b639dd933af4f68cb9d" + "7de77e1f3b28e5b122891afce62b79acd5b1ab4ba411662cc77d806449e69c5a" + "45a143b742d98ac84a0826d68433b9b700ace6cd472ba2d58a90847f42ce9c43" + "f38ffc017db4bf40450b2eee1f4594dc740c0f"); + v.push_back( + "6a6058b0a498b7ea76a93c646eb9b8629f0cba4a0c726420c5f67ba9b0412cad" + "e356abdf0a4fb94384bad32ce0d5dd9e23dcaae1d6f28ff8683616b30f139289" + "0c67b3a2c04b360893b801f127e527e4da82e239f4c878da13f4a4f1c76db071" + "90e77ec123995168102fb274434a2d1e12913b9b5cbab4aacaad2bd89d88b3ca" + "2b8e60dacf7c22c9379097ff60880f552e320ca3b571994f52534470feee2b39" + "e0dadb5cd88257a3e459a4cc6f12f17b8d54e1bb"); + v.push_back( + "adeced01fc5671531cbb45679f5ddd42b3a95151677b6125aaf6f5e8f82fbaba" + "a5ecf7c3552c2458587224f0042870f178f5fca5465250e75d71352e652eeed2" + "3cdb7f915f5ebb44099b6db116ca1be45530ac8ed32b7f161d60ed4397ad3d7d" + "649ae6bf75ca5bec891d8e595605be9764f3a03965e1fe0eaffbf212e3df4f0f" + "a35e08ff9d0091e6d4ac4748edfe43b611085a6ffec163014655fdd839fd9e81" + "b63b1fa8cae4ec335ec343289758e389a79ceedfae"); + v.push_back( + "d014592f3a83ba40af366f137c674724916c3cdd3f6cf9d4c5c7c8d6d51ebf26" + "e315e2c12b3546be56fb52382904046ecbd2f5b883aa4ff473de6f0c26ab862c" + "3fa34bf3d880cc1911ce39a4088c6617c179dc5faf68a2c488bbde12d67b50f7" + "3abcfab0e3b062e68c95363e11f5f1de8ec36ed01ea21442518089045df67d34" + "6135283ad5b3fff80cf57f20876849f6db9fa139728358415a90610f69ec720f" + "c92d8234e3e122551e9df2c644c4a2c4e3734d07de8e"); + v.push_back( + "c0d0c37838873ba8757d6e41b409605043bc1635edcd731219587676d94217e9" + "f0ab44b71de25000661ce7303b7015f45e6eaa7b7ebef92b8f4a34c902c908d2" + "172185505fa33aca5a41be83079316cdfdd430fc2c45f505f85d867e6d516f7e" + "1bf19c001d9f43018968aab65ec031b3801399231c83ec9e622dab5629922a6b" + "424cab938c135ff7310501c2c02971bfd2f577e25904d1a618baf0859f77f4e8" + "b1d0cde9544e95ec52ff710c0672fdb3d891feeea2b017"); + v.push_back( + "7022e7f00902219ba97baa0e940e8ac7727f58955aa068c29680fac4a16bcd81" + "2c03eeb5adbcfe867a7f7c6b5d89f4641adb9173b76a1a8438866f9b4f640ce2" + "aedf5f1080c890bcf515b4be4e3e512352f1e5323c62ec46cb73f3d71be8235f" + "ee55a154763f7c3f9aeb61ffd28f4cd93d3310f608e2133586bf1ab3f102de96" + "f64c68a4668de8acb2a76a7ce0cddddc8fa3df5e9d230823da16ed9ebb402d36" + "e38e6e018795e5a71517ecab5f9ca472b9ced8ff69d2d195"); + v.push_back( + "acaf4baf3681ab865ab9abfae41697141ead9d5e98523c2e0e1eeb6373dd1540" + "5242a3393611e19b693cabaa4e45ac866cc66663a6e898dc73095a4132d43fb7" + "8ff7166724f06562fc6c546c78f2d5087467fcfb780478ec871ac38d9516c2f6" + "2bdb66c00218747e959b24f1f1795fafe39ee4109a1f84e3f82e96436a3f8e2c" + "74ef1a665b0daaa459c7a80757b52c905e2fb4e30c4a3f882e87bce35d70e292" + "5a1671205c28c89886a49e045e31434abaab4a7aed077ff22c"); + v.push_back( + "84cb6ec8a2da4f6c3b15edf77f9af9e44e13d67acc17b24bd4c7a33980f37050" + "c0301ba3aa15ad92efe842cd3ebd3636cf945bb1f199fe0682037b9dacf86f16" + "2dadabfa625239c37f8b8db9901df0e618ff56fa62a57499f7ba83baebc085ea" + "f3dda850835520344a67e09419368d81012168e5de5ea45158397af9a5c6a165" + "7b26f319b66f816cd2c28996547d697e8df2bb163ccb9dda4d6691dffd102a13" + "667ab9cde60ffbfb872187d9c425a7f67c1d9fffff9276ed0aeb"); + v.push_back( + "6a52c9bbbba454c14540b2be58230d78ecbeb391646a0c6fcce2f789086a7836" + "4b81ae85d5396d7cfa8b46bda41e3083ec5cf7b4c47dc601c8a697df52f557de" + "fca248506dbebab25657f5a561d09625b7f4b2f0119a12beeac087efc9d350a7" + "35c35d2431c1da7dda99befb17f41a3dc4da0f00bb95366be128538ce27763d8" + "1f832fe3c1d4efc07b5b08ad8dc9e65fb5e48546664e18cb2d3bb3fe1f56fa7a" + "ae718c5e3bbdeaf70e15023f6a25b72a2d177fcfd04211d40664fe"); + v.push_back( + "c3c4d3b31f1f5f9538923df3478c84fffaef411520a542da9a220ee4132eabb9" + "d718b5076fb2f985485e8ba058330aed27ddfd3afa3db34aa60301088caec3d0" + "053828c0c2bc87e2e61db5ea5a29f62fdad9c8b5fc5063ec4ee865e5b2e35fac" + "0c7a835d5f57a1b1079833c25fc38fcb14311c54f8a3bd251bca19342d69e578" + "5f9c2e43cf189d421c76c8e8db925d70fa0fae5ee3a28c4047c23a2b8a167ce5" + "3f35ced33bec822b88b06f41558c47d4fed1bfa3e21eb060df4d8ba1"); + v.push_back( + "8d55e92136992ba23856c1aea109766fc44772477efc932b3194af2265e433ed" + "77d63b44d2a1cff2e8680eff120a430fe012f0f09c6201d546e13ad46fc4ce91" + "0eab27bb1569879abed2d9c37fae9f1267c2216ec5debcb20d4de58461a621e6" + "ce8946899de81c0add44d35e27b7982a97f2a5e6314901caebe41dbba35f48bc" + "9244ca6dca2bdde7306435892f287036df088633a070c2e385815ab3e2bfc1a4" + "7c05a5b9fe0e80dd6e38e4713a70c8f82bd32475eea8400c7bc67f59cf"); + v.push_back( + "5016284e20362610fa05ca9d789cad25f6d43263787e7e085476764ce4a8908c" + "e99b262b375e9d106170b1bec1f473d5e777e0c1896533040e39c8c1465e0790" + "7ef5860e14e4d8310013e35f12090e0bfc687474b1f15f3dd2033a0edac52461" + "02da4deec7e188c3517d84d9c2a0a4497a4c5f82a30f1ba009e45ee6eb3ab436" + "8c720ea6feee428ffd2c4cc52debb8d634a64176572c72368f94a66689f23f8a" + "01218f532117af5a8060d140e7ca435a92882fcb5630ebe14a4805f1dc83"); + v.push_back( + "05456ec59b8d41bbd736727976b96b38c43827f9e16169be673ff37870c2ecd5" + "f0d1ea1a136be4cc7b047a02a4421d484fd2a12ece418e42ee391a13a0b1df5a" + "0162b29ab70d3fe3e04ba6ab26b37d62b7cf05a5e2f033611bf970b8e1f30e19" + "8e483e740fa9618c1e8677e07b61296b94a9787a68fba622d7653b5568f4a862" + "8025939b0f74389ea8fced6098c065bf2a869fd8e07d705eadb53006be2abb71" + "6a3114ceb0236d7e916f037cb954cf977720855d12be76d900ca124a2a66bb"); + v.push_back( + "eb6f60b83fcee77060ff346aaf6ec34d82a8af469947d3b5074cde8eb26566eb" + "1fa039bcc707738df1e95869bd827c246e88436f0614d9834ead5392ef376105" + "c4a9f370071cdeaaff6ca0f18b74c3a48d19a717253c49bd9009ccbfdd5728a0" + "8b7d112a2ed8dbafbbb46d7a75dc9a05e09bfde1a0a92d74a51887f9d123d789" + "6e9f9d0057b660ed7d55454c069d3c5260411db4cdc67e7b74f680d7ac4b9dcc" + "2f8baf72e15e6b3cafebcdf449a6436ed2c398b675f79c644747c57553bf7ea2"); + v.push_back( + "187a88e88514f6c4157c1ba40b442baae1ae563a6c989277443b12a219aa484c" + "b9fa8adbb9a29d429f50155321b15664926317477079c7060dfdaa84c1d74bba" + "78892c34e6f21ad35208d2ae622012401696bff5cd57b6485944b3db7b9071fa" + "5f57fbfb1085d91bb9cff5808d662cdc6c8157249478262c44b7fbc397ed42a4" + "977b202e817717bfccc9f0467294062313f7705251ed09573f16d23429361fad" + "a259dfb300369c4198f07341b38e84d02cdb74af5de6aab1fc2026208ea7c418" + "c0"); + v.push_back( + "be31bc96606d0fab007e5caeded2f1c9f747c759777e9b6eef962bed49e45a1d" + "4fc993e279d024915e600865ecb087b960584be18c41114d3c43f92169b9e0e1" + "f85a0ebcd4e196376ccdc920e66103cd3b1c58407d0aafd0e003c4e341a1dadd" + "b9f4faba974362a32f35db83384b05ae8e3322d728893861afd8b1c940de5a17" + "f691e763ce4969b6d94f67fb4a0235d100225bd8602f291388f0ca4a568748ad" + "0d6040f1262eac2aede6cd27419bb78a394c1ffad72c262be8c3f9d9619d633e" + "51d0"); + v.push_back( + "4d83d85ca838b4518588f2a90228a4dd18f14dd5b4c012d26298a97d848abbd8" + "25d221d02cceb6e8c701b4ad00e1dee4889b5c533e4bb60f1f41a4a61ee5478b" + "e2c1b1016c30345afd7a5253668260515e70751f22c8b4022d7fe4877d7bbce9" + "0b46531507dd3e89549e7fd58ea28f4cb23d33662bd003c1345ba94cc4b06867" + "f778957901a8c441bee0f3b12e16463a51f7e50690356971dd73a686a49fda1e" + "ae46c9d54fba262811d698025d0ee053f1c58591c3bb3cbde69de0b31549ef5b" + "69cf10"); + v.push_back( + "cdeb07d36dc5f9a1cd717a9e9cca37a2ce93caa298eee63571f7d6c5fde2a11c" + "666cf53cf2dcb41ca2ea2319e7230ca68e38c647905928713a13982bf47fe33d" + "7095ebd50b2df976208920a43eb2e29b942f32467403c45cea18bf44e0f6aeb1" + "55b48a8e5c471fec972a9d62f7ae093d2758f0aaec7ca50cb4725bfa219f1a3a" + "46ad6bde7361f445f86b94d66b8ece080e56c510250693a5d0ea0ae87b442186" + "0b853bcf0381eae4f1bf7c5c0472a93ad18407bc88475ab8560d344a921d3e86" + "a02da397"); + v.push_back( + "a598fad52852c5d51ae3b10528fc1f722e21d44fbd42ae5acdf20e85a28532e6" + "46a223d27fd907bfd38eb8bb75175636892f8242877aab89e8c0824d368f3339" + "ce7a82aa4e5af6db1f3b588a4d667a00f67bee37cfd2724dde06d2909fb9e58d" + "892f4cfd2c4ca85acdf8256f5458b030a6bda151154ff2e6d7a8da90b54a2884" + "c8a99fab5a4ac211ff23dc0975f4f592fd1b6b9dc7783bdcd2d4ca4e68d2902f" + "2013e122cb62e2bff6b0a98ec55ba25837e21f1cfe67739b568d43e6413dab2b" + "d1dc471e5a"); + v.push_back( + "17b68c74c9fe4926e8102070916a4e381b9fe25f5973c9bd4b04ce25749fc189" + "31f37a65a356d3f5e5a1ef125d546f4f0ea797c15fb2efea6fbfcc5739c56469" + "3d47adeb12dcb3d98a2830719b13247792cb2491dca159a28138c6cff925aca4" + "2f4fdb02e73fbd508ec49b25c60703a7595a3e8f44b155b371d525e48e7e5dc8" + "4ac7b17c52bf5e526a67e7187234a2f19f57c548c70fc0b27183df73ffa53fa5" + "8b658034c896fa791ae9a7fd2620f5e46ce84c842a6e60e9324ae4db224ffc87" + "d9617cb85ca2"); + v.push_back( + "b9e4267ea39e1de1fed0579f93bb351007c9f8fcdd811053fae33f09e2753d74" + "28f04e1a9efcd45ea701a5d87a35b3afb2e6b65365dee6ead0bbb611b7797b21" + "2ac688653f542e604a39df277f12514ddfee3b4e27b98395c2cd97a203f1f115" + "3c50327965770802ec2c9783edc428271762b275471e7ac65ac36523df28b0d7" + "e6e6ccc7674268a132a63411fc82c0738dbb68af003b769a0bf9e6587b36476c" + "b465350fee13f88ea355d47ffac7b0f964f4139db11b7642cb8d75fe1bc74d85" + "9b6d9e884f75ac"); + v.push_back( + "8ca704fe7208fe5f9c23110c0b3b4eee0ef632cae82bda68d8db2436ad409aa0" + "5cf159223586e1e6d8bdae9f316ea786809fbe7fe81ec61c61552d3a83cd6bea" + "f652d1263862664df6aae321d0323440430f400f291c3efbe5d5c690b0cc6b0b" + "f871b3933befb40bc870e2ee1ebb68025a2dcc11b68daadef6be29b5f21e4403" + "74301bde1e80dcfade4c9d681480e65ec494a6af48df232c3d51447b9d06be71" + "4949249c44c43cf73ed13ef0d533e770284e51369d94ae241a5fb2f163893071" + "b2b4c118aeaf9eae"); + v.push_back( + "4fd8dd01012bb4df82bf42e0683f998e6f52dd9c5617bae33f867d6c0b69798c" + "ead8179346d70acc941abbbdd26e3229d5651361d2252c72ff22db2938d06ff6" + "fc29a42fdf800ae967d06479bc7bbb8e71f40b1190a4b7189ffc9a7096cdb76d" + "40aec424e1388e1eb7ef4ac3b34f3f089da8fda7d1927f5d775c0b2801d22dd1" + "265c973158f640cec93edfed06dc80b20ef8c496b98289d54d46ccd205951cbb" + "0f4e7daeb866b60bacb483411e4382b6f04d472843186bd0e31fbaa93e5c901e" + "c028efafeb45fc551a"); + v.push_back( + "e9ee1b22b04b321a5fdd8301627011f583887d77560fb0f35552e207561f81e3" + "8ac58a0d0aeaf832d1ee72d913720d01f75574e9a321864fe95f4d0d8f0b8db9" + "7649a53e71e940aede5c40b4b9105daa42a6fb2811b61209247534cbaf830b07" + "abe338d75d2f5f4eb1c3cf151e9edabe2c8d5f6fff08fac1495ef48160b100d3" + "0dcb0676700bcceb28723a29980ab0766a93abb8cb3d1963007db8458ed99b68" + "9d2a7c28c788743c80e8c1239b20982c81dadd0eed6740c65fbc4ef15c7b5569" + "cb9fc997c6550a34b3b2"); + v.push_back( + "ec01e3a60964360f7f23ab0b22e021815765ad706f242265ebc19a2bb9e4eac9" + "4393952dcf61aae47682671a10f9165f0b20adf83a6706bfbdcf04c6faba6114" + "653a35584267267873291c6fe7ff5f7695243143421509502c8875aafa9e9afe" + "5be5ef2c851c7f35d69be5d3896000ccdbbfab5c238bb34d607cfe2d55d74888" + "0545b4aa7ca61137992925189025c62654b1f20d49c3ccd75aa73ce99cd7258d" + "abedd6480a9f5185531fc0118beb68cc0a9cd182f6973287cf9252e12be5b619" + "f15c25b65c71b7a316ebfd"); + v.push_back( + "db51a2f84704b78414093aa93708ec5e78573595c6e3a16c9e15744fa0f98ec7" + "8a1b3ed1e16f9717c01f6cab1bff0d56367ffc516c2e33261074935e0735ccf0" + "d018744b4d28450f9a4db0dcf7ff504d3183aa967f76a507357948da9018fc38" + "f150db53e2df6cea14466f03792f8bc11bdb5266dd6d508cde9e12ff04305c02" + "95de29de19d491ad86e766774bb517e7e65befb1c5e2c267f013e235d8483e17" + "7214f89978b4cdc81aa7eff8b39f2825ad3a1b6ac1424e30edd49b067d770f16" + "e74dd7a9c3af2ad74289a676"); + v.push_back( + "00e40f30ae3746edad0f5dd03d0e640933cf3d1694804c1e1ed6399ac36611d4" + "05196ee48f129344a8512feda16a354517871322bd5d9c6a1b592933eab53192" + "3efb393ffb23d9109cbe1075cebfa5fb917b40df028a621460ff6783c798792c" + "b1d9635b5a6f84ec13918fa302924649b5c7fcb1f7007f0d2f06e9cfd7c27491" + "e565a96c68a0c3644f92cd8f38857258c33801c5d537a83dfe583cba59d7eec7" + "e394199c0a2660a62fabe3ed2099d57f315a6cd8de1a4ade29d977f15d65759c" + "ff433e5ac0c182aef3761163e1"); + v.push_back( + "3c5ea24d0d9b618294a263f062b2414a722be4eb10dfc346a6ec3b821d7396eb" + "a61cd6ef33618b04cd087a811f299d4606820227f16000d7c839062b96d3e3f5" + "9cd1a082448d13fc8f56b3fa7fb5f66d0350aa3b72dd7c165d590282f7da2e12" + "cfe9e60e1796122bb8c2d40fdc2997af634b9c6b127a893dfb3467909378300d" + "b3da911be1d7b616bb8e0572433e65527e15d936500a2c60e9f9909dcf22ab5e" + "4b6700f0238c205b4a813626fac3d945bab2637fb08203044a73d20c9a3fcf7c" + "3fc4eb7807c3276dd5f73ce89597"); + v.push_back( + "9271aeeebfac46f4de85df78f1bfd36136aa8905e15835c9e1941176f71e3aa5" + "b1b131843d40479735e23e182a2bd71f66f6149dccb7ed8c16469079dc8590bb" + "f165374951785f4531f7e7361de62f936cfb23a2b5bdf186632e7042a0dd451f" + "dc9b7208f923f3a5f250ae590ec348c63a16c3aacaf7379f53b5dd4152dcd40d" + "23e683e2156e64c592ffc07e2cd6bbeebef4dd590b2f6b2bcbf08fcd111c079f" + "5c4033adb6c17574f8756ecd87be27eff1d7c8e8d0324438d59ae171d5a17128" + "fbcb5533d921bd044a2038a5046b33"); + v.push_back( + "4e3e533d5bcb15793d1b9d0468aaee801f32fdb486b11027183553a09ddbee82" + "13924296f2815dc61577297459e834bf1c7a53f87d43782209e589b8295219ba" + "7073a8fff18ad647fdb474fa39e1faa69911bf83438d5f64fe52f38ce6a991f2" + "5812c8f548de7bf2fdea7e9b4782beb4011d3567184c817521a2ba0ebad75b89" + "2f7f8e35d68b099827a1b08a84ec5e8125651d6f260295684d0ab1011a9209d2" + "bdeb75128bf5364774d7df91e0746b7b08bda9185035f4f226e7d0a1946fcaa9" + "c607a66b185d8546aac2800e85b74e67"); + v.push_back( + "b5d89fa2d94531093365d1259cc6fe8827fea48e6374c8b9a8c4d2209c280fa5" + "c44958a1847222a692a59e6aa2696e6cdc8a543dd89b0ce03bc293b4e78d6ef4" + "8e1839694ccd5c65661143095c705b07e3ced84a0f5959114dd89deb956ab3fa" + "c8130eb4a878278205b801ae41a29e34146192308c4e759b374757b0c3b00319" + "bce92a1b95a4d2ee179fd6714ff96155d26f693a5bc973f84ac8b3b91e392627" + "6297532d98b46992a3f104c08100bf1671c43134bac280c617da711e90a01001" + "37525375ebb12802a428885ae7fce6514a"); + v.push_back( + "40e3d8048fc10650cb8a7fc2e7113e26dec34f9ca2d5129cd10a8e8e44d113d6" + "1ee48c7d003e19fd307fc6debd70feb30243f298c510ccc4418355ce143066f0" + "67ad7c6de7288c3080e7ad46a23c8d34deb55a43e652fe90444ad3c57d3ec1e1" + "c489d63ef915a24bc74a7925a0a7b1e1523f21ca8fee78df24e3d0a68d001342" + "3db97c280799a0618229c0f2c167289a891e5c8d6661ab21285951c31710e3b5" + "fe55f6347fe16d9b40507948a59252efeb616df83e5c098b07d0a7247cd371da" + "ff0e50491c582503fd89f79ba94d6af9ed76"); + v.push_back( + "1fa444de01dd3901e2b4684e3d7a799ffa02d85afd35fb30fe4c9d672837bee6" + "dd8a3b8608b4bb5e589220ad5a854f46b46e41c6d57ad124a46beab4169ff69f" + "ee7e3838a6165e19dad8eb5d7bf53d4edd3cd2769daf219510a02fdd2afe0c0e" + "1da3cd30fcd1aa88b68965586f07a25a1720fbd90a096ea30fc8e945e3637d78" + "57c8a9c0ab4154ffb2000e57b5f9adfa4e4eaf8065bc3c2b2e75f49596332558" + "8785a6ce417dcddffd299873b15dcccca128d63cd4eeeadb64cda28099a9ad7c" + "80d34844901f26b88b00b9aafeb2f90286d29d"); + v.push_back( + "fde0a0d9d813983bd1f55cf778a003a2023b34a555322ab280584537bc6bdd84" + "4d22a7d6066c18da83ec09f3d8d5a1aab4be0d5ce19b436052f6e259a4b49017" + "a1f47f1fe2bf115d5bc8599fb216351c60dd6b1bedb2e6f4dcadf424b833501b" + "6f099cbfad9e2290680fb69c25032b42a6274f7cb9b5c5950401354838a45f7c" + "b77b95bf54718e2f3d3d9fb91eb2311903980277396398d9736d8e92fd838594" + "ac8a537c6c529db5a8a4f89290e6ba6f20ac0e5ed6fef40901d0e0e8e3e50299" + "0811f9acaae555dd54eb1bcd96b513e2fe751bec"); + v.push_back( + "9f8e0caec87858599f5ab29bff86da78a841a918a023a111098687ecdf274761" + "2d3f3809d9ca400b878bd4f92c43a1004f1c17c7f19a3cd1ce449bd2b23aff55" + "1623c37dd8c0be56bf3fd857b500c2b9f9ccea62481944090a3cf3b6ee81d9af" + "8eeb60f65ef150f9fa4d3ed6ce4762d3d4f174ee8ccd460c25cafac0ea5ec8a6" + "a4b2f9e8c0520cb7061155e532cb65f188b01e4b9086db951f504b060c296b32" + "6b3fc1c590498ecce594f828f4a10ea416675720ae505295d38a791bd0e93f42" + "8448a8f4c1fc0af53604a9e8255384d29ae5c334e2"); + v.push_back( + "33d1e683a4c97ee6bbaa5f9df1a88cb53b7f3c157b6045d70a56fda0ccbd3a1f" + "a1f049cd564da072b53f415bf5fb843771c1d2551fd075d33377362b2f7c0645" + "f9723123d11975991db8a2b518f02e2c7c30342a044754290bae2c77496d755e" + "5981f12e6b0a0174280b958bf11ed628a9062775993ced04bf752ea8d165e3ac" + "2177d7cd1b9371c44efa98f0b3e68602a839d384eec007979f46429dafb138cb" + "c231ad928a9f65f7d66fac77416395e8f1debaaf76ec2e4e03e8674102cd26f6" + "14739f3ec9f949033df1fb97e87c2326d65aef94ed5f"); + v.push_back( + "180048f09d0b480887af7fd548a85abf605440c1ddde6afe4c30c30670233f7b" + "f928f43b4681f59279ebbda5e8f8f2a1abefdee129e18ac60f9224e90b38b0aa" + "bd01308e0a27f41b6fb2ee07ee176ec9048c5fe33c3f7c791469c81f30e28170" + "585b9f3e7e3c8c2e9d74370cb4518f13bf2dee048cbd98ffa32d85e43bcc64a6" + "26b40efb51ce712925fdd6fee006dc68b88004a81549d2121986dd1966084cd6" + "54a7c6686b3bae32afbd9625e09344e85cf9611ea08dfce835a2e5b3726e69ae" + "8a76a97db60fcc539944ba4b1e8449e4d9802ae99fae86"); + v.push_back( + "13c0bc2f5eb887cd90eae426143764cf82b3545998c386007cca871890912217" + "aa143ac4ed4ddb5a7495b704aa4de18419b8664b15bc26cfc6596a4d2ae408f9" + "8b47a566476d5802d594ba84c2f538def9d016661f6404bb2337a3932a24f6e3" + "0073a6c9c274b940c62c727242e24466084a3ea336365d71ea8fa6499c0ea8d5" + "9eea505f1126b99c795023c4963aa0d99323d0391e8701110edf551b2d3799e1" + "063ca443f1add162156e445502ca1a052fe70c289838593b58839fc63de128a0" + "3e2bbf389e22ae0cf957fd03315ee407b096cc1cfd92dee6"); + v.push_back( + "6f1eb607d679efef065df08987a1174aab41bdac8aece7726dfa65805d6fff5b" + "3d17a672d96b770dc32165f144f0f7324822a5c87563b7cd9e37a742ae83ef24" + "5d09006d91576f435a03476f509ea2936636232f66aa7f6cdf1ac187bbd1fcb8" + "e20f8791866e60ed96c73374c12ac16795e999b891c64507d2dbd97e5fc29fac" + "750ad27f2937cbcd29fdafccf27ab22453834d475f6186eaf975a36fad5c8bd6" + "1c21da554e1ded46c4c39765dcf5c8f5ccfb49b6a4dc562c919d0c7d8940ec53" + "6ab2448ec3c9a9c8b0e8fd4870cad9de2577c7b0c38563f355"); + v.push_back( + "dcdd993c94d3acbc555f464871a32c5da6f13b3d5bbc3e34429705e8ad2e7639" + "3fdd96a69a94acb652f5dc3c120d41187e9aa919669f727c4868013b0cb6acc1" + "65c1b7706c52248e15c3bf81eb6c147619467945c7c48fa14a73e7c3d5bec917" + "06c567145342a026c9d97eff97ec672c5debb9df1a998083b0b0081d65c517b3" + "e5634c95e347e781aa30ca1c8af815e2e494d844e847fdcb41622894a518dc36" + "571123a40bfdbe8c4f4cff44d83c61dd9dcd24c464c53b395edb31efee9f3aa0" + "80e87cdc3d22d613ae84a53c9249c32c96f9a3bc4629bb126a70"); + v.push_back( + "49971f9823e63c3a72574d977953329e813b22a8387cd13f56d8ea77a5d1a8a2" + "0012632d1d8732bbcb9f756b9675aab5db927beacab7ca263e5718b8dfa7b2ee" + "d9a91bf5ed163b16139d45f7b8cc7e3f7bdda6202106f67dfb23b7c315ee3e17" + "a09d466b1e6b13e7c7428184a979f5358667b4fa8bd40bcc8ea46058db44587a" + "85377ac46bf155136c09ac58cb6c27f28e17028c91e7e8f74d5b500e56293b31" + "6974f02b9d9ea205d9b6ac4cfb74eb8eb0c944577fd2f41316368307beab3e32" + "7bf7dbaa0a4428836ec4e895dea635234abeaf113ceeadac33c7a3"); + v.push_back( + "c57a9cc958cee983599b04fe694f15fb470fcbc53e4bfcc00a27351b12d5d243" + "4444253ad4184e87b81b738922ffd7ff1dc1e54f39c5518b49fb8fe50d63e393" + "5f99e4bd125e8dc0ba8a17fd62de709339a43fabe15cf86d96a54010112170c3" + "40cfac4132182eed7301402bc7c8276089dec38488af145cb6222525894658f0" + "3501204b7a66aba0be1b557b28a2f652d66f7313ed825ecc4d8596c1be7420d4" + "425b86a1a90a5b7f30d0f24e0d1aae0eb619ca457a71699e44be612a4011c597" + "ee80b94d5507e429d7fc6af22579cd6ad642723b05ef169fade526fb"); + v.push_back( + "0568a672cd1ecbaa947045b712e2ac27995392fbef8f9488f79803cbee561c21" + "2287f080eca95adb5ba42739d78e3ba667f06045d87850d3a0499358649caa25" + "7ad29f1a9c511e7054db20554d15cbb55ff854afa45cae475c729cea72ede953" + "522031865bc02b95589ed4d9841c552a8cc94904a93ed09ed77222f6c1781950" + "56be59bc4e96a815adf534e6b466fb47e262ff79c803c157a21b6e2269c2e0ab" + "eb494113cd868d8466e82d4b2f6a28b73645853d96bc9242515d803e33294848" + "d3fe42fdff68da53c03491636beede47ff1399dd3d54a5e914d55d7adf"); + v.push_back( + "3f19f61a4cd085796731ac9f85a75a8bce77031932c31762d87d8b8d07b8bd19" + "ff78d6b7d1bd1e87f3a4f41aad03b6c4d17a6cbc86be55f7c8b88ada047bb04f" + "8d49f1c34bcf81cc0f3389ad01a758fc7eeb0072aa9ad1481992bfdde82e438e" + "75590a4423832dfbe3756e2229ea873bc3606e6d72174cb2163bf40b5d49c810" + "09dab85ecc03e311351bbf96e32c030a2b276a7698cb25bc2c967acb3213161a" + "1fdde7d912cd6a804490f8056c47da1333f6e35c41e749c2c23919cb9af5eec5" + "652e6e072b034fb1682e9aaa194a9c0bd456ea0b008d14dbce37967a7a8e"); + v.push_back( + "705f98f632d99d3651793825c38dc4deda56c59eac539da6a0159c83131cf8ab" + "6f2ee0c3b74111fde351f7aa1a8c500a0cecab17c212d2c58ca09eae608c8eef" + "c922b9902ef8d6832f799ba48c3c28aa702b3242107edeba01daafe424406a38" + "22965056cfe8783455a671e93b1e2eae2321364f1871471c82124df33bc09e1b" + "52882bd7e1c4c7d0b2f3dd4a28c2a002a43246768af0700f9659de99d62167be" + "93177aabf19d678e79e9c726ac510d94e74873eda99620a3961930cd91937c88" + "a06d8153d64fd60da7ca38cf26d1d4f04a0df273f52127c53fdc593f0f8df9"); + v.push_back( + "ea6f8e977c954657b45f25480ff42c36c7a10c77caa26eb1c907062e24fbca5a" + "ebc65cacca0de10abea8c78322f08672e13d8ac16996eca1aa17402eaea4c1cc" + "6c800b22dc18cb8d620192d74bac02c07b5cfa61e513c7f28b7e29b9700e0e44" + "2720bf4c669d4995da19d19f841d9eb68cc74153592591e3bf059ef616b95305" + "aa453b32fe99a91afb35bd482cf2b7aa42702837a53be3c38883d2963020e347" + "556f841254ec6b85854485fe8c520b05f2ea67a9bf3981555c20991e2bacd4db" + "5b418228b6002d8d41c025cb472bf5443aaa885974a408ea7f2e3f932c600deb"); + v.push_back( + "408190134ed06556811b1af808ab2d986aff152a28de2c41a2207c0ccc18125a" + "c20f48384de89ea7c80cda1da14e60cc1599943646b4c0082bbcda2d9fa55a13" + "e9df2934edf15eb4fd41f25fa3dd706ab6de522ed351b106321e494e7a27d5f7" + "caf44ec6fadf1122d227eefc0f57aefc140d2c63d07dcbfd65790b1099745ed0" + "42cfd1548242076b98e616b76ff0d53db5179df8dd62c06a36a8b9e95a671e2a" + "9b9dd3fb187a31ae5828d218ec5851913e0b52e2532bd4bf9e7b349f32de2b6d" + "5d3cdf9f372d49617b6220c93c05962327e99a0480488443349f0fd54c1860f7" + "c8"); + v.push_back( + "5f9e5c6f38573a85010a9d84d33f29c057003b2645e3ea6f72cbc7af95d197ce" + "6a06b13fea81722853e6991791b8b15091cd066f5ed913592ed3d3af5370d39b" + "a22beeb2a582a414b16824b77e194a094c2afdcc09aa73ce36f4943cca5ae32c" + "5017dc398801dd92a47382d9327c9f6cffd38ca4167cd836f7855fc5ff048d8e" + "fba378cdde224905a0425e6b1de061fc951c5e624a5153b008ad41160a710b3f" + "f2081748d5e02deb9f841f4fc6cf4a15153dd4fe874fd447482696283e79ee0e" + "6bc8c1c0409baa5ab02c5209c319e3169b2476149c0c6e541c6197ca46e004ee" + "f533"); + v.push_back( + "218c6b3508aec69574f2b5039b30b942b72a8349d05f48ff945bbbe5c8957d5a" + "6199492a6bf54bab821c9377e2edfa4c908384664d2c80112d5e805d66e0a551" + "b941021be17dd20bd825bea9a3b6afb1b8c605805b3bda58750f03ea5c953a69" + "8494b425d8980c69f34d1c3f6b5866e8717031152a127215c256e08873c21b0f" + "5cc85875d0f7c94601659150c04cd5fe5d381ba29983a2d94fcd3a65a94c53c7" + "279cd000dddd4253d8cff8d7f6ace10247fe3bc30d63ba4bb54f557b3d22a392" + "4369430d71ab37b701e9500bda70b5a643704858beed4726a889b6c9c9158419" + "4c68f1"); + v.push_back( + "dac26aa7273fc25d6e044c79fc2bfa46e59892a42bbca59a86826c91e76ab03e" + "4bd9f7c0b5f08d1931d88b36ea77d94f7ba67cd4f1d3086e529427201119096a" + "e066ae6f170940830ed7900de7bb9d66e09788287403a4ecc93c6da975d2fb08" + "e918840a236c15f5d3a8f7375c2eeebbf6f01a6e7f29ca2b8d42df158414c320" + "777433663c59fdcd1f39ca68e3473db721be7ce8c6dba5fddc024f94fedb286b" + "0477581d451313ca8c737484daf60d67f9b2d56d4bcc271f7e9ae958c7f258ef" + "bc74d25753e0516f28282461941bf2dcc7dd8c7df6173b89760cefcac0719024" + "3ff863fb"); + v.push_back( + "c46e6512e6797cc7a54254a1b26b2de29aa83d6c4b1ea5a2786fbcec38827062" + "5b12635eae39e1fba013f8a65219421bca8b52a8ddfd431cda60299bdf160734" + "d5a7450ec79620058522702174ae451b9bfa7c4a455fbbee3e1d048c7d4bac51" + "31018228f137c8e130440c7059b4f15eaa34ce872a851a16ce86f982df78a00b" + "e4d564da2003a450ddee9ab43ea876b8b4b65c84f0b39265fd5456417afb5bc5" + "4997c986e66fc222f2123ba5e719c4d6b9a177b188277df384f1125821cf19d5" + "248cef0be183ccdc84ac194506f740ed2188b2689ea4c9236a9e9e3a2fff85b6" + "af4e9b49a3"); + v.push_back( + "1ccd4d278d67b65cf2564ecd4de1b55fe07adc80e1f735fe2f08ea53fd397732" + "3689122c29c798957abaff6aba09bdcbf661d77f4dc8913ab1fe2bef38846166" + "e3834785e7105d746484eff8c656af5d8c7854abc1c62b7fadb65521dc6f793d" + "978bda9838eb3800417d32e8a24d8c8cb1d18a5de6ca79d9e1b0ff9aa25e6218" + "fe944cf18666fecc1e31334b390260dbe0997539e1b02f6366b2aea4f4a21efe" + "04f4b97568fcb39e59919d5ebac6543d5d0f48fc66b923c34aac377dc95c2032" + "9b837b6ed5e8d9a3d2089cd0d8f025658006ff41cbdaccca618822ca590ab155" + "253f8bc1c7f5"); + v.push_back( + "9875209588395ee3c9fdd793fd48717cc84c8c3ea622b2ccc4a1be4448e6034b" + "7810569855255031f10be5ffd714b05f9ce01972d712d40abf03d4d0ce175813" + "a7a668f761324996093fc2aa5912f7fc2abdadd8775d2b4d9ad4922162933814" + "60ed8f6db3d641d1525f4242c348bbfe504c704f215dc461de51b5c75c1aae96" + "7936963848f16c673eca5e78dfd47eb19001d52d1bcf96c98956dad5ddf594a5" + "da757e7ca35f2f69803b784e66ac5a58b75c228b8266ec592505e5d1ca87d812" + "25738855f15bc0914677e81593fd409e77d159f8a908f67788de9eb06c556154" + "7aada96c47c535"); + v.push_back( + "40c90e375e366f3756d89091eb3eed9fe0fbfc5638700af4617d358812bac531" + "24a2205dd6756456787d49cd6a35e302479a0992288f47532e4ea7ab62fc5ad5" + "adc690a5d9a446f7e035ad4641bd8dae83946aee3338ec984ccb5cc633e1409f" + "2531eeffe05532a8b0062ba99454c9aeabf8ecb94db195af7032bfebc22912f4" + "9d39330add47ff8fa5720612d697f0b602738930e060a1bb214efc5e292224cf" + "34e29deaea6b1b1ff847e94ecc997325ac38df61db45d82bf0e74a664d2fe085" + "c20b04c39e90d6a170b68d2f1d373f00c731c524456ada73d659aaac9df3191a" + "7a3865083343fc13"); + v.push_back( + "e8800d82e072210ca6d7fa2472028974780b76aad4bcb9ad362422dd05ae3232" + "668251d164daa375a43b26a38cce28dbeb3dee1a4a579f70d0fe7febb29b5ece" + "8aa836e050fb3d188c63aa9c3c0da6c717d86458a6096b5effceb964efdec703" + "5960c09ccd10dea3c5f1c7f9f478d5887ebbe2e15c5ff85dbacbc444bb951c4e" + "ec7abecb89ed80187e409e2972ffe1a5f01562af109f2cf09471cf72cf83a3bb" + "8f4e2ef38ed0e326b698296394e5b2718a5000c01425708e8ad0461e62462d88" + "19c2377f13ab1be2c7c9f33dc06fe23cad27b87569f2ce2e56e4b2c60c7b1b3d" + "370841d89ebdc1f192"); + v.push_back( + "796d6d1447d5b7e8c55cd8b2f8b7010db39f27565f907e3fc0e464ea2d4bb52b" + "37f10e7c6dcfc59231b9cdee12c32aeb4adbc42b86e86eb6defb5b69e6ca75e1" + "f4d0dae3e124e5a1b8b6697f7e10b0403f1f0a5ff848eef3752837a9ba17780f" + "16a9a709188a8d5b89a2fa74adb2e651163b1c2b3d261e225c9158dcd9eb7ac3" + "d6704cee290cdff6bcb3cb90cee030aa0d19d4693655c3c30ac6fc06d2ae3778" + "7c47126d57ed9a6bef5f8a6c56859aefc08755739a95aac57a4dd916a92ba9f3" + "afbf969df8085949615033365c751a9a3e1a18cee98a69d22e64009bebf83071" + "69b6c61de0617ecfafdf"); + v.push_back( + "4f9057183566153cf337b07c3f5556006de54c56b2a1e5326c07aaeabd1886ec" + "6f1641358925db232b2f0dbf75229c796a7395b2f934c1f99090bec1123f3c84" + "1b1cb3c5b1ec42ed5408f2940f0c48a9470b852c46d6557853d459cecd2c32bb" + "cd8ee21fa11e385eef0857cba4d8545a61b52a484cdd779db4739fbc7aa9860d" + "cabe0488b98fa0b60c3f7d6153db279000a52ffb573dab37d2ab1896a90e5deb" + "7ac6bbe56239085c325d83a917dc6e8a448425b718c2356b9f3066163555ec44" + "4f372e184e02c8c4c69b1c1c2ae2b51e45b98f73d933d18750968945ca85d6bb" + "b22014b4c4015262e3c40d"); + v.push_back( + "79dcca7d8b81a61359e4aece21f3df7b99518ce70bd2f57a18bab5e7114af2ad" + "d0a0cea7f319d69f231f060e0a539d9a23fb3e95451ce8c6340cfb09edf931df" + "84203a39226dd9eb278f11b691ef612585b973daab373e65d11325898badf673" + "2100371fd759960fa8fec373268421d28bffdb9b12a430b92fe4b07566ca0c89" + "e616e49f8fc75ccd9cdc66db820d7c02e109aa5ed86b89770262918a518f90a2" + "292f6b68d68ae03992e4259a17a23c84ec2a417f082b5abf3a26e44d2278ecb8" + "ba9456965303a75f25394d1aaf5544590e74b14d8a4cc4050be2b0ebcfe4d2db" + "6b12a02c68a3bcdda70301f3"); + v.push_back( + "848755dc31e25e9a42f9ec12d847d19f292c14c162c9aba49e972cb123b58b8e" + "57bb263a923929833373858594ff52dbc298dbbc078599194e4c07b0e5fc1e10" + "808bbacdb6e93c72b333685cf961f28eb0d5a395c63266b01f130d25db384b35" + "6e5da6d01042fc2359581b89c63b3bb2d1ce897fbc9e83fe85d9666cb60e6a8c" + "657f70caad5387b8a045bf91095606802c8424ea8ac52ef29386dc46183378a5" + "fcb2cb927428b8c070f1c42aafd3bc70ca25437807696a46873cfeb7b80ba2eb" + "c3c4272443d445e46343a1465253a9eebd532a0d1d2c18264b91ff45159f2454" + "04ae9335f2af55c802772426b4"); + v.push_back( + "ecaa6e999ef355a0768730edb835db411829a3764f79d764bb5682af6d00f51b" + "313e017b83fffe2e332cd4a3de0a81d6a52084d5748346a1f81eb9b183ff6d93" + "d05edc00e938d001c90872dfe234e8dd085f639af168af4a07e18f1c56ca6c7c" + "1addffc4a70eb4660666dda0321636c3f83479ad3b64e23d749620413a2ecdcc" + "52ad4e6e63f2b817ce99c15b5d2da3792721d7158297cce65e0c04fe810d7e24" + "34b969e4c7892b3840623e153576356e9a696fd9e7a801c25de621a7849da3f9" + "9158d3d09bf039f43c510c8ffb00fa3e9a3c12d2c8062dd25b8dabe53d8581e3" + "0427e81c3dfc2d455352487e1255"); + v.push_back( + "23a3fe80e3636313fdf922a1359514d9f31775e1adf24285e8001c04dbce866d" + "f055edf25b506e18953492a173ba5aa0c1ec758123406a97025ba9b6b7a97eb1" + "4734424d1a7841ec0eaeba0051d6e9734263bea1af9895a3b8c83d8c854da2ae" + "7832bdd7c285b73f8113c3821cced38b3656b4e6369a9f8327cd368f04128f1d" + "78b6b4260f55995277feffa15e34532cd0306c1f47354667c17018ee012a791a" + "f2dbbc7afc92c388008c601740cccbbe66f1eb06ea657e9d478066c2bd2093ab" + "62cd94abadc002722f50968e8acf361658fc64f50685a5b1b004888b3b4f64a4" + "ddb67bec7e4ac64c9ee8deeda896b9"); + v.push_back( + "758f3567cd992228386a1c01930f7c52a9dcce28fdc1aaa54b0fed97d9a54f1d" + "f805f31bac12d559e90a2063cd7df8311a148f6904f78c5440f75e49877c0c08" + "55d59c7f7ee52837e6ef3e54a568a7b38a0d5b896e298c8e46a56d24d8cabda8" + "aeff85a622a3e7c87483ba921f34156defd185f608e2241224286e38121a162c" + "2ba7604f68484717196f6628861a948180e8f06c6cc1ec66d032cf8d16da039c" + "d74277cde31e535bc1692a44046e16881c954af3cd91dc49b443a3680e4bc42a" + "954a46ebd1368b1398edd7580f935514b15c7fbfa9b40048a35122283af731f5" + "e460aa85b66e65f49a9d158699bd2870"); + v.push_back( + "fe511e86971cea2b6af91b2afa898d9b067fa71780790bb409189f5debe719f4" + "05e16acf7c4306a6e6ac5cd535290efe088943b9e6c5d25bfc508023c1b105d2" + "0d57252fee8cdbddb4d34a6ec2f72e8d55be55afcafd2e922ab8c31888bec4e8" + "16d04f0b2cd23df6e04720969c5152b3563c6da37e4608554cc7b8715bc10aba" + "6a2e3b6fbcd35408df0dd73a9076bfad32b741fcdb0edfb563b3f753508b9b26" + "f0a91673255f9bcda2b9a120f6bfa0632b6551ca517d846a747b66ebda1b2170" + "891ece94c19ce8bf682cc94afdf0053fba4e4f0530935c07cdd6f879c999a8c4" + "328ef6d3e0a37974a230ada83910604337"); + v.push_back( + "a6024f5b959698c0de45f4f29e1803f99dc8112989c536e5a1337e281bc856ff" + "721e986de183d7b0ea9eb61166830ae5d6d6bc857dc833ff189b52889b8e2bd3" + "f35b4937624d9b36dc5f19db44f0772508029784c7dac9568d28609058bc437e" + "2f79f95b12307d8a8fb042d7fd6ee910a9e8df609ede3283f958ba918a9925a0" + "b1d0f9f9f232062315f28a52cbd60e71c09d83e0f6600f508f0ae8ad7642c080" + "ffc618fcd2314e26f67f1529342569f6df37017f7e3b2dac32ad88d56d175ab2" + "2205ee7e3ee94720d76933a21132e110fefbb0689a3adbaa4c685f43652136d0" + "9b3a359b5c671e38f11915cb5612db2ae294"); + v.push_back( + "af6de0e227bd78494acb559ddf34d8a7d55a03912384831be21c38376f39cda8" + "a864aff7a48aed758f6bdf777779a669068a75ce82a06f6b3325c855ed83daf5" + "513a078a61f7dc6c1622a633367e5f3a33e765c8ec5d8d54f48494006fdbf892" + "2063e5340013e312871b7f8f8e5ea439c0d4cb78e2f19dd11f010729b692c65d" + "d0d347f0ce53de9d849224666ea2f6487f1c6f953e8f9dbfd3d6de291c3e9d04" + "5e633cfd83c89d2f2327d0b2f31f72ac1604a3db1febc5f22cad081532780472" + "10cc2894582c251a014c652e3951593e70e52a5d7451be8924b64f85c8247dab" + "6268d24710b39fc1c07b4ac829fbda34ed79b5"); + v.push_back( + "d7314e8b1ff82100b8f5870da62b61c31ab37ace9e6a7b6f7d294571523783c1" + "fdedcbc00dd487dd6f848c34aab493507d07071b5eb59d1a2346068c7f356755" + "fbde3d2cab67514f8c3a12d6ff9f96a977a9ac9263491bd33122a904da5386b9" + "43d35a6ba383932df07f259b6b45f69e9b27b4ca124fb3ae143d709853eed866" + "90bc2754d5f8865c355a44b5279d8eb31cdc00f7407fb5f5b34edc57fc7ace94" + "3565da2222dc80632ccf42f2f125ceb19714ea964c2e50603c9f8960c3f27c2e" + "d0e18a559931c4352bd7422109a28c5e145003f55c9b7c664fdc985168868950" + "396eaf6fefc7b73d815c1aca721d7c67da632925"); + v.push_back( + "2928b55c0e4d0f5cb4b60af59e9a702e3d616a8cf427c8bb03981fb8c29026d8" + "f7d89161f36c11654f9a5e8ccb703595a58d671ecdc22c6a784abe363158682b" + "e4643002a7da5c9d268a30ea9a8d4cc24f562ab59f55c2b43af7dbcecc7e5ebe" + "7494e82d74145a1e7d442125eb0431c5ea0939b27afa47f8ca97849f341f7076" + "60c7fbe49b7a0712fbcb6f7562ae2961425f27c7779c7534ecdeb8047ff3cb89" + "a25159f3e1cefe42f9ef16426241f2c4d62c11d7ac43c4500dfcd184436bb4ef" + "33260366f875230f26d81613c334dbda4736ba9d1d2966502914ec01bbe72d88" + "5606ec11da7a2cb01b29d35eebedbb0ecc73ed6c35"); + v.push_back( + "fd993f50e8a68c7b2c7f87511ce65b93c0aa94dcbdf2c9cca93816f0f3b2ab34" + "c62c586fc507b4900a34cf9d0517e0fe10a89d154c5419c1f5e38de00e8834fe" + "3dc1032abdeb10729a81655a69a12856a78ca6e12110580de879b086fd660872" + "6541cfa9616326bdd36064bc0d1e5f9c93b41278bff6a13b2494b81e238c0c45" + "aea1b07d855e8f3fe1478e373bd9d3957cf8a5e5b9003386793d994c7c575cff" + "2322e2428cbbaa4f47560316ae3354a7478842ff7cc5dcbacb6e871e72b36f06" + "d63a9aaeb9044cfb7974afdc238a5816f537dcf33ee40b4e1a5eb3cff2402b46" + "d548264e133008d284f11b7e4e450bc3c5ff9f79b9c4"); + v.push_back( + "8df21892f5fc303b0de4adef1970186db6fe71bb3ea3094922e13afcfabf1d0b" + "e009f36d6f6310c5f9fda51f1a946507a055b645c296370440e5e83d8e906a2f" + "b51f2b42de8856a81a4f28a73a8825c68ea08e5e366730bce8047011cb7d6d9b" + "e8c6f4211308fad21856284d5bc47d199988e0abf5badf8693ceeed0a2d98e8a" + "e94b7775a42925edb1f697ffbd8e806af23145054a85e071819cca4cd4887529" + "0ca65e5ee72a9a54ff9f19c10ef4adaf8d04c9a9afcc73853fc128bbebc61f78" + "702787c966ca6e1b1a0e4dab646acdfcd3c6bf3e5cfbec5ebe3e06c8abaa1de5" + "6e48421d87c46b5c78030afcafd91f27e7d7c85eb4872b"); + v.push_back( + "48ec6ec520f8e593d7b3f653eb15553de246723b81a6d0c3221aaa42a37420fb" + "a98a23796338dff5f845dce6d5a449be5ecc1887356619270461087e08d05fb6" + "0433a83d7bd00c002b09ea210b428965124b9b27d9105a71c826c1a2491cfd60" + "e4cfa86c2da0c7100a8dc1c3f2f94b280d54e01e043acf0e966200d9fa8a41da" + "f3b9382820786c75cadbb8841a1b2be5b6cbeb64878e4a231ae063a99b4e2308" + "960ef0c8e2a16bb3545cc43bdf171493fb89a84f47e7973dc60cf75aeeca71e0" + "a7ebe17d161d4fb9fe009941cc438f16a5bae6c99fcad08cac486eb2a48060b0" + "23d8730bf1d82fe60a2f036e6f52a5bff95f43bbe088933f"); + v.push_back( + "f4d84ed3e564c102600a795eaa9b1eaf4ad12f1a4deca1d042a0a2750ddf6201" + "db03073d8bf553cb9dde48a1b0083827a609f7242b86584cc180964ae794b12c" + "e55661e00e36a6ba4dbc389e6a5a85f1b45df9af7ead1b0a54db56e68639b9d4" + "38a91504e82c35d40c7bc7e048a53ac0b04accd0dadf4ac9884b0ca0e3cb5ba4" + "336e3581be4c4760a553823ffa283a1120d4e145af56a59f2533903650f0b9e9" + "ad9fe2e8a3c3c3dd03a1fcb709032c8835324839c735b0c051d0cbd8b5d86761" + "7c11023432e4bd275d3d0eb98a0b6cf58071a5b712922f2bc751ac7c2588c447" + "444cde2f37a8ea5ec126425bf517e0d17c9e2999f52fee14b3"); + v.push_back( + "2ccea21bac9c2b70d3923309cbf2d7cb7abd1fcc8b8b002688870a80029c6239" + "7350c3c898194e5deea360bb963d26d485cb7963f8167586976ec0556950b2e8" + "6135f4a2800991ce8473bfd44a3c5e937a48b5e355ba5141bccf2131a83988d9" + "d2a9e8e7635a956105b3512c05ef708139ced51d7a4e204c12d8a49a21e8dc6d" + "e2629a2fd092326885d9f218745fe09f6d91fb6afce250a30a63689534b6be1f" + "26899ffa3767d835cf586aa47776700f94241bc999b1e3deefe188f37ff734f5" + "f16ee6a00914323dc7b8a143c9137cdcc5cd08ae9566f04bb2941532674c97df" + "f6ffa5ce3405ef8e5d27ec403114253dd6394c0167d72a0044c5"); + v.push_back( + "2b681c6398aee63bf862770341648bbcd31d7de7903c5903fe3d9469311320bb" + "24d914f2af0cdca199c97214c7c679dc32a2800ba484a03c010ea6be3bb9f2c8" + "7e30a98b606050b8a3f297f12b8f92caaeceb3e844652115934874e0a1ab093a" + "73d759b53f6a6c3096940dd22c2bb96ce6820a7b9c6d71a208de9892aa6a7209" + "b0fff56a0cafea52b952cdd6f5752cff3309d448800b4e4c878aa595595b56b1" + "2b83fcd6ca89520c7da664e449d7b4438fc455888aad5de0fad9a06eed14afd3" + "513b5ebbffe01775549b701181bd26370764f56eba52fdb24286ad1ac0f5418a" + "7c429f7dfc7f3168437fa8eed7a2ed7c723a485e4c3ed14dea2e07"); + v.push_back( + "aadfd505a89f4aade2c3018258a7e039401b1fc6a7f3d87910dddbb880d372ec" + "8a13c70d92245de5b8e5f9a285c33b99dc82fa2b22decee72b93a72211656ad7" + "a52696c8e570f78be28c0e427a371dafde856e8d5ed24f83b0660b51e7fac05d" + "93a8666dfde6def59af863f80f3e5f6801182c87422203df390dcb736b8f8300" + "52a8832eeeb0b4e27e732aaf793d166b5a3ec7745aeef3766937c2b75a276bdd" + "d145f6010c29d035e343e267cb2d828436876ec3a7ebe3b6347d4172f7a99d68" + "21ce152e039e53deb33340b324c7f068ffb94b3cde35a8eaa12d15c3806a7ad0" + "acec3e8c7078c1d32a28fd3eec9f32cb86e4c22166ff69e83785e851"); + v.push_back( + "1605b8cce529a9d6262fd4390d9e4ae5e14e0adc0ec89b028ef68dd0f373ea25" + "9aaa96f2967091dd0874c0105385e9e6da9ca68297c31afa44ef834535fb302c" + "e5b4e49edacbbdf359fe1228a8172495b3e57014c27edd58b685110980056c50" + "c398a64f4923f2d720b4df16d75cb36b4233660694182099c35028a972519c24" + "764fc94e18e582b24deb3491535fc06b83837c7958522800e822201d694af0bd" + "0aa3834e17d4b1ba36f470905ae5f8bbeeb6c4c8604d8af02baa347b07086d69" + "89867ddd5e8e8ed7740c3469bfa2810519c55c6add1332c4c54ee9097961d674" + "1cb12a09713a0d07645f784f42f5ad94b48b836b34263130b0483f15e3"); + v.push_back( + "ff9c6125b2f60bfd6c2427b279df070e430075096647599bdc68c531152c58e1" + "3858b82385d78c856092d6c74106e87ccf51ac7e673936332d9b223444eaa0e7" + "62ee258d8a733d3a515ec68ed73285e5ca183ae3278b4820b0ab2797feb1e7d8" + "cc864df585dfb5ebe02a993325a9ad5e2d7d49d3132cf66013898351d044e0fe" + "908ccdfeeebf651983601e3673a1f92d36510c0cc19b2e75856db8e4a41f92a5" + "1efa66d6cc22e414944c2c34a5a89ccde0be76f51410824e330d8e7c61319433" + "8c93732e8aea651fca18bcf1ac1824340c5553aff1e58d4ab8d7c8842b471202" + "1e517cd6c140f6743c69c7bee05b10a8f24050a8caa4f96d1664909c5a06"); + v.push_back( + "6e85c2f8e1fdc3aaeb969da1258cb504bbf0070cd03d23b3fb5ee08feea5ee2e" + "0ee1c71a5d0f4f701b351f4e4b4d74cb1e2ae6184814f77b62d2f08134b7236e" + "bf6b67d8a6c9f01b4248b30667c555f5d8646dbfe291151b23c9c9857e33a4d5" + "c847be29a5ee7b402e03bac02d1a4319acc0dd8f25e9c7a266f5e5c896cc11b5" + "b238df96a0963ae806cb277abc515c298a3e61a3036b177acf87a56ca4478c4c" + "6d0d468913de602ec891318bbaf52c97a77c35c5b7d164816cf24e4c4b0b5f45" + "853882f716d61eb947a45ce2efa78f1c70a918512af1ad536cbe6148083385b3" + "4e207f5f690d7a954021e4b5f4258a385fd8a87809a481f34202af4caccb82"); + return v; +}(); + +#endif//DICE_HASH_TESTBLAKE2XB_DATA_HPP diff --git a/tests/TestDiceHash.cpp b/tests/TestDiceHash.cpp index d010e02..7c1577c 100644 --- a/tests/TestDiceHash.cpp +++ b/tests/TestDiceHash.cpp @@ -1,11 +1,10 @@ -#define CATCH_CONFIG_MAIN// This tells Catch to provide a main() - only do this in one cpp file +#include #include -#include #define AllPoliciesToTestForDiceHash dice::hash::Policies::Martinus, dice::hash::Policies::xxh3, \ dice::hash::Policies::wyhash -#define AllTypesToTestForDiceHash int, long, std::size_t, std::string, std::string_view, int *, long *, \ +#define AllTypesToTestForDiceHash int, long, std::size_t, std::byte, std::string, std::string_view, int *, long *, \ std::string *, std::unique_ptr, std::shared_ptr, std::vector, \ std::set, std::unordered_set, (std::array), (std::tuple), \ (std::pair), (std::variant), (std::variant) @@ -23,6 +22,7 @@ namespace dice::tests::hash { struct ValuelessByException { ValuelessByException() = default; ValuelessByException(const ValuelessByException &) { throw std::domain_error("copy ctor"); } + ValuelessByException &operator=(const ValuelessByException &) { throw std::domain_error("copy assignment"); } }; template @@ -53,9 +53,16 @@ namespace dice::tests::hash { template bool test_vec_arr(Args &&...args) { - size_t vec = getHash(std::vector({args...})); - size_t arr = getHash(std::array, sizeof...(Args)>({args...})); - return vec == arr; + std::vector const vec{args...}; + std::array, sizeof...(Args)> arr{args...}; + std::span span1{vec}; + std::span span2{arr}; + + size_t vech = getHash(vec); + size_t arrh = getHash(arr); + size_t spanh1 = getHash(span1); + size_t spanh2 = getHash(span2); + return equal({vech, arrh, spanh1, spanh2}); } template @@ -67,6 +74,7 @@ namespace dice::tests::hash { TEMPLATE_PRODUCT_TEST_CASE("DiceHash with default policy compiles for every type", "[DiceHash]", dice::hash::DiceHash, (AllTypesToTestForDiceHash)) { TestType hasher; + (void) hasher; } TEMPLATE_TEST_CASE("DiceHash works with different Policies", "[DiceHash]", AllPoliciesToTestForDiceHash) { @@ -94,6 +102,10 @@ namespace dice::tests::hash { REQUIRE(test_vec_arr(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0)); } + SECTION("Vectors and arrays of std::byte generate the same hash (basic type)") { + REQUIRE(test_vec_arr(std::byte{1}, std::byte{2}, std::byte{3}, std::byte{4}, std::byte{5}, std::byte{6}, std::byte{7}, std::byte{8}, std::byte{9})); + } + SECTION("Vectors and arrays of tuples generate the same hash (non-basic type)") { REQUIRE(test_vec_arr(std::make_tuple(1, 2), std::make_tuple(3, 4), std::make_tuple(5, 6))); } diff --git a/tests/TestLtHash_Hwy.cpp b/tests/TestLtHash_Hwy.cpp new file mode 100644 index 0000000..2bf091c --- /dev/null +++ b/tests/TestLtHash_Hwy.cpp @@ -0,0 +1,4 @@ +#define DICE_HASH_TEST_LTHASH_MATH_ENGINE MathEngine_Hwy +#define DICE_HASH_TEST_LTHASH_INSTRUCTION_SET "Hwy" +#define DICE_HASH_BENCHMARK_LTHASH_HIGHWAY_TARGRETS +#include "TestLtHash_template.hpp" diff --git a/tests/TestLtHash_metall.cpp b/tests/TestLtHash_metall.cpp new file mode 100644 index 0000000..66e170a --- /dev/null +++ b/tests/TestLtHash_metall.cpp @@ -0,0 +1,41 @@ +#include + +#include + +#include +#include +#include + +#include "TestLtHash_metall_common.hpp" + +TEST_CASE("LtHash metall") { + std::string const path{"/tmp/" + std::to_string(std::random_device{}())}; + + auto pid = fork(); + assert(pid >= 0); + + if (pid == 0) { + int st = execl("TestLtHash_metall_phase1", "TestLtHash_metall_phase1", path.data(), nullptr); + assert(st == 0); + } else { + int rc; + int st = waitpid(pid, &rc, 0); + assert(st >= 0); + assert(WIFEXITED(rc)); + assert(WEXITSTATUS(rc) == 0); + } + + pid = fork(); + assert(pid >= 0); + + if (pid == 0) { + int st = execl("TestLtHash_metall_phase2", "TestLtHash_metall_phase2", path.data(), nullptr); + assert(st == 0); + } else { + int rc; + int st = waitpid(pid, &rc, 0); + assert(st >= 0); + assert(WIFEXITED(rc)); + assert(WEXITSTATUS(rc) == 0); + } +} diff --git a/tests/TestLtHash_metall_common.hpp b/tests/TestLtHash_metall_common.hpp new file mode 100644 index 0000000..a0563c2 --- /dev/null +++ b/tests/TestLtHash_metall_common.hpp @@ -0,0 +1,28 @@ +#ifndef DICE_HASH_TESTLTHASH_METALL_COMMON_HPP +#define DICE_HASH_TESTLTHASH_METALL_COMMON_HPP + +#include +#include +#include + +#include +#include + +using namespace dice::hash::lthash; +using namespace dice::hash::blake3; + +using allocator_type = metall::manager::allocator_type; +inline constexpr char const *lthash_name = "lthash0"; + +using LtHash_t = LtHash<20, 1008, Blake3, MathEngine_Simple>; + +inline std::span obj = as_bytes(std::span{"spherical cow"}); + +void print_span(std::span bytes) noexcept { + for (auto const b : bytes) { + std::cout << std::hex << static_cast(b); + } + std::cout << std::endl; +} + +#endif//DICE_HASH_TESTLTHASH_METALL_COMMON_HPP diff --git a/tests/TestLtHash_metall_phase1.cpp b/tests/TestLtHash_metall_phase1.cpp new file mode 100644 index 0000000..25b3ad3 --- /dev/null +++ b/tests/TestLtHash_metall_phase1.cpp @@ -0,0 +1,15 @@ +#include "TestLtHash_metall_common.hpp" + +int main(int argc, char **argv) { + assert(argc >= 2); + char const *path = argv[1]; + + { // create segment + metall::manager manager(metall::create_only, path); + } + + metall::manager manager(metall::open_only, path); + + auto lthash_ptr = manager.construct(lthash_name)(); + lthash_ptr->add(obj); +} diff --git a/tests/TestLtHash_metall_phase2.cpp b/tests/TestLtHash_metall_phase2.cpp new file mode 100644 index 0000000..e464ebe --- /dev/null +++ b/tests/TestLtHash_metall_phase2.cpp @@ -0,0 +1,26 @@ +#include "TestLtHash_metall_common.hpp" + +int main(int argc, char **argv) { + assert(argc >= 2); + auto const *path = argv[1]; + + { // reopen and read the segment + metall::manager manager(metall::open_only, path); + auto lthash_ptr = std::get<0>(manager.find(lthash_name)); + + LtHash_t other_lthash1; + other_lthash1.add(obj); + + LtHash_t other_lthash2; + other_lthash2.add(obj); + + print_span(lthash_ptr->checksum()); + print_span(other_lthash1.checksum()); + print_span(other_lthash2.checksum()); + + assert((std::ranges::equal(lthash_ptr->checksum(), other_lthash1.checksum()))); + assert((std::ranges::equal(lthash_ptr->checksum(), other_lthash2.checksum()))); + } + + metall::manager::remove(path); +} diff --git a/tests/TestLtHash_simple.cpp b/tests/TestLtHash_simple.cpp new file mode 100644 index 0000000..7f75952 --- /dev/null +++ b/tests/TestLtHash_simple.cpp @@ -0,0 +1,3 @@ +#define DICE_HASH_TEST_LTHASH_MATH_ENGINE MathEngine_Simple +#define DICE_HASH_TEST_LTHASH_INSTRUCTION_SET "x86_64" +#include "TestLtHash_template.hpp" diff --git a/tests/TestLtHash_template.hpp b/tests/TestLtHash_template.hpp new file mode 100644 index 0000000..29fce4a --- /dev/null +++ b/tests/TestLtHash_template.hpp @@ -0,0 +1,707 @@ +#include +#include +#include + +#ifdef DICE_HASH_BENCHMARK_LTHASH_HIGHWAY_TARGRETS +#include +#endif + +// integer value -> hexadecimal ascii representation (e.g 0 => '0', 10 => 'a') +static constexpr std::array encode_lut{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f'}; + +static char hex_encode(uint8_t const half_octet) noexcept { + assert(half_octet <= 15); + return encode_lut[half_octet]; +} + +std::string to_hex(std::span bytes) noexcept { + if (bytes.empty()) { + return ""; + } + + std::string buf; + for (auto const byte : bytes) { + auto const lower = static_cast(byte) & 0b1111; + auto const higher = (static_cast(byte) >> 4) & 0b1111; + + buf.push_back(hex_encode(higher)); + buf.push_back(hex_encode(lower)); + } + + return buf; +} + +namespace dice::hash::lthash { + template + std::ostream &operator<<(std::ostream &os, LtHash const &h) { + os << to_hex(h.checksum()); + return os; + } +} // namespace dice::hash::lthash + +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace dice::hash::blake2xb; +using namespace dice::hash::lthash; + +std::vector make_random_data(size_t length) { + std::vector data; + data.resize(length); + + std::default_random_engine rng{std::random_device{}()}; + std::uniform_int_distribution dist{0, std::numeric_limits::max()}; + + for (size_t ix = 0; ix < length; ++ix) { + data[ix] = static_cast(dist(rng)); + } + + return data; +} + +// Note: the template parameter H must be an instance of LtHash for some +// valid B and N. +template +struct LtHashTest { + static constexpr std::array obj1{static_cast('a')}; + static constexpr std::array obj2{static_cast('b')}; + static constexpr size_t checksum_len = H::element_count / H::elements_per_uint64 * sizeof(uint64_t); + inline static H const empty_hash; +}; + +#define DICE_HASH_TEST_LTHASH_CONFIGS (LtHash<16, 512, Blake2Xb, DICE_HASH_TEST_LTHASH_MATH_ENGINE>), \ + (LtHash<16, 1024, Blake2Xb, DICE_HASH_TEST_LTHASH_MATH_ENGINE>), \ + (LtHash<20, 1008, Blake2Xb, DICE_HASH_TEST_LTHASH_MATH_ENGINE>), \ + (LtHash<32, 1024, Blake2Xb, DICE_HASH_TEST_LTHASH_MATH_ENGINE>) + +/** + * @note tests are adapted from: https://github.com/facebook/folly/blob/main/folly/experimental/crypto/test/LtHashTest.cpp + */ +TEMPLATE_TEST_CASE("Test LtHash using " DICE_HASH_TEST_LTHASH_INSTRUCTION_SET, "[DiceHash]", DICE_HASH_TEST_LTHASH_CONFIGS) { +#ifdef DICE_HASH_BENCHMARK_LTHASH_HIGHWAY_TARGRETS + auto target = GENERATE(from_range(hwy::SupportedAndGeneratedTargets())); + hwy::SetSupportedTargetsForTest(target); + INFO(hwy::TargetName(target)); +#endif + + + using H = TestType; + using T = LtHashTest; + + SECTION("constexpr") { + static constexpr H h = []() { + constexpr std::array key{}; + + H tmp; + tmp.set_key(std::span{key}); + + [[maybe_unused]] bool b1 = tmp.checksum_equal(H::default_checksum); + [[maybe_unused]] bool b2 = tmp.key_equal(key); + + return tmp; + }(); + } + + SECTION("change optim policy") { + H h1; + h1.add(T::obj1); + + H h2{h1}; + CHECK(h1.checksum_equal(h2.checksum())); + } + + SECTION("empty") { + H h; + auto checksum_len = T::checksum_len; + CHECK(checksum_len == H::checksum_len); + auto checksum = h.checksum(); + CHECK(checksum_len == checksum.size()); + CHECK(std::string(checksum_len * 2, '0') == to_hex(checksum)); + } + + SECTION("reset") { + H h; + h.add(T::obj1); + CHECK(h != T::empty_hash); + h.clear_checksum(); + CHECK(h == T::empty_hash); + } + + SECTION("copy ctor") { + H h1; + h1.add(T::obj1); + + H h2{h1}; + CHECK(h1 == h2); + + h2.add(T::obj2); + CHECK(h1 != h2); + } + + SECTION("copy assignment") { + H h1; + h1.add(T::obj1); + H h2{h1}; + CHECK(h1 == h2); + H h3 = T::empty_hash; + h3 = h1; + CHECK(h1 == h3); + } + + SECTION("move assignment") { + H h0; + h0.add(T::obj1); + H h1 = h0; + H h2{std::move(h1)}; + CHECK(h0 == h2); + H h3; + h3 = std::move(h2); + CHECK(h0 == h3); + // Make sure copying to a moved-from object works + h2 = h3; + CHECK(h0 == h2); + // Move from h3 to make sure calling destructor of a moved-from object + // does not crash. + H h4 = std::move(h3); + // This line is here to make sure the previous line is not optimized out. + CHECK(h0 == h4); + } + + SECTION("add commutativity") { + H h1; + h1.add(T::obj1); + h1.add(T::obj2); + + H h2; + h2.add(T::obj2); + h2.add(T::obj1); + + // add('a'); add('b') == add('b'); add('a') + CHECK(h1 == h2); + } + + SECTION("add two LtHashes") { + H h1; + h1.add(T::obj1); + + H h2; + h2.add(T::obj2); + + h1.combine_add(h2); + + H h3; + h3.add(T::obj1); + h3.add(T::obj2); + + CHECK(h1 == h3); + } + + SECTION("subtract two LtHashes") { + H h1; + h1.add(T::obj1); + h1.add(T::obj2); + + H h2; + h2.add(T::obj2); + + h1.combine_remove(h2); + + H h3; + h3.add(T::obj1); + + CHECK(h1 == h3); + } + + SECTION("add chaining") { + H h1; + h1.add(T::obj1); + h1.add(T::obj2); + H h2; + h2.add(T::obj1).add(T::obj2); + + // add('a'); add('b') == add('a').add('b') + CHECK(h1 == h2); + } + + SECTION("add different objects") { + H h1; + H h2; + h1.add(T::obj1); + h2.add(T::obj2); + + // add('a') != add('b') + CHECK(h1 != h2); + } + + SECTION("add and remove same object") { + H h; + h.add(T::obj1); + h.remove(T::obj1); + + // add('a'); delete('a') == 0 + CHECK(h == T::empty_hash); + } + + SECTION("add with initial checksum") { + H h1; + h1.add(T::obj1); + h1.add(T::obj2); + + H h2; + h2.add(T::obj1); + + H h3{h2.checksum()}; + h3.add(T::obj2); + + // add('a'); LtHash(checksum) and add('b') == add('a'); add('b'); + CHECK(h1 == h3); + } + + SECTION("add object random shuffle") { + // 1) generates random objects + // 2) add them to LtHash + // 3) shuffle the object list + // 2) add them to a new LtHash + // 3) then verifies that they reach the same checksum + int objectCount = 1000; + std::vector> objects; + for (int i = 0; i < objectCount; i++) { + // object size is between 1 byte and 1 KB + size_t objectSize = (rand() % 1024) + 1; + objects.push_back(make_random_data(objectSize)); + } + + H h1; + for (auto const &o : objects) { + h1.add(o); + } + + std::default_random_engine rng{std::random_device{}()}; + std::shuffle(std::begin(objects), std::end(objects), rng); + + H h2; + for (auto const &o : objects) { + h2.add(o); + } + // H(o1 + o2 + ...) == H(o_i + o_j + ...) + CHECK(h1 == h2); + } + + SECTION("add and remove random") { + // 1) generate random objects + // 2) adds them to LtHash while storing the checksum after each add + // 3) removes objects in reverse order, verifies that checksum is reversed + H h; + size_t objectCount = 1000; + std::vector> objects; + for (size_t i = 0; i < objectCount; i++) { + // object size is between 1 byte and 1 KB + size_t objectSize = (rand() % 1024) + 1; + objects.push_back(make_random_data(objectSize)); + } + + std::vector> checksums; + checksums.push_back(std::vector{h.checksum().begin(), h.checksum().end()}); + + for (auto const &o : objects) { + h.add(o); + checksums.push_back(std::vector{h.checksum().begin(), h.checksum().end()}); + } + + CHECK(std::ranges::equal(h.checksum(), checksums.back())); + for (int i = static_cast(objects.size() - 1); i >= 0; i--) { + h.remove(objects[i]); + CHECK(std::ranges::equal(h.checksum(), checksums[i])); + } + CHECK(h == T::empty_hash); + } + + SECTION("set checksum") { + H h1; + h1.add(T::obj1); + auto checksum = h1.checksum(); + H h2{checksum}; // copy version + CHECK(h1 == h2); + + H h4; + h4.set_checksum(checksum); // copy version + CHECK(h1 == h4); + } + + SECTION("set checksum fail") { + if constexpr (H::needs_padding) { + // If padding bits are not properly zeroed out, the checksum is invalid. + + H h1; + h1.add(T::obj1); + auto checksum = h1.checksum(); + + std::array new_checksum; + std::ranges::copy(checksum, new_checksum.begin()); + + uint64_t *ptr = reinterpret_cast(new_checksum.data()); + *ptr = 0xFFFFFFFFFFFFFFFFULL; + CHECK_THROWS(h1.set_checksum(new_checksum)); + } + } + + SECTION("add object with known value") { + auto obj3 = as_bytes(std::span{"hello", 5}); + H h; + h.add(T::obj1); + h.add(T::obj2); + h.add(obj3); + h.remove(T::obj2); + if (H::element_bits == 16 && H::element_count == 1024) { + std::string_view expected_checksum = + "353cae6169e519eb9cf80edd2c5b33810276227e77a09030e3ac3b00299c9716c6b592" + "b262b2b05ad82db539f23fc03baa1ffacc9704fe078219307c02c0f501c810895c19a7" + "71934855d091e30db8eba564596f071400fcca93b69115055c55e0b333b5583ec0068a" + "219289b557be5b24cfa679ae8e20b9084c77eadab966e4f94239d5f671371aa17c41f0" + "510aaaeb6e28fed0eb37b57c5ff8f6c64a0395ddb32d2948abee9ae84930ee0d43d015" + "b2f577cadb558eef33e715f349114c1937817ff26b606f1f33a1f3b4a72eaa3b24573a" + "78d06b315857a8295675ec2bfc9897b644f60d401c4315bea8a6ad410f77e3969aaa03" + "2d31526df0c271665647c98f1e4d3946b659e47f45480c3eac9b0e0b742501595b24d5" + "362d3f6f4ba8a4fcda7d87951ade9ec184a45c2fd5bff5282835c29071551e96d940c3" + "ed19bb3124c3b37080dc3c80bc22f61b431195b9489bed3244e0e522bf8f8c752145b0" + "1ee47701085ffa1238f3a1d5e778052b393330fff8b586d9399cced75d4d15697f9015" + "174d3302d97b1cc55ae20cdb573d4061d2940b213a35808122e7d55bd53e2c9ba1779c" + "8a19532ff1e65a440e871f96e086dce6693efba86e033f7e3b04069f9eeccc0f5c5947" + "af0b04f5528be1b57bba0912eeb52fdd11f0cac0e40ae641bbc40207188adbfe13463c" + "880e84016476facae56f7f6de26e7f508a277a409988aabec7f9bb552000e3f7a44f51" + "ec5c7c98979a227403464797a06fae0d7aa951bb429cb9df4ed65a430a98e0c88f7d4e" + "47e1256f17c4b126f05b885154507b3b80a2a1b6e1f43eea48b4b93cab0622bd002a25" + "dd5d1b69fe05c11619837eba6edfac493d663409f5ce82762584205fe49e8f718fbc5a" + "92823cd9a17c1c9a07ce9f2535c918c6ee0f0729b67eb0be8b5e0edc990260679fdf5a" + "9991a6d62ec1d72f5e5a478dbf0e5cbd1703daf5f170411d0d7aca4921cb644ec1d86e" + "02711d09359b0f2b45a5b9fe57e122add8b5ae27aaeb44aa77a9fe187a67ea7447b27d" + "02b4bf41fc5024350dc8838fb8f977535ba4481569a74d90306e0c9979a9d149be9502" + "23d1ca5d425b9ec281ee3884d8e8a1ad0d00504f0f57ff35e0ee33d184f35fd28dfa34" + "8686fb926da95597fb947acc509a5cb7cfe1eeb33dfdf9b4b384346c862cfb198f6948" + "a6f6d53a74848043c8b647076b0a90151bd40c58d32434ebf549aa92f4a5b7581b7ec6" + "821ca3485cf8e2a6ce0f5e204ed5a92c84618c2828e5c6f222ec3c48e37dcada7ce28b" + "ba5c09740170d32aa004b43cde46d45f9912528a3a7a7f30fb6019548dd174b4d7b0ba" + "fb232920b972362db4d863a5e0a9e30a041ecb874a7acbd378ccb11ffbffcd086ad797" + "be5b4de07859d0b1fb3e4835a84ea224940482a3849cf392528dfcf8920d4b4bfc4060" + "6e852d85b7bfd1f2723214969dab6adfb8c26dc5f51b1b043b8a25df1eadd90d1a2324" + "0b735943841ae4e13564ddb6f0f7dcac1db82a34ab9ca042f8c4690727c7a0fac98c10" + "dac065a57dff8010e9d49ba3b801622e8b786bb44079ceecf61ff7cc07be8672c647b5" + "25ffea7c3fab95d40d9d36e220bb3a5292880faf05a8dd94e60a4ff0ccfc124d2dca03" + "a85d0864bfa28cddb7bdcc83ff717239dae979596691b6e3062068e6ea442ebd354bc6" + "53b0e5b750bcbfaec275c77ab82bd3452e4776734df686d6bae946855a4659dd3566f4" + "8d0879a00c06a7ce81c0f234e0203ce68ffc9434f3f10281d76110887a4b460514f761" + "b517f1d151d88724160fbeff7f69a5a23eae2bf48916ad55c084b908d955519a67096b" + "94638fa10d8d153a60d0c44f2d9148ad549fb1e64ac423aac1fdc754bc44a69573578c" + "6b881bca177698e68d6ffdc2d7d89469f2e1039e8e3b955581a56c15519590b65bd9bc" + "c3b3b1a95d1d484c2585ebdfd8a15c737b436456934d9b8439d92d1212bf8799028780" + "d9f35d208c093ba6506aff74979faa10fa807398e8fb769be070318caee6b4f5091d8d" + "9254656d0a1e838ba73ed0f0c8e8d4a0d19f9e91340578baa7ce5aec9f73f8e26db927" + "3c544f11d6b8e5e142f4a8ad70a9e21c3dc2c7b4403073c4722e0af775f98c37ae0645" + "e1829dec574de3108f62965a5354aaa7695c1e4feab1fc8ccf9a5e2a7ed0758e411ea9" + "ea25f4f659a36cc5aa0bed2a9ce4518cd1aa1b4ed94c2d62596059d20cd948a058b78e" + "f9ad3c9e7c7c9fd433c42701aad7aff74fb14ea39812c3e68b6ca8585432ecd53a7dfe" + "ece8e6a73b0ecddabc8c9da37b140adee6308c540bedbcf77d49762e7efaeededf5196" + "6503315fb287b69a08854ec58fd41c2f214c3273cc48bc71718b801c27936c7fd339b7" + "f78c2eda6835e7d532ef6496cbbc7b018cc48ed49e33c16e16d2bdc98f47f376208770" + "b5d5b6e789b30ca55ad4e8cd09b6bd90b66e8d4abfd0fbc3e98fc28f3913e476161d0f" + "0f7477d3ca066adce7567d1af90dc3415970199ada286e22ea90892da107c34d3745c5" + "d5d3f290fbd2d0b64942117955e94b343517d76959f0764216ce27bc33e772fefe4a48" + "20315d67b43302f73e8002cc6144c3f3ad66c307eb2f9e0192a00da9ddf262b600dd0a" + "49721da25ca71997d17c3441cd9588c5469f6927966d06676f89243387f01ebd2094be" + "ac0716a2e486d85524c51ba2898abb8b8df3a169f93fe6333f4f6a868738969905ba55" + "176f1b8055d19749c0c5122ab1eaf34b0eb458fc10a656811fe4a7bb588eac3450b6d6" + "1c7f634588996ef2d903478cde206f58c1a93069c7df80a28394d05e9a8ed99b5312e9" + "cbec0a2dba2e3e3e24e854798e9dc8c09922fadb987e945a765e2f614993f2b5605548" + "1e3702371d4eb86c872ca65269125be61d86"; + CHECK(expected_checksum == to_hex(h.checksum())); + } else if (H::element_bits == 20 && H::element_count == 1008) { + std::string_view expected_checksum = + "aec6e81142ad553e9eae2f5ea2c9d20dec91881229418b20024b05a235b5f50adb98ed" + "276aa9443a7be0047ed20d3f31c8f80ba3d5048719d5174076ac1db9298ec1e62d6b74" + "0e3dd825427e4d1dcf1270eb656e8cddfc16f549cf769e8dc30fec35039986e41e0230" + "a063e4ca145c162766404257f89c2d43f4eb45beddfc036d552dcf94b5fe3780514217" + "52797a3d67d909529399c31ffdb2ce05f725f03fb0892d45ba84602818af60832ea1f1" + "0b7489ee8ce0c8eb01478ca884b4602f197858a0a95d71e83f4796ee61a2a130297691" + "203342e49820f3ab2e78cdedc0190268a81102d08026851eeeeed5600333b6b6cff6df" + "2c7511c1cfc0cb7b85f7283933abffe308d42779698c3f10b8c42058ec4a60ec78270b" + "68104d6dc7fd042124a32bd2c0b4422d3d9ea3c0d878b71ca12d6778a068db04f49aed" + "a11b31160283abef5143e84111c2502012e490f33488ee6098c809aa2870bd63e1dcc9" + "081925fb4622af39ad02322d24de7311fd1dfaa3eb543ec0a5086f6a4f849281322124" + "d7650430409c3cfff6a2d7d2259937ee21eef320657c13cc1fe1689a35a81ee51802a7" + "54ad1b1a8242ed98f18d033550922eb6b191123f803c68a249d9ef1818290085a00870" + "3db27fad9985f42d36469be3edfa450a290194230f2ad9350304ee8cb3e97cb40ca773" + "6e2953c106256f08c4d7972c611cc134049e431c032d91ff0d5cbf549c006f9042cb79" + "3ca7128ca90faf42212f1e1e02877d91f5ec14884440b3df70352ff73ea75c4768541c" + "de0f03f85ef9a824d1a64834ddd9b0303aa543e959813a07950e235a8b1d9e11c7e26e" + "6e42a8c80a89c58f590561be0dd70a6e2f820c9c2c0af3478eecb47513bcef8ff1ccdd" + "9c1830a92b25c518513033856391c009ec37919fa6d5de6ddc32c4cca1a267257b0184" + "17adb3d1e0d9341f6de3143c98d61d625f8aa39fd0723f9773cb84a6c4c2140c0fa0d7" + "ca5d7d15058c0acdd0cd703b19920b2c42c9c30e71392cb6d39c7b1d5794c44af309c6" + "1db784415a80940a129135abd79bac822f067b4631db656d07a53a8f3a212d510f3724" + "c031cab0a51632792ef2477c061fbef3855725a9f90fbe1608498918392ba532c7927a" + "90a3140b0d4309bd31131381270ef120b51720936184f4c4494e05d10082efe281f936" + "10184ef84c198c3ccb77a2e76fd10f31b9afc33a0e51ff2f707842f590719a1368e589" + "69bbc9db0e208a20c957fd663079f2e347184d081abd5382965ca59817d7f40df44e98" + "f42c668169a79fbc0324a36a86e78c2d4507d2672889760577009261e494cc9d3638b9" + "81697bbc71373ed31149804e3497184bcd2e5f2b11310a360b812431792a1a509560e1" + "eb597c3eb6ec09ea32e1823a3400ec0e8af96f38a35c0f623271cb3fee83461da71823" + "288e1a47de8a79f4284ac24daa8bb9493d65d3051198740600774a63e7cbe4b6125aba" + "678c7d94c920e93cab8fc239be312fb2aa214b91fb3dca17a0f4acd1e732838e09c255" + "615c2d9a2d066a705ce334c0cacc3625fcd413b0c5276da3d935393d0a896861a0e415" + "86da248985ed4626fdde66dacdbc97384cb724b11c51d42947dd686376b19004bdc26b" + "b677543d309b4feb60bd88e922fb5227aed9489130d7954b1b65e152337fb545ad83a8" + "6b2ca548e439c268741dbec80181190d232c4fc8c67399e5900b269463069f6c4506f1" + "7e4f507f40ee294e12eb2973b48022d2b7293a6b9501100bf9aa58ce01c82813bfc79e" + "99e80514ce5b6d92f899800edbf34b87f52c121d8de5cc16429072167eee4c7cfcd050" + "3fa42cace2ad2c641f6187a5d42cc8f60cf70d81b2019182274dc64540cf85c62ba229" + "09d82284fe1899fdc9f13761d408c7072563c5400224a3b9ad73127582311691e03ca7" + "9cf71ff31f226fe6cc8a0807494b976854bf0db4dfe02975a0ee2ee66d414aaaada809" + "855fa4bdea98880a7adfa4e81a099a1ad718cee89fa88b0640fbadfbb3e47d00b075a2" + "f5b0e50f34d09c8ef98fa5462559b50f17b9a44e36e9e949c2c4ccac0e2467a7431adc" + "812dc3c2087119f1521bc8080760db2dfa1f4928c97a2b88ce01011921250cf8ca3ba8" + "39461b7868030ec410e2463659ea29ea540bdcde606304504180a147ed052a56150a75" + "2f045320d34ec0fd469c621d29408af4b861e20dda2262b1fdbd4d02739cadd16f64e2" + "339f62c14ef2a5371e1b4de553e5309528e51d262744896132dda9c48983a801021ff3" + "634ec1bd070f5dd5c8ade63de51b348d0c0fb00d003d17720d4673cd5d05e18f69c90f" + "e82c0b1c6661fe60f41a16a285accbf0f47539d8986cf1d78961321a9564ac9b11221e" + "e3be45d9e1dd1a00a35301d968ccf521230ac5aa85a97f35bafea0d27a918d258c1e0d" + "cca7041b2a598a86e291493c140c3e0cbee51c120bdcf1e8ff4ac1ad07f35663f1f1ac" + "be3c8ae58baccb598a127293232631a878351764046d09494914b6e7a7e11b98d427bd" + "d4aa0b0da9f1271ed78d6ea20dbb1a77974b9b16e9b013cc0f85bf6ac5ed23ec048bb5" + "17f4133a98a1a3279375cd0faecc86ba317dfe184177294e430c3d0fb81a8904a3c9a6" + "2d790566069dbcee14b79882268eb88b37becb23791f218b3743b66c281ca0912c380f" + "8d40acedb72885174640d0f824157af0ef2968c93e25679ae4ade784322f31034c5d4f" + "9c2538ea4a800900a55027d2b1239a5bf09209e91a83a402654f06fc0f43800dd46029" + "61a4c58180a0e2252de900de3eb1873ad74707bb962cbf0dd1c7e0672ae9da0a14bbef" + "125408343478dee0549119431b01d9aa8ae850ff133f8a89bde69d8914a316850ee409" + "970a5383a8d491c806307d9082dfa1b9f03f9cbb40889ff509121c9fa1dfda80f52803" + "0de018a1a8c517797b08065d90bd30c8dc67d1dee50d17885a8e0b6ef57a23e290634d" + "1589d0158f9c8a6a7b14471172cbcab20ae5e024918faf959ab8a80c729141f294b8ff" + "07b6af09666aad7c3ebc6ca88e95c1da3057efcd124d7d123bc2d0474d36c4783c1619" + "45181d856007df8dc040d331d2207262a01cd63d7514279def9d4d98243d132e6554c8" + "611b3adfa683d9a3a8a61cca40cb8af3058e3683fc0bdfc1d9bd0f62cd2265db18542b" + "17d9a08671bc6d381d6ee1aa27c5400b4112a0c15d087306581d67006ac1343861ae4d" + "01d938732deae4e9efabf44f1102aa6fbbce2d111f3edb4ac0bc2c0533b186e6632e2c" + "631edde5e4dc9ebce8158f4ee0fa10cd5e046a78c2d03b91bf1a608cad8c248c5e1cb0" + "62847e09850e13fa2187be526d883cdcc168555b1c081ffe8b4d3e1764971bb7b9255c" + "07a4361a857a88d4e059b7300d3e0c769e3ce304ce7048163f7d7b3f16738987faa4ed" + "21d1cb853997341d193947484317584d39e3882691bd29773937184ca49265e93e3fca" + "2cedd84cd0353eac027e456d1c26050661a5444d7b36be4fcd38720c3637e5326e759e" + "9d3838729e0fcc49b4540989ed2e7534a956209b6da9b7f470ff033f23a7f1a6e9101d" + "00d00e9cdcb8482a5b06c18cc6993f29f8d2a7ddcb91e804b82a417c404d761edc6ae6" + "0b81d884268e41c161fcf18f3dec7061e86b046e3831afe93ca869fe06404f4e174d75" + "420686108a011129790b1d4046eae7714e11d980c271eae5f13858d1427caaec9c023e" + "73afdcd76d593922d30a6f04d41f2d8d8a820e0e48171f92ee4ee455700b381fac06e5" + "70c58b0a81bc499a835826117f1146877c90b011ea0ca4db40a17d29"; + CHECK(expected_checksum == to_hex(h.checksum())); + } else if (H::element_bits == 32 && H::element_count == 1024) { + std::string expected_checksum = + "68c13c13c663d66f6f834f309cc293115b5c2026dab510ce1c6dae6b13763954ad4771" + "4f93a997a80ca52922770c4e64e3bbf10f67cc87a51d183ec0c66dc9ff751fdc5723cc" + "f8bb6636e0d34e1c7b3a38adcaa2b22e1e9f68bc1524ae89c62a601b1fd1ae544ede9b" + "a826e2153ef1c406d5e6807018e635e677a676031d94a798281d6801792a2aeb22d5ab" + "3082fe6b90b0bb5ffcc71727eb662fb0996e374b4a50074c47f8d68725986e142c98e1" + "38d4c934e17c17ce8ac9b554ed3d0e5016aa8d34976104ccdf63f5309f9a38de29a45f" + "14cd08041f7dd27882891307346495eb98348f06999fae96eba531e7837fcf868ae438" + "2200051f4f5c6ada89584a4a301faa5943ca28c3c0d850fe4754818674fc5a41efdd5b" + "6860c23ad851a5f35ed54dfc70603023b02ae43a8d211dcf7857764559b50aa6cbaa01" + "9fea1485aa0b597efc968e969f1e88add067022a695836fb5da6baacbf4f2278544acd" + "a0b22182788b872a2a75f70fbd2b36ae7dd8c5a291e2e2d2657f583b3852e5183d4bf1" + "5a2b6084cd3f5ef66f2457673aeb13a366ae31ec2cb8c36bf265ffe34af45e8e4e6782" + "0730952b527a955af1fa2dd5a3c4ba3bf94847627e3d049cce0c44c976be8f197f41da" + "a61fa9cf4f95fdc130585d30d897c0364e7a1b162841f7af267bf76888b376dfe7790e" + "14b3d1786e3369c0270bc3d0130c3acfd6a4638b72bd39a9e68e50b16d00a6ba74cf12" + "ccf3ab028e4d728d25c3f99323ffe5c158054b9581cd2a897d24468bb0001337b68b14" + "94fc4ba2f407d2df0d49a593daa31993b4ac16d880efbb94feca0728bd284e0acea435" + "4e386327a086e2e1b991ec31690e0b3eb4a8dffe5c982cf368597980553e2b12e87ccf" + "2e6994c2ea74546a3029a62a9851c5a23b89c2053519ae6304704a19284d8930fa7cca" + "2ab6231e5d620d5743594f11e1248b6b362ba8b0015ca9c7d6bc36d0ae9104221e75c9" + "519e34b0d0888a672cadaca3198d79c214cc275ffee909e600d949e3353cb24e033e19" + "596b4a6d7c299de11337f4d46704f6ad5292fa3eb72535bfd6a27c106a457eca485dd5" + "fdea051666affc73d0e051db5aaa894735c403e2456d0594c42cb0bd1ec325d30ac2dc" + "6aa698a2c1e499a809f9c25f88101d1307b339c7251fc4f455d5d52d1cc08d17ec626a" + "be8ff10ae10b61c0d96b786357dc1385b84f76e1a0fc2fd38ed67f3021ebff3c1655fe" + "20056434a7e68f2569c262f5c1bca2ecc0f9c8160e6b9bfed5fb6d8cf0e74f9df39453" + "9e5a9f04d4a0f2da01023d92ea2e8d2c36ce0b87d07fe6c996bba8795dc81c3c9b7c07" + "26879351817464e4626694aa5ce33ae6ee988f81412c74a8216901fbe46a4adaa75b78" + "39ab01d933bd77b43d8bd4736adbb0507318801f024a044a6a3308cccf4438b6865ecd" + "59521505806a935871c26cfcd4bf3955b2b573eabfbecaf53eb52e92f6fd3e397d4c2a" + "63090ef8b9e2434290e4184aff69bc64dc1246a5e4acf3f02b663c723404911ae4863b" + "fa3222af924647fb47e6ca45dc51bc5e20f771865e73652607006df18ff0d62f688a4d" + "3ed986fa8e79d7f400253d28028f79770d84daa2b14d3aee4a2d4153882f56d693ec7d" + "d9dbca9e1496464e0772abe54c0610dc969952189649a52ceb910cb52a61f989bb993a" + "e948899edd5a08dc4b10eceb47cf68cc3747304dcdde918c4fe60d336af06b0e84afbe" + "e81f39cd3bdfc054f421a12a0d0c8c89b451b3dc385e1ea94aece03cd692ad01237425" + "98fc638ac688c15d1738ef89c07bd19384ea0acbf8e97342c33c71b10993df7c28a36e" + "14278d352a02599478a30a942069dfbb10c7dfc6b921d75eb47f5e6296e17c3a79ef39" + "3d764f167b8c1c6598482497ffe7dd3103ea0c682d15565fe2d75357500482b6dc2848" + "f5a0ef2b7ab697fff5878ec016aa674cf21666cd83b88720ccb6fcfaf9ca9d85f42409" + "471718ec28c85717369f1d9c130b00e18ae7bfab24994bcb582c8e5e9bae67aabf5306" + "8f8d3ae2860c24250f1388b6cda75f2218b674084ee131823b8a1bf2bf70d1028cb99f" + "db3cbb3758e0f03efd6d928900d8ea73f3c9600a593d146ee6d10d437021699ba3fede" + "c2e7942200bfd968a4744b477078b2479f223264e4c845e994558424ebfebfea0cc4af" + "9975b0e59792a28b972d81e2391ad73970842fc7ceab6e217fb40338d107fb5522b0ba" + "c9ae4346600a7444c97f488db697a4a3d63c2b762b1a48eb036a2464bcf5def2c20919" + "bb2bd8ad4f5e00e5a8fc3f77ba8489069b415ed84ff767c6b3e80130688459770bb1bd" + "870d1861f043270ea20a31545a1734134ff406a00d47c65d7e09fb06f714f7a70f20ce" + "cc39bf5e4d4fca8e083fcfc61d8c2212561bc0633dc5c32ff0d922f583ef2b1aa21d83" + "4767708c257d65123d7280f4508bad4c36e162554dfb3a913454b94a4fd78573dc2499" + "f11092ba5b02549f0abf5c325e57d82e749d1e78809cd6484edf8e9b71e1a9b1e58c86" + "b6d82b11ffd1c36a19bb31a6fb38857c4811bd184ae1658b6a3a86175c8d0567ff08cc" + "e74c281b47557a48a1c92a3eee6a903f730ee62e0ef37882dbeb1526a6181f3681ed48" + "529a8af5efdf4c898e84546a18e084223b8077cea0ba9d2fd3d43a2a8560945167e8ef" + "d293a839d086c9b324c3da7a082576fc5e7bbe80b3b2a4057571714eeaf28e5e927fa1" + "f046759dcb5c7bd0b33f1b109ea40b5afaded8c72cfa555f558a2cb581aa4d41216587" + "e9bc03ddfda78471b26aae2b12a265f6f0d1b5a5a61a3e0af9efb96aa606b4d8c6bdaf" + "bab903fb553da5088e7dcebd2666b9c02fc287ec89bfd49a2107f5eac4f60a105c590c" + "a6fba3c9bdce8d385ea8f4b2ee0117f8bf29fe855f27e3ae6f205c43854410a8a5f6b5" + "3c9474d2eeb14bc050df8b8c9b697a510b77f93fe5c41ac0ccf2f13512768e6db2e43f" + "e78e5f8bf61129a91cad0bff6a25d96b568696ea5920f2a4bcc3902df8f929a1d7af3f" + "1e7e37aacf22488dd63a5f33c290d3d57a30a17740210e7f949cc244042fa43f75f25f" + "1da2210cb28e255d57111a647b7b1e2a209545a288fca4f19a1d628c0f5a03e680560f" + "80e7561478cfe222a10844cd5a9257b0ec8bc388d18d6bfbf2cf020448e20c4efaeaea" + "4441c8b82685533ad6c2e657e18d22038fc1fac7e2500b29e86080a54d85661c326646" + "7af9fd33fdc84aa7ce4b5297c9477eec2b45ebaf3c324629f2bfd9fd38eb62c11da2d1" + "abf6291005da15d12910e27cbf02dbe2c02093923668d0dac192c402dd874f7f3692c5" + "fca4b2d9850a6c28dbf0b1d13690cb077174fbaa14c9638d773f5e6ad43a891f0783ac" + "b90da25c894d5b33075ef153a477f76934380dbf2a6b39f3039f43a403387b33137348" + "8cbbcc5534df67c3b1c4323d20d98b4a68e266ce8eff85cc49053449d254e1f8eace18" + "9597731c9b8a5780437c53755706b72a959ce57a2baa10e559e577527c9dacb89fc6bc" + "cbbe250cf6ad229fa68186e7c0687992990d1fac6dfb0af7e6f10f600717d8ece368b9" + "ffb7460f50f8ba1ea58849781ea7f4c369df359c9d08f1e401fe31aafd0ba9a7af0550" + "599c33a1452a7d35dd7ebd241f93d725cb53864c6b0ecae5383d36ed1a1baee0d9a4fb" + "8191091eb68113210a7d2fb7441127987300677eddeb0b3d4104d4e70f40ae0f67468e" + "cf1f7c56e38737ab32dc4e3adbf8bff02cd9c730eab344bb48d4a87625cfbf8b01090a" + "c935e5b63835202945904281659fb1091b3c9ecb48bd2a1192552006f6f6d32d779b86" + "fe3aafb4927972b99be92cc229ef6b0008ac2f8ac782e499442accf4e27eeb19b8e53b" + "46669ee9a53fa78d7e5e03bb40195fec07673f7dcf21c4b71ff1eae78a8728ba7c6658" + "be16cbcfb8bebd400367ffac62b3e5a64a25becea53c62f0dfe3a62273d83dd97bdf82" + "8f4ad6b8c240d4043cf5f8a472e0d5a6caebd3915f68cfe82f30eafd499c6aec35199a" + "bc76fddf895320d5cebd24600532588aa35adc3c117ab71b528b80064b55bf6c7f107a" + "e5465a41a24cc0d59bc3608c101d93c52fd614d37b9a80e912285859d470e4d9815d49" + "9250cf4bd0a85a0d8fd825f37128e62ef37b64a2c0648badd3ccbd448b5ab57e92baee" + "949d2ec36d3c1512316a972f570bb1e44fc8e4d07ab417af6771de2fa66677323b8cbe" + "587f978e5a463f56b0b76e9ce5871d24c80f2a0ef3c57c28338de45871a3632ae1cf7b" + "4437bb1f148ef8bba02911147aa536d140d09e456f41db3151d23c1037a812e4c37a64" + "d95297170729ad8e7b3ed13794e78f16f40c11c50ea3af8723b142fb53e22a9ed9ad47" + "a4a9a919614cf72311f6566cb3b3dfe046530524787e773181e734e86728dbfae34a04" + "5041c83958c509ac68664870a1c7334614506da4692a29e9cf4cb9ac99ac94b3e63dbe" + "f58f80d55863a4a13b3543efff58295f8c690e5fe71534860f67b19c73013c0e6ff52f" + "e0d4c6830023dc56d0473a9797417df439d63d83779097b9f6899a42d6b8a63312aef6" + "f6e75b66efccfcbb63fe4e14002aeb043ec467b68b6bb7fb3ed2e0e850d1c5da0e0015" + "7d8ad7a12c3ec9b6fff04ae03ade0f00ad6d6d76d8bf50f7f9309bcff6184aa3161eee" + "7ffe48b24145389b08a892d2f71efcc9b8be2db9309efd419073519c39993fff210de3" + "ced7eb2e62c6356fc936db012cb2e29b0724e3da7ec9a74042daaf476e150eb0227334" + "0994673cf019525eb3a341e7bb92424ac66c0af6c00a4c94a54bbd65c7de052a6c4d5c" + "cb6e905e609d9c9d93cd28a3993b0df86a7097411e91c46db8471ce2e4645ed7901116" + "fcfd72a117158752b02dfae2e4b2b7be13b8eb01b49066a359fbcb013607d9623fb275" + "711576e948acbacba72fba1c385799b908d14d7ecf57b9d83a59b628c092b686fcf6a9" + "4a5330d95aa8f204a34c4502350564e3657219c914029139fb0fc95a536183e40c1d96" + "d6e1f75073b833c51170cca1a32ca1937fc7e46af88e81346f10aaf88a8e2473444cf6" + "dcef0a178d6f88ec82e8cc79eb6a162d0f177ff4e6ab3b738210f57995a57800dbbe73" + "89349484e6d71813a93c074c55279ad87a64cc0c9e33203a2a275ed59cdc0d8a03aa74" + "6793a93e83868b42fd781d091181455aa28d1d93850766a0b58cdd4fb6d4fd30966da7" + "ab99c42f6c337f8654869803dc372839e3b0e21ec959e59ec07a50e8d11642c4a82b73" + "d07064d265a33f030d397133306d0d69caa3e93238fdc7f78432eea40851a05f683924" + "d7f959c353a66fa11da2c27090505529474ec9f8220794725ab25575e4562f6f94dafc" + "107d5c9fd808471dacf6ec06faa727d7960fb81648551dc248deea3b13bbb941d3d485" + "dec2b60d81c9d8378f25f88dba0d3611137fa0181e8d2b73dddb24aa2f904b97e207cf" + "df4fba7faf36dcb6e7bb857b20751dbaf85608181bcc04d6f23e661ca8595cef86c837" + "75a67825dc7274924e81ef31a01b9a9067ed16cb8c0f2ccd92123b2dc11f847e1467a8" + "9e09259cb63c25b35fb3cf8f20e53667ca44a95f5d7bbe00c50f92cdd970e6c60ae2d6" + "232bc7720c128ef4a4a5e3913be0ae1c5e1c64d10b1b8f24e493c9f543a7cc2ca5eaa9" + "ea208de5ce933340c4f7df1151694c8f24edf4abd8ca09db389b14f4afa2a3be7be8ea" + "0f384384a324c75569278ab5a9a29d48714db5525e63c7bdde225a59d36b9786fc6e09" + "686a79a29884ae47e0cb69a020f9e796cf13fcc53ef08ba265d121a9f7bb35021eb098" + "09"; + CHECK(expected_checksum == to_hex(h.checksum())); + } else if (H::element_bits == 16 && H::element_count == 512) { + // TODO + } else { + FAIL_CHECK("Unexpected size and/or count"); + } + } + + SECTION("key too short") { + H h1; + auto short_key = as_bytes(std::span{"0123456789abcde", 15}); + CHECK_THROWS(h1.set_key(short_key)); + } + + SECTION("key too long") { + H h1; + auto long_key = as_bytes(std::span{"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0", 65}); + CHECK_THROWS(h1.set_key(long_key)); + } + + SECTION("add with different keys fail") { + H h1; + h1.add(T::obj1); + + H h2; + h2.set_key(as_bytes(std::span{"0123456789abcdef"})); + h2.add(T::obj1); + CHECK_THROWS(h1.combine_add(h2)); + } + + SECTION("subtract with different keys fail") { + H h1; + h1.add(T::obj1); + + H h2; + h2.set_key(as_bytes(std::span{"0123456789abcdef"})); + h2.add(T::obj1); + CHECK_THROWS(h1.combine_remove(h2)); + } + + SECTION("keyed LtHashs same key equal") { + auto key = as_bytes(std::span{"0123456789abcdef"}); + H h1; + h1.set_key(key); + h1.add(T::obj1); + h1.add(T::obj2); + + H h2; + h2.set_key(key); + h2.add(T::obj1); + h2.add(T::obj2); + + CHECK(h1 == h2); + } + + SECTION("keyed LtHashes different keys not equal") { + auto key = as_bytes(std::span{"0123456789abcdef"}); + H h1; + h1.set_key(key); + h1.add(T::obj1); + h1.add(T::obj2); + + auto key2 = as_bytes(std::span{"1123456789abcdef"}); + H h2; + h1.set_key(key2); + h2.add(T::obj1); + h2.add(T::obj2); + + CHECK(h1 != h2); + + // Compare to unkeyed LtHash + H h3; + h3.add(T::obj1); + h3.add(T::obj2); + + CHECK(h1 != h3); + } + +#ifdef DICE_HASH_BENCHMARK_LTHASH_HIGHWAY_TARGRETS + hwy::SetSupportedTargetsForTest(0); +#endif +}