Last active
June 23, 2022 02:02
-
-
Save davezarzycki/86be758beb05afcac0b90f28255218e2 to your computer and use it in GitHub Desktop.
CRC examples with poly: 0x(1)E691A23E28619DF3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Copyright (c) 2022 David Zarzycki | |
| // | |
| // 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. | |
| #include <assert.h> | |
| #include <stdint.h> | |
| #include <immintrin.h> | |
| // This sample code computes a 64b hash using a specialized CRC that is | |
| // optimized for data less than 192 bits and provides a Hamming distance of 17 | |
| // over 255 bits (i.e. including the CRC result itself). What does that mean | |
| // for hashing? | |
| // | |
| // 1) Given a random value <192 bits, any combination of changes <17 bits will | |
| // always result in a different hash value. That's >2^76 potential changes! | |
| // 2) Given a random value <192 bits, all combinations of changes <9 bits will | |
| // always result in a collision-free set. That's >2^45 hash values! In | |
| // contrast, a hash function that aspires to random output will probably | |
| // start having collisions around 2^32 values in the set. | |
| // 3) Smaller changes result in bigger changes in the hash value. For example, | |
| // changing any combination of 7 bits guarantees that 10+ bits change in | |
| // the CRC result. | |
| // | |
| // | |
| // Details to know: | |
| // | |
| // 1) Hashing an empty/zero buffer will result in a hash value of zero. | |
| // 2) You're not implementing hardware so you can arguably ignore the tricks | |
| // used to detect extra bits at the start of data being checksummed. | |
| // 3) That being said, if trailing zeros are semantically meaningful, you might | |
| // want to set a bit right after the last zero in the data. | |
| // 4) You can push this hash to <=192 bits instead of <192 bits, but the | |
| // caveat is that extra bit will only flip the top or bottom bit of the | |
| // CRC result depending on how the CRC is computed. | |
| // 5) Do not nest this hash. I.e. this: crc64z8({ x, crc64z8(buffer) }) because | |
| // this behaves like buffer concatenation, which is incompatible with the | |
| // core design of this CRC. Also avoid this: crc64z8({ crc64z8(buffer), x }) | |
| // I haven't studied how this behaves but the behavior is not good. | |
| // 6) CRCs are ideal for hash flood attacks, so you really should not use this | |
| // with untrusted data. (I think creating a random permute pattern at launch | |
| // and then permutting the data before the CRC can solve that problem but I | |
| // haven't tested this idea.) | |
| // | |
| // And for the curious, the higher Hamming distance message lengths (not | |
| // including the CRC itself): | |
| // | |
| // HD 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | |
| // Bits 48 32 32 29 16 16 7 7 7 7 6 5 5 1 1 1 | |
| namespace { | |
| inline uint64_t rdrand() { | |
| unsigned long long result; | |
| while (__builtin_ia32_rdrand64_step(&result) == 0) { | |
| __builtin_ia32_pause(); | |
| } | |
| return result; | |
| } | |
| void update_rem(uint64_t &rem, uint64_t poly) { | |
| if (int64_t(rem) < 0) { | |
| rem = (rem << 1) ^ poly; | |
| } else { | |
| rem <<= 1; | |
| } | |
| } | |
| const __m128i inv_and_poly = { int64_t(0x9EB373F963A012AAULL), int64_t(0xE691A23E28619DF3ULL) }; | |
| const __m128i fold_magic = { 0x15014A680736459ELL, 0x67B9985917234E53LL }; | |
| template<unsigned N> | |
| uint64_t crc64z8(const uint64_t (&buffer)[N]) { | |
| __m128i fold = {}; | |
| static_assert(N > 0 && N < 4); | |
| switch (N) { | |
| case 3: { | |
| __m128i temp = { int64_t(buffer[2]), 0 }; | |
| fold = _mm_clmulepi64_si128(temp, fold_magic, 0x10); | |
| [[fallthrough]]; | |
| } | |
| case 2: { | |
| __m128i temp = { int64_t(buffer[0]), int64_t(buffer[1]) }; | |
| fold ^= _mm_clmulepi64_si128(temp, fold_magic, 0x01) ^ _mm_slli_si128(temp, 8); | |
| break; | |
| } | |
| default: { | |
| fold = { 0, int64_t(buffer[0]) }; | |
| break; | |
| } | |
| } | |
| auto first = _mm_clmulepi64_si128(inv_and_poly, fold, 0x10) ^ fold; | |
| auto result = _mm_clmulepi64_si128(inv_and_poly, first, 0x11); | |
| if (N > 1) { | |
| result ^= fold; | |
| } | |
| return uint64_t(result[0]); | |
| } | |
| }; | |
| int main() { | |
| uint64_t buffer[3] = { rdrand(), rdrand(), rdrand() }; | |
| const auto poly = uint64_t(inv_and_poly[1]); | |
| // The literal is in Koopman notation which is nice because the notation | |
| // self-documents the CRC size at the cost of implying the +1 coefficient. | |
| assert(poly == ((0xF348D11F1430CEF9ull << 1) | 1)); | |
| // First compute the result one bit at a time. | |
| uint64_t rem = poly; | |
| uint64_t crc1 = 0; | |
| for (unsigned i = 0; i < 64*3; i++) { | |
| if (buffer[i / 64] & (1ull << (i % 64))) { | |
| crc1 ^= rem; | |
| } | |
| update_rem(rem, poly); | |
| } | |
| assert(rem == 2); | |
| // Now compute the result one "round" at a time. | |
| uint64_t crc2 = 0; | |
| for (int i = 2; i >= 0; i--) { | |
| crc2 = crc64z8({ crc2 ^ buffer[i] }); | |
| } | |
| // Finally compute the result as a buffer via folding tricks. | |
| uint64_t crc3 = crc64z8(buffer); | |
| assert(crc1 == crc2); | |
| assert(crc1 == crc3); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment