Unused Array Affects Program

Hi all,

I've been creating an implementation of SHA-512 in C++ to run on the Arduino Nano 33 IoT for fun, and I've recently run into some really strange behavior that I don't understand. I should note that I am relatively new to both C++ and Arduino, so it is very possible that I am overlooking something obvious.

The program uses an array of eight constant, unsigned, 64-bit integers called hInit:
const uint64_t hInit[HASH_WORDS] = {0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179};
where static const constexpr unsigned short HASH_WORDS = 8

hInit contains the initial values of a different array, h, which is used in many other places in the program:
uint64_t h[HASH_WORDS];

The first signs that something was off came when testing my program against some known SHA-512 test vectors. When the message length being fed into the program exceeded 811 bytes, the hash that was returned would be incorrect. When I probed further into why this was I discovered that when the array, h, was being initialized with the values of hInit, the last byte of the first 64-bit word was being zeroed out:

In The .ino:

unsigned long long messageLength = 112;
char message[messageLength] = {"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"};
hash.hashBytes(hashOut, message, messageLength);

In The SHA-512 Header File:

void SHA512Hash::hashBytes(char* hashOut, char* messageIn, unsigned long long messageBytes) {
	initialize(messageIn, messageBytes);

	uint64_t* message = new uint64_t[wordIndex + 1];

	buildMessage(message, messageIn, messageBytes);
	hashProcess(message);
	outputHash(hashOut);

	delete[] message;
}

Where The Problem First Shows Itself:

void SHA512Hash::initialize(char* messageIn, unsigned long long messageBytes) {
	for(unsigned short i = 0; i < HASH_WORDS; i += 1) {
		h[i] = hInit[i];

		Serial.print(h[i], HEX);
		Serial.print(' ');
	}
	Serial.println();

	...
}

The Serial Printout For h:
6A09E667F3BCC900 BB67AE8584CAA73B 3C6EF372FE94F82B A54FF53A5F1D36F1 510E527FADE682D1 9B05688C2B3E6C1F 1F83D9ABFB41BD6B 5BE0CD19137E2179

As you can see, h is equal to hInit except for the last byte of the first word. 6a09e667f3bcc908 does not equal 6A09E667F3BCC900. I found this rather strange and decided to temporarily fix the problem by hard-coding h = hInit as opposed to copying from an existing variable, as such:

void SHA512Hash::initialize(char* messageIn, unsigned long long messageBytes) {
	h[0] = 0x6a09e667f3bcc908;
	h[1] = 0xbb67ae8584caa73b;
	h[2] = 0x3c6ef372fe94f82b;
	h[3] = 0xa54ff53a5f1d36f1;
	h[4] = 0x510e527fade682d1;
	h[5] = 0x9b05688c2b3e6c1f;
	h[6] = 0x1f83d9abfb41bd6b;
	h[7] = 0x5be0cd19137e2179;

for(unsigned short i = 0; i < HASH_WORDS; i += 1) {
		Serial.print(h[i], HEX);
		Serial.print(' ');
	}
	Serial.println();

	...
}

This worked for a time, but when I tried deleting hInit the program would return a false hash, even though it was no longer being used anywhere in the program. With the hard-coded h, and hInit included, the program seems to run as intended. As soon as hInit is commented out, the wrong hash is returned. I am still trying to work out where exactly the program is "messing up," as h will print out the correct initial values when hard-coded, so it is going awry somewhere else.

If anyone knows what is going on here, I'd love to figure out the issue.

I found two other posts which seem to share some similarities to what I am running into:

It should be noted that I tried to update the IDE version I am running but that did not solve the issue.

Below is a copy of the entirety of the program:

The Header File:

#ifndef SHA512_H
#define SHA512_H

#include "Arduino.h" // DELETE ME!!
#include "HardwareSerial.h" // DELETE ME!!

#include <stdint.h>


class SHA512Hash {
private:
	static const constexpr unsigned short HASH_BYTES = 64;
	static const constexpr unsigned short WORD_CONVERSION = 8; // 8 bytes/1 word, 1 word/8 bytes.
	static const constexpr unsigned short HALF_WORD_CONVERSION = WORD_CONVERSION/2;
	static const constexpr unsigned short HASH_WORDS = HASH_BYTES/WORD_CONVERSION;
	static const constexpr unsigned short ROUNDS = 80;
	static const constexpr unsigned short BLOCK_BITS = 1024;
	static const constexpr unsigned short BIT_CONVERSION = 8; // 8 bits/1 byte, 1 byte/8 bits.
	static const constexpr unsigned short BLOCK_BYTES = BLOCK_BITS/BIT_CONVERSION;
	static const constexpr unsigned short APPEND_BIT = 1;
	static const constexpr unsigned short MESSAGE_LENGTH_BITS = 128;
	static const constexpr unsigned short WORD_BITS = 64;
	static const constexpr unsigned short HALF_WORD_BITS = WORD_BITS/2;
	static const constexpr unsigned short MESSAGE_LENGTH_WORDS = MESSAGE_LENGTH_BITS/WORD_BITS;
	static const constexpr unsigned short WORD_SHIFT = WORD_BITS - BIT_CONVERSION;
	static const constexpr unsigned short HALF_WORD_SHIFT = 24;
	static const constexpr unsigned short BLOCK_WORDS = BLOCK_BITS/WORD_BITS;

	const uint64_t hInit[HASH_WORDS] = {0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179}; // Derived from the first 64 bits of the fractional parts of the square roots of the first 8 primes.
	const uint64_t k[ROUNDS] = {
		0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 
		0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694, 
		0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, 
		0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70, 
		0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, 
		0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, 
		0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, 
		0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, 
		0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b, 
		0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817
	}; // Derived from the first 64 bits of the fractional parts of the cube roots of the first 80 primes.

	uint64_t h[HASH_WORDS];
	uint64_t a[HASH_WORDS]; // Working variables.

	unsigned short messageRemainderBits;
	unsigned short zeroBits;
	unsigned short zeroWords;

	unsigned long long messageWords;
	unsigned short wordRemainder;
	unsigned long long wordIndex;
	unsigned long long blockCount;
	uint32_t buildBuffer;

	uint64_t rotRBuffer;

	uint64_t w[ROUNDS]; // Message schedule array.
	uint64_t s0;
	uint64_t s1;
	uint64_t t0;
	uint64_t t1;

	void initialize(char*, unsigned long long);

	void buildMessage(uint64_t*, char*, unsigned long long);

	void rotR(uint64_t, unsigned short);
	void hashProcess(uint64_t*);

	void outputHash(char[HASH_BYTES]);
public:
	SHA512Hash();
	~SHA512Hash();

	void hashBytes(char[HASH_BYTES], char*, unsigned long long);
};


SHA512Hash::SHA512Hash() {

}


SHA512Hash::~SHA512Hash() {

}


void SHA512Hash::initialize(char* messageIn, unsigned long long messageBytes) {
/*	for(unsigned short i = 0; i < HASH_WORDS; i += 1) {
		h[i] = hInit[i];
		Serial.print(h[i], HEX);
		Serial.print(' ');
	}
	Serial.println();*/

// 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
// 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179

	h[0] = 0x6a09e667f3bcc908; // Why does this work, when the previous method failed? Maybe try the previous method without error messages?
	h[1] = 0xbb67ae8584caa73b;
	h[2] = 0x3c6ef372fe94f82b;
	h[3] = 0xa54ff53a5f1d36f1;
	h[4] = 0x510e527fade682d1;
	h[5] = 0x9b05688c2b3e6c1f;
	h[6] = 0x1f83d9abfb41bd6b;
	h[7] = 0x5be0cd19137e2179;

	for(unsigned short i = 0; i < 8; i += 1) {
		Serial.print(h[i], HEX);
		Serial.print(' ');
	}
	Serial.println();

	messageRemainderBits = (messageBytes % BLOCK_BYTES)*BIT_CONVERSION;
	zeroBits = BLOCK_BITS - ((((messageRemainderBits + APPEND_BIT + MESSAGE_LENGTH_BITS) - 1) % BLOCK_BITS) + 1); // 7 (messageBytes = 111) to 1023 (messageBytes = 112)
	zeroWords = zeroBits/WORD_BITS;

	messageWords = messageBytes/WORD_CONVERSION;
	wordRemainder = messageBytes % WORD_CONVERSION;

	wordIndex = messageWords + zeroWords + MESSAGE_LENGTH_WORDS;
	blockCount = (wordIndex + 1)/BLOCK_WORDS;
}


void SHA512Hash::buildMessage(uint64_t* message, char* messageIn, unsigned long long messageBytes) {
	for(unsigned long long i = 0; i < messageWords; i += 1) {
		message[i] = 0x0000000000000000;
		for(unsigned short j = 0; j < HALF_WORD_CONVERSION; j += 1) {
			message[i] |= (messageIn[(WORD_CONVERSION*i) + j] << (HALF_WORD_SHIFT - (BIT_CONVERSION*j)));
		}
		message[i] = message[i] << HALF_WORD_BITS;
		buildBuffer = 0x00000000;
		for(unsigned short j = HALF_WORD_CONVERSION; j < WORD_CONVERSION; j += 1) {
			buildBuffer |= (messageIn[(WORD_CONVERSION*i) + j] << (HALF_WORD_SHIFT - (BIT_CONVERSION*(j - HALF_WORD_CONVERSION))));
		}
		message[i] |= buildBuffer;
	}

	if(wordRemainder == 0) {
		message[messageWords] = 0x8000000000000000;
	} else {
		message[messageWords] = 0x0000000000000000;
		if(wordRemainder < HALF_WORD_CONVERSION) {
			for(unsigned short i = 0; i < wordRemainder; i += 1) {
				message[messageWords] |= (messageIn[(WORD_CONVERSION*messageWords) + i] << (HALF_WORD_SHIFT - (BIT_CONVERSION*i)));
			}
			message[messageWords] |= (0x80 << (HALF_WORD_SHIFT - (BIT_CONVERSION*wordRemainder)));
			message[messageWords] = message[messageWords] << HALF_WORD_BITS;

		} else if(wordRemainder == HALF_WORD_CONVERSION) {
			for(unsigned short i = 0; i < wordRemainder; i += 1) {
				message[messageWords] |= (messageIn[(WORD_CONVERSION*messageWords) + i] << (HALF_WORD_SHIFT - (BIT_CONVERSION*i)));
			}
			message[messageWords] = message[messageWords] << HALF_WORD_BITS;
			buildBuffer = 0x80000000;
			message[messageWords] |= buildBuffer;

		} else {
			for(unsigned short i = 0; i < HALF_WORD_CONVERSION; i += 1) {
				message[messageWords] |= (messageIn[(WORD_CONVERSION*messageWords) + i] << (HALF_WORD_SHIFT - (BIT_CONVERSION*i)));
			}
			message[messageWords] = message[messageWords] << HALF_WORD_BITS;
			buildBuffer = 0x00000000;
			for(unsigned short i = HALF_WORD_CONVERSION; i < wordRemainder; i += 1) {
				buildBuffer |= (messageIn[(WORD_CONVERSION*messageWords) + i] << (HALF_WORD_SHIFT - (BIT_CONVERSION*(i - HALF_WORD_CONVERSION))));
			}
			buildBuffer |= (0x80 << (HALF_WORD_SHIFT - (BIT_CONVERSION*(wordRemainder % HALF_WORD_CONVERSION))));
			message[messageWords] |= buildBuffer;
		}
	}

	for(unsigned long long i = 0; i < zeroWords; i += 1) {
		message[(messageWords + 1) + i] = 0x0000000000000000;
	}

	message[wordIndex - 1] = (uint64_t)messageBytes >> 61; // Has the effect of multiplying messageBytes by 8 (to convert from bytes to bits).
	message[wordIndex] = (uint64_t)messageBytes << 3;

	for(unsigned long long i = 0; i < (wordIndex + 1); i += 1) {
		if(message[i] == 0) {
			Serial.print('0');
		} else {
			Serial.print(message[i], HEX);
		}
		Serial.print(' ');
	}
	Serial.println();
}


void SHA512Hash::rotR(uint64_t n, unsigned short c) {
	rotRBuffer = ((n >> c) | (n << (WORD_BITS - c)));
}


void SHA512Hash::hashProcess(uint64_t* message) {
	for(unsigned long long b = 0; b < blockCount; b += 1) {
		for(unsigned short i = 0; i < BLOCK_WORDS; i += 1) {
			w[i] = message[(BLOCK_WORDS*b) + i];
			if(w[i] == 0) {
				Serial.print('0');
			} else {
				Serial.print(w[i], HEX);
			}
			Serial.print(' ');
		}
		Serial.println();

		for(unsigned short i = BLOCK_WORDS; i < ROUNDS; i += 1) {
			rotR(w[i - 15], 1);
			s0 = rotRBuffer;
			rotR(w[i - 15], 8);
			s0 ^= rotRBuffer;
			s0 ^= (w[i - 15] >> 7);

			rotR(w[i - 2], 19);
			s1 = rotRBuffer;
			rotR(w[i - 2], 61);
			s1 ^= rotRBuffer;
			s1 ^= (w[i - 2] >> 6);

			w[i] = w[i - 16] + s0 + w[i - 7] + s1;
		}

		for(unsigned short i = 0; i < HASH_WORDS; i += 1) {
			a[i] = h[i];
			Serial.print(a[i], HEX);
			Serial.print(' ');
		}
		Serial.println();

		for(unsigned short i = 0; i < ROUNDS; i += 1) {
			rotR(a[4], 14);
			s1 = rotRBuffer;
			rotR(a[4], 18);
			s1 ^= rotRBuffer;
			rotR(a[4], 41);
			s1 ^= rotRBuffer;

			s0 = (a[4] & a[5]) ^ ((~a[4]) & a[6]); // Used in place of ch.

			t0 = a[7] + s1 + s0 + k[i] + w[i];

			rotR(a[0], 28);
			s0 = rotRBuffer;
			rotR(a[0], 34);
			s0 ^= rotRBuffer;
			rotR(a[0], 39);
			s0 ^= rotRBuffer;

			s1 = (a[0] & a[1]) ^ (a[0] & a[2]) ^ (a[1] & a[2]); // Used in place of maj.
			t1 = s0 + s1;

			a[7] = a[6];
			a[6] = a[5];
			a[5] = a[4];
			a[4] = a[3] + t0;
			a[3] = a[2];
			a[2] = a[1];
			a[1] = a[0];
			a[0] = t0 + t1;
		}

		Serial.print("pre-add h: ");
		for(unsigned short i = 0; i < HASH_WORDS; i += 1) {
			Serial.print(h[i], HEX);
			Serial.print(' ');
		}
		Serial.println();

		Serial.print("pre-add a: ");
		for(unsigned short i = 0; i < HASH_WORDS; i += 1) {
			Serial.print(a[i], HEX);
			Serial.print(' ');
		}
		Serial.println();

		for(unsigned short i = 0; i < HASH_WORDS; i += 1) {
			h[i] += a[i];
			Serial.print(h[i], HEX);
			Serial.print(' ');
		}
		Serial.println();
	}
}


void SHA512Hash::outputHash(char* hashOut) {
	for(unsigned short i = 0; i < HASH_WORDS; i += 1) {
		for(unsigned short j = 0; j < WORD_CONVERSION; j += 1) {
			hashOut[(WORD_CONVERSION*i) + j] = h[i] >> (WORD_SHIFT - (BIT_CONVERSION*j));
		}
	}
}


void SHA512Hash::hashBytes(char* hashOut, char* messageIn, unsigned long long messageBytes) {
	initialize(messageIn, messageBytes);

	uint64_t* message = new uint64_t[wordIndex + 1];

	buildMessage(message, messageIn, messageBytes);
	hashProcess(message);
	outputHash(hashOut);

	delete[] message;
}

#endif

The .ino File:

#include "Arduino.h"
#include "HardwareSerial.h"

#include "src/include/SHA512.h"


void setup() {

	Serial.begin(9600);
	while(!Serial) {
		delay(250);
	}

	SHA512Hash hash;

	char cornedBeef[64];

	unsigned long long messageLength = 3;
	char message[messageLength] = {"abc"};
	hash.hashBytes(cornedBeef, message, messageLength);
	for(unsigned short i = 0; i < 64; i += 1) {
		if(cornedBeef[i] > 0x0f) {
			Serial.print(cornedBeef[i], HEX);
		} else {
			Serial.print('0');
			Serial.print(cornedBeef[i], HEX);
		}
	}
	Serial.println('\n');

	messageLength = 0;
	char message1[messageLength] = {""};
	hash.hashBytes(cornedBeef, message1, messageLength);
	for(unsigned short i = 0; i < 64; i += 1) {
		if(cornedBeef[i] > 0x0f) {
			Serial.print(cornedBeef[i], HEX);
		} else {
			Serial.print('0');
			Serial.print(cornedBeef[i], HEX);
		}
	}
	Serial.println('\n');

	messageLength = 56;
	char message2[messageLength] = {"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"};
	hash.hashBytes(cornedBeef, message2, messageLength);
	for(unsigned short i = 0; i < 64; i += 1) {
		if(cornedBeef[i] > 0x0f) {
			Serial.print(cornedBeef[i], HEX);
		} else {
			Serial.print('0');
			Serial.print(cornedBeef[i], HEX);
		}
	}
	Serial.println('\n');

	messageLength = 111;
	char message3[messageLength] = {"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrst"};
	hash.hashBytes(cornedBeef, message3, messageLength);
	for(unsigned short i = 0; i < 64; i += 1) {
		if(cornedBeef[i] > 0x0f) {
			Serial.print(cornedBeef[i], HEX);
		} else {
			Serial.print('0');
			Serial.print(cornedBeef[i], HEX);
		}
	}
	Serial.println('\n');

	messageLength = 112;
	char message4[messageLength] = {"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"};
	hash.hashBytes(cornedBeef, message4, messageLength);
	for(unsigned short i = 0; i < 64; i += 1) {
		if(cornedBeef[i] > 0x0f) {
			Serial.print(cornedBeef[i], HEX);
		} else {
			Serial.print('0');
			Serial.print(cornedBeef[i], HEX);
		}
	}
	Serial.println('\n');
/*
	messageLength = 240;
	char message5[messageLength] = {"apovabapouea198asa8d91f15efgpoasohidoiea89a9da8f1a32sdf4ado8ivha1a6s8e68868668asefadhajfshacasedlkjadADAIPHLKJAzzzzlakdf97165g00abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"};
	hash.hashBytes(cornedBeef, message5, messageLength);
	for(unsigned short i = 0; i < 64; i += 1) {
		if(cornedBeef[i] > 0x0f) {
			Serial.print(cornedBeef[i], HEX);
		} else {
			Serial.print('0');
			Serial.print(cornedBeef[i], HEX);
		}
	}
	Serial.println('\n');
*/
}


void loop() {}

Your string literals are too long for the arrays you're trying to store them into, the compiler should have warned you about that, if it didn't please enable all warnings in the IDE.

See https://godbolt.org/z/74KxfY4Tf:


<source>: In function 'int main()':
<source>:322:45: warning: array subscript 'const char [4][0]' is partly outside array bounds of 'unsigned char [3]' [-Warray-bounds]
  322 |         char message[messageLength] = {"abc"};
      |                                             ^
<source>:322:14: note: while referencing 'message.88'
  322 |         char message[messageLength] = {"abc"};
      |              ^~~~~~~

/app/example.cpp:322:38: runtime error: index 3 out of bounds for type 'char [*]'
/app/example.cpp:322:38: runtime error: store to address 0x7ffcd1a3ff40 with insufficient space for an object of type '<unknown>'
0x7ffcd1a3ff40: note: pointer points here
 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00
              ^ 
=================================================================
==1==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffcd1a3ff40 at pc 0x00000040c010 bp 0x7ffcd1a3fce0 sp 0x7ffcd1a3fcd8
WRITE of size 4 at 0x7ffcd1a3ff40 thread T0
    #0 0x40c00f in main /app/example.cpp:322
    #1 0x7f427d4db0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
    #2 0x40c1cd in _start (/app/output.s+0x40c1cd)

Address 0x7ffcd1a3ff40 is located in stack of thread T0 at offset 464 in frame
    #0 0x4024cf in main /app/example.cpp:316

  This frame has 32 object(s):
    [48, 49) '<unknown>'
    [64, 65) '<unknown>'
    [80, 81) '<unknown>'
    [96, 97) '<unknown>'
    [112, 113) '<unknown>'
    [128, 129) '<unknown>'
    [144, 145) '<unknown>'
    [160, 161) '<unknown>'
    [176, 177) '<unknown>'
    [192, 193) '<unknown>'
    [208, 209) '__c' (line 5)
    [224, 225) '__c' (line 5)
    [240, 241) '__c' (line 5)
    [256, 257) '__c' (line 5)
    [272, 273) '__c' (line 5)
    [288, 289) 'message1.89' (line 335)
    [304, 305) '__c' (line 8)
    [320, 321) '__c' (line 8)
    [336, 337) '__c' (line 8)
    [352, 353) '__c' (line 8)
    [368, 369) '__c' (line 8)
    [384, 385) '__c' (line 8)
    [400, 401) '__c' (line 8)
    [416, 417) '__c' (line 8)
    [432, 433) '__c' (line 8)
    [448, 449) '__c' (line 8)
    [464, 467) 'message.88' (line 322) <== Memory access at offset 464 partially overflows this variable
    [480, 536) 'message2.299' (line 348)
    [576, 640) 'cornedBeef' (line 319)
    [672, 783) 'message3.300' (line 361)
    [816, 928) 'message4.301' (line 374)
    [960, 2520) 'hash' (line 317)

See also https://en.cppreference.com/w/cpp/language/aggregate_initialization#Character_arrays:

In C, character array of size one less than the size of the string literal may be initialized from a string literal; the resulting array is not null-terminated. This is not allowed in C++.

In reference to reply #2, and not having looked at your code yet, my first impression was that you were writing past the end of whatever is stored immediately before hInit in memory. Integers such as the uint64_t are stored with the least significant byte in the lowest memory addrerss.

To clarify, the object before hInint in memory is often the object after it in the code, as the stack grows downwards: objects that come first in the code have an address that's higher than objects that come later (of course the compiler is free to reorder local variables if it needs to).

For example, https://godbolt.org/z/fqbM8T4eM:

int main() {
    uint32_t i = 0xDEADBEEF;
    hexdump(i);  // ef be ad de 
    uint8_t a[4] { 0x11, 0x22, 0x33, 0x44 };
    std::cout << "Full stack: "; hexdump(a, 8);
    a[4] = 0x55; // oops, out of bounds
    hexdump(i);  // 55 be ad de 
    std::cout << "Full stack: "; hexdump(a, 8);
}
ef be ad de 
Full stack: 11 22 33 44 ef be ad de 
55 be ad de 
Full stack: 11 22 33 44 55 be ad de 

In the code in the first post, it seems to be the null terminator of message4 that's overwriting the first byte of hash.hInit.

Edit: it should be noted that the root cause here is the use of evil variable-length arrays (VLAs).
If you make messageLength a constant, the compiler (rightfully) gives an error:

Bad, VLA

size_t messageLength4 = 112;
char message4[messageLength4] = {"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"};

Just warnings, only at high optimization levels:

<source>:374:14: warning: variable length array 'message4' is used [-Wvla]
  374 |         char message4[messageLength4] = {"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"};
      |              ^~~~~~~~
<source>:374:156: warning: array subscript 'const char [113][0]' is partly outside array bounds of 'unsigned char [112]' [-Warray-bounds]
  374 |         char message4[messageLength4] = {"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"};
      |                                                                                                                                                            ^
<source>:374:14: note: while referencing 'message4.301'
  374 |         char message4[messageLength4] = {"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"};
      |              ^~~~~~~~

Good, no VLA

const size_t messageLength4 = 112;
char message4[messageLength4] = {"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"};

Compilation error instead of run-time bug:

<source>:374:42: error: initializer-string for 'char [112]' is too long [-fpermissive]
  374 |         char message4[messageLength4] = {"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"};
      |

I'd strongly encourage you to add the option -Werror=vla to your build flags (e.g. in platform.txt if you're using the Arduino IDE).

This appears to be the reason, thanks for your help and advice.

Looks like your hunch was right, thanks for helping with this.