From e10b39894902405f047aaafd15bd77a050e6ac23 Mon Sep 17 00:00:00 2001 From: Rhys Weatherley Date: Sat, 14 Mar 2015 08:47:03 +1000 Subject: [PATCH] BLAKE2b hash algorithm --- doc/crypto.dox | 13 +- doc/mainpage.dox | 2 +- libraries/Crypto/BLAKE2b.cpp | 246 ++++++++++++++++++ libraries/Crypto/BLAKE2b.h | 58 +++++ .../examples/TestBLAKE2b/TestBLAKE2b.ino | 193 ++++++++++++++ 5 files changed, 505 insertions(+), 7 deletions(-) create mode 100644 libraries/Crypto/BLAKE2b.cpp create mode 100644 libraries/Crypto/BLAKE2b.h create mode 100644 libraries/Crypto/examples/TestBLAKE2b/TestBLAKE2b.ino diff --git a/doc/crypto.dox b/doc/crypto.dox index 2f0ddd70..f3cb600d 100644 --- a/doc/crypto.dox +++ b/doc/crypto.dox @@ -29,14 +29,14 @@ \li Block ciphers: AES128, AES192, AES256 \li Block cipher modes: CTR, CFB, CBC, OFB \li Stream ciphers: ChaCha -\li Hash algorithms: SHA1, SHA256, SHA512, BLAKE2s +\li Hash algorithms: SHA1, SHA256, SHA512, BLAKE2s, BLAKE2b \li Public key algorithms: Curve25519 \li Random number generation: \link RNGClass RNG\endlink, TransistorNoiseSource All cryptographic algorithms have been optimized for 8-bit Arduino platforms like the Uno. Memory usage is also reduced, particularly for SHA1, SHA256, and SHA512 which save 256, 192, and 512 bytes respectively over traditional -implementations. For other algorithms, static sbox tables and the like are +implementations. For all algorithms, static sbox tables and the like are placed into program memory to further reduce data memory usage. ChaCha with 20 rounds and 256-bit keys is the recommended @@ -45,10 +45,10 @@ constant-time, and much more secure. AES128, AES192, and AES256 are provided for use in applications where compatibility with other systems is desirable. -BLAKE2s is a variation on the ChaCha stream cipher, designed for hashing, -with a 256-bit hash output. It is intended as a high performance drop-in -replacement for SHA256 for when speed is critical but exact SHA256 -compatibility is not. +BLAKE2s and BLAKE2b are variations on the ChaCha stream cipher, designed for +hashing, with 256-bit and 512-bit hash outputs respectively. They are +intended as high performance replacements for SHA256 and SHA512 for when +speed is critical but exact bit-compatibility of hash values is not. \section crypto_performance Performance @@ -67,6 +67,7 @@ Ardunino Mega 2560 running at 16 MHz are similar: SHA25643.85us 106 SHA512123.25us 210 BLAKE2s18.54us 170 +BLAKE2b50.59us 338 Where a cipher supports more than one key size (such as ChaCha), the values diff --git a/doc/mainpage.dox b/doc/mainpage.dox index a8940535..53f153fd 100644 --- a/doc/mainpage.dox +++ b/doc/mainpage.dox @@ -93,7 +93,7 @@ realtime clock and the LCD library to implement an alarm clock. \li Block ciphers: AES128, AES192, AES256 \li Block cipher modes: CTR, CFB, CBC, OFB \li Stream ciphers: ChaCha -\li Hash algorithms: SHA1, SHA256, SHA512, BLAKE2s +\li Hash algorithms: SHA1, SHA256, SHA512, BLAKE2s, BLAKE2b \li Public key algorithms: Curve25519 \li Random number generation: \link RNGClass RNG\endlink, TransistorNoiseSource diff --git a/libraries/Crypto/BLAKE2b.cpp b/libraries/Crypto/BLAKE2b.cpp new file mode 100644 index 00000000..ec0fbe64 --- /dev/null +++ b/libraries/Crypto/BLAKE2b.cpp @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2015 Southern Storm Software, Pty Ltd. + * + * 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 "BLAKE2b.h" +#include "Crypto.h" +#include "utility/EndianUtil.h" +#include "utility/RotateUtil.h" +#include "utility/ProgMemUtil.h" +#include + +/** + * \class BLAKE2b BLAKE2b.h + * \brief BLAKE2b hash algorithm. + * + * BLAKE2b is a variation on the ChaCha stream cipher, designed for hashing, + * with a 512-bit hash output. It is intended as a high performance + * replacement for SHA512 for when speed is critical but exact SHA512 + * compatibility is not. + * + * Reference: https://blake2.net/ + * + * \sa BLAKE2s, SHA512 + */ + +/** + * \brief Constructs a BLAKE2b hash object. + */ +BLAKE2b::BLAKE2b() +{ + reset(); +} + +/** + * \brief Destroys this BLAKE2b hash object after clearing + * sensitive information. + */ +BLAKE2b::~BLAKE2b() +{ + clean(state); +} + +size_t BLAKE2b::hashSize() const +{ + return 64; +} + +size_t BLAKE2b::blockSize() const +{ + return 128; +} + +// Initialization vectors for BLAKE2b. +#define BLAKE2b_IV0 0x6a09e667f3bcc908ULL +#define BLAKE2b_IV1 0xbb67ae8584caa73bULL +#define BLAKE2b_IV2 0x3c6ef372fe94f82bULL +#define BLAKE2b_IV3 0xa54ff53a5f1d36f1ULL +#define BLAKE2b_IV4 0x510e527fade682d1ULL +#define BLAKE2b_IV5 0x9b05688c2b3e6c1fULL +#define BLAKE2b_IV6 0x1f83d9abfb41bd6bULL +#define BLAKE2b_IV7 0x5be0cd19137e2179ULL + +void BLAKE2b::reset() +{ + state.h[0] = BLAKE2b_IV0 ^ 0x01010040; // Default output length of 64. + state.h[1] = BLAKE2b_IV1; + state.h[2] = BLAKE2b_IV2; + state.h[3] = BLAKE2b_IV3; + state.h[4] = BLAKE2b_IV4; + state.h[5] = BLAKE2b_IV5; + state.h[6] = BLAKE2b_IV6; + state.h[7] = BLAKE2b_IV7; + state.chunkSize = 0; + state.finalized = false; + state.lengthLow = 0; + state.lengthHigh = 0; +} + +/** + * \brief Resets the hash ready for a new hashing process with a specified + * output length. + * + * \param outputLength The output length to use for the final hash in bytes, + * between 1 and 64. + */ +void BLAKE2b::reset(uint8_t outputLength) +{ + state.h[0] = BLAKE2b_IV0 ^ 0x01010000 ^ outputLength; + state.h[1] = BLAKE2b_IV1; + state.h[2] = BLAKE2b_IV2; + state.h[3] = BLAKE2b_IV3; + state.h[4] = BLAKE2b_IV4; + state.h[5] = BLAKE2b_IV5; + state.h[6] = BLAKE2b_IV6; + state.h[7] = BLAKE2b_IV7; + state.chunkSize = 0; + state.finalized = false; + state.lengthLow = 0; + state.lengthHigh = 0; +} + +void BLAKE2b::update(const void *data, size_t len) +{ + // Reset the hashing process if finalize() was called previously. + if (state.finalized) + reset(); + + // Break the input up into 1024-bit chunks and process each in turn. + const uint8_t *d = (const uint8_t *)data; + while (len > 0) { + if (state.chunkSize == 128) { + // Previous chunk was full and we know that it wasn't the + // last chunk, so we can process it now with f0 set to zero. + processChunk(0); + state.chunkSize = 0; + } + uint8_t size = 128 - state.chunkSize; + if (size > len) + size = len; + memcpy(((uint8_t *)state.m) + state.chunkSize, d, size); + state.chunkSize += size; + uint64_t temp = state.lengthLow; + state.lengthLow += size; + if (state.lengthLow < temp) + ++state.lengthHigh; + len -= size; + d += size; + } +} + +void BLAKE2b::finalize(void *hash, size_t len) +{ + // Finalize the hash if necessary. + if (!state.finalized) { + // Pad the last chunk and hash it with f0 set to all-ones. + memset(((uint8_t *)state.m) + state.chunkSize, 0, 128 - state.chunkSize); + processChunk(0xFFFFFFFFFFFFFFFFULL); + + // Convert the hash into little-endian in the message buffer. + for (uint8_t posn = 0; posn < 8; ++posn) + state.m[posn] = htole64(state.h[posn]); + state.finalized = true; + } + + // Copy the hash to the caller's return buffer. + if (len > 64) + len = 64; + memcpy(hash, state.m, len); +} + +void BLAKE2b::clear() +{ + clean(state); + reset(); +} + +// Permutation on the message input state for BLAKE2b. +static const uint8_t sigma[12][16] PROGMEM = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0}, + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, +}; + +// Perform a BLAKE2b quarter round operation. +#define quarterRound(a, b, c, d, i) \ + do { \ + uint64_t _b = (b); \ + uint64_t _a = (a) + _b + state.m[pgm_read_byte(&(sigma[index][2 * (i)]))]; \ + uint64_t _d = rightRotate32_64((d) ^ _a); \ + uint64_t _c = (c) + _d; \ + _b = rightRotate24_64(_b ^ _c); \ + _a += _b + state.m[pgm_read_byte(&(sigma[index][2 * (i) + 1]))]; \ + (d) = _d = rightRotate16_64(_d ^ _a); \ + _c += _d; \ + (a) = _a; \ + (b) = rightRotate63_64(_b ^ _c); \ + (c) = _c; \ + } while (0) + +void BLAKE2b::processChunk(uint64_t f0) +{ + uint8_t index; + + // Byte-swap the message buffer into little-endian if necessary. +#if !defined(CRYPTO_LITTLE_ENDIAN) + for (index = 0; index < 16; ++index) + state.m[index] = le64toh(state.m[index]); +#endif + + // Format the block to be hashed. + memcpy(state.v, state.h, sizeof(state.h)); + state.v[8] = BLAKE2b_IV0; + state.v[9] = BLAKE2b_IV1; + state.v[10] = BLAKE2b_IV2; + state.v[11] = BLAKE2b_IV3; + state.v[12] = BLAKE2b_IV4 ^ state.lengthLow; + state.v[13] = BLAKE2b_IV5 ^ state.lengthHigh; + state.v[14] = BLAKE2b_IV6 ^ f0; + state.v[15] = BLAKE2b_IV7; + + // Perform the 12 BLAKE2b rounds. + for (index = 0; index < 12; ++index) { + // Column round. + quarterRound(state.v[0], state.v[4], state.v[8], state.v[12], 0); + quarterRound(state.v[1], state.v[5], state.v[9], state.v[13], 1); + quarterRound(state.v[2], state.v[6], state.v[10], state.v[14], 2); + quarterRound(state.v[3], state.v[7], state.v[11], state.v[15], 3); + + // Diagonal round. + quarterRound(state.v[0], state.v[5], state.v[10], state.v[15], 4); + quarterRound(state.v[1], state.v[6], state.v[11], state.v[12], 5); + quarterRound(state.v[2], state.v[7], state.v[8], state.v[13], 6); + quarterRound(state.v[3], state.v[4], state.v[9], state.v[14], 7); + } + + // Combine the new and old hash values. + for (index = 0; index < 8; ++index) + state.h[index] ^= (state.v[index] ^ state.v[index + 8]); +} diff --git a/libraries/Crypto/BLAKE2b.h b/libraries/Crypto/BLAKE2b.h new file mode 100644 index 00000000..781b6761 --- /dev/null +++ b/libraries/Crypto/BLAKE2b.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 Southern Storm Software, Pty Ltd. + * + * 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. + */ + +#ifndef CRYPTO_BLAKE2B_H +#define CRYPTO_BLAKE2B_H + +#include "Hash.h" + +class BLAKE2b : public Hash +{ +public: + BLAKE2b(); + virtual ~BLAKE2b(); + + size_t hashSize() const; + size_t blockSize() const; + + void reset(); + void reset(uint8_t outputLength); + void update(const void *data, size_t len); + void finalize(void *hash, size_t len); + + void clear(); + +private: + struct { + uint64_t h[8]; + uint64_t m[16]; + uint64_t v[16]; + uint8_t chunkSize; + bool finalized; + uint64_t lengthLow; + uint64_t lengthHigh; + } state; + + void processChunk(uint64_t f0); +}; + +#endif diff --git a/libraries/Crypto/examples/TestBLAKE2b/TestBLAKE2b.ino b/libraries/Crypto/examples/TestBLAKE2b/TestBLAKE2b.ino new file mode 100644 index 00000000..adfe1418 --- /dev/null +++ b/libraries/Crypto/examples/TestBLAKE2b/TestBLAKE2b.ino @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2015 Southern Storm Software, Pty Ltd. + * + * 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. + */ + +/* +This example runs tests on the BLAKE2b implementation to verify correct behaviour. +*/ + +#include +#include +#include + +#define HASH_SIZE 64 + +struct TestHashVector +{ + const char *name; + const char *data; + uint8_t hash[HASH_SIZE]; +}; + +// Test vectors generated with the reference implementation of BLAKE2b. +static TestHashVector const testVectorBLAKE2b_1 = { + "BLAKE2b #1", + "", + {0x78, 0x6a, 0x02, 0xf7, 0x42, 0x01, 0x59, 0x03, + 0xc6, 0xc6, 0xfd, 0x85, 0x25, 0x52, 0xd2, 0x72, + 0x91, 0x2f, 0x47, 0x40, 0xe1, 0x58, 0x47, 0x61, + 0x8a, 0x86, 0xe2, 0x17, 0xf7, 0x1f, 0x54, 0x19, + 0xd2, 0x5e, 0x10, 0x31, 0xaf, 0xee, 0x58, 0x53, + 0x13, 0x89, 0x64, 0x44, 0x93, 0x4e, 0xb0, 0x4b, + 0x90, 0x3a, 0x68, 0x5b, 0x14, 0x48, 0xb7, 0x55, + 0xd5, 0x6f, 0x70, 0x1a, 0xfe, 0x9b, 0xe2, 0xce} +}; +static TestHashVector const testVectorBLAKE2b_2 = { + "BLAKE2b #2", + "abc", + {0xba, 0x80, 0xa5, 0x3f, 0x98, 0x1c, 0x4d, 0x0d, + 0x6a, 0x27, 0x97, 0xb6, 0x9f, 0x12, 0xf6, 0xe9, + 0x4c, 0x21, 0x2f, 0x14, 0x68, 0x5a, 0xc4, 0xb7, + 0x4b, 0x12, 0xbb, 0x6f, 0xdb, 0xff, 0xa2, 0xd1, + 0x7d, 0x87, 0xc5, 0x39, 0x2a, 0xab, 0x79, 0x2d, + 0xc2, 0x52, 0xd5, 0xde, 0x45, 0x33, 0xcc, 0x95, + 0x18, 0xd3, 0x8a, 0xa8, 0xdb, 0xf1, 0x92, 0x5a, + 0xb9, 0x23, 0x86, 0xed, 0xd4, 0x00, 0x99, 0x23} +}; +static TestHashVector const testVectorBLAKE2b_3 = { + "BLAKE2b #3", + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + {0x72, 0x85, 0xff, 0x3e, 0x8b, 0xd7, 0x68, 0xd6, + 0x9b, 0xe6, 0x2b, 0x3b, 0xf1, 0x87, 0x65, 0xa3, + 0x25, 0x91, 0x7f, 0xa9, 0x74, 0x4a, 0xc2, 0xf5, + 0x82, 0xa2, 0x08, 0x50, 0xbc, 0x2b, 0x11, 0x41, + 0xed, 0x1b, 0x3e, 0x45, 0x28, 0x59, 0x5a, 0xcc, + 0x90, 0x77, 0x2b, 0xdf, 0x2d, 0x37, 0xdc, 0x8a, + 0x47, 0x13, 0x0b, 0x44, 0xf3, 0x3a, 0x02, 0xe8, + 0x73, 0x0e, 0x5a, 0xd8, 0xe1, 0x66, 0xe8, 0x88} +}; +static TestHashVector const testVectorBLAKE2b_4 = { + "BLAKE2b #4", + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" + "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", + {0xce, 0x74, 0x1a, 0xc5, 0x93, 0x0f, 0xe3, 0x46, + 0x81, 0x11, 0x75, 0xc5, 0x22, 0x7b, 0xb7, 0xbf, + 0xcd, 0x47, 0xf4, 0x26, 0x12, 0xfa, 0xe4, 0x6c, + 0x08, 0x09, 0x51, 0x4f, 0x9e, 0x0e, 0x3a, 0x11, + 0xee, 0x17, 0x73, 0x28, 0x71, 0x47, 0xcd, 0xea, + 0xee, 0xdf, 0xf5, 0x07, 0x09, 0xaa, 0x71, 0x63, + 0x41, 0xfe, 0x65, 0x24, 0x0f, 0x4a, 0xd6, 0x77, + 0x7d, 0x6b, 0xfa, 0xf9, 0x72, 0x6e, 0x5e, 0x52} +}; + +BLAKE2b blake2b; + +byte buffer[128]; + +bool testHash_N(Hash *hash, const struct TestHashVector *test, size_t inc) +{ + size_t size = strlen(test->data); + size_t posn, len; + uint8_t value[HASH_SIZE]; + + for (posn = 0; posn < size; posn += inc) { + len = size - posn; + if (len > inc) + len = inc; + hash->update(test->data + posn, len); + } + hash->finalize(value, sizeof(value)); + if (memcmp(value, test->hash, sizeof(value)) != 0) + return false; + + // Try again to make sure the hash resets. + for (posn = 0; posn < size; posn += inc) { + len = size - posn; + if (len > inc) + len = inc; + hash->update(test->data + posn, len); + } + hash->finalize(value, sizeof(value)); + if (memcmp(value, test->hash, sizeof(value)) != 0) + return false; + + return true; +} + +void testHash(Hash *hash, const struct TestHashVector *test) +{ + bool ok; + + Serial.print(test->name); + Serial.print(" ... "); + + ok = testHash_N(hash, test, strlen(test->data)); + ok &= testHash_N(hash, test, 1); + ok &= testHash_N(hash, test, 2); + ok &= testHash_N(hash, test, 5); + ok &= testHash_N(hash, test, 8); + ok &= testHash_N(hash, test, 13); + ok &= testHash_N(hash, test, 16); + ok &= testHash_N(hash, test, 24); + ok &= testHash_N(hash, test, 63); + ok &= testHash_N(hash, test, 64); + + if (ok) + Serial.println("Passed"); + else + Serial.println("Failed"); +} + +void perfHash(Hash *hash) +{ + unsigned long start; + unsigned long elapsed; + int count; + + Serial.print("Hashing ... "); + + for (size_t posn = 0; posn < sizeof(buffer); ++posn) + buffer[posn] = (uint8_t)posn; + + hash->reset(); + start = micros(); + for (count = 0; count < 1000; ++count) { + hash->update(buffer, sizeof(buffer)); + } + elapsed = micros() - start; + + Serial.print(elapsed / (sizeof(buffer) * 1000.0)); + Serial.print("us per byte, "); + Serial.print((sizeof(buffer) * 1000.0 * 1000000.0) / elapsed); + Serial.println(" bytes per second"); +} + +void setup() +{ + Serial.begin(9600); + + Serial.println(); + + Serial.println("Test Vectors:"); + testHash(&blake2b, &testVectorBLAKE2b_1); + testHash(&blake2b, &testVectorBLAKE2b_2); + testHash(&blake2b, &testVectorBLAKE2b_3); + testHash(&blake2b, &testVectorBLAKE2b_4); + + Serial.println(); + + Serial.println("Performance Tests:"); + perfHash(&blake2b); +} + +void loop() +{ +}