Code compiled with Arduino IDE returns different value than (mostly) same code with g++ compiler

I am attempting to create a SHA256 hashing library for a project I am working on, and I started off writing the code completely in cpp. I then converted the cpp file to be compatible with Arduino by making a .h file and writing a little bit of code in the .ino file for Serial printing.

The cpp code outputs the correct hash (according to this site) when compiled with the g++ compiler and run from a mac computer, but the Arduino-compiled sketch run on an Arduino Nano does not.

Input: "abcdefg"
Correct hash / g++ output from mac: "7d1a54127b222502f5b79b5fb0803061152a44f92b37e23c6527baf665d4da9a"
Arduino Nano output:
"ee2ea23488de8c2cd89c5e70854573af"

The fact that the Nano output is 32 characters as opposed to 64 seems suspicious, but nothing in the calculation code is different between both cases. I tried putting the include statements for assorted standard library stuff in the Arduino cpp file even though they already come bundled with Arduino, and they did not make a difference. Could there possibly be a difference in the way the Arduino compiles the code (even though it is also g++)? If not, might there be a variation in the way certain code is performed on an Arduino as opposed to a Mac that caused these differing outputs?

Here is the .cpp code run from a Mac and compiled with g++:

#include <stdint.h>
#include <iostream>
#include <math.h>
using namespace std;

// Initialize hash values (first 32 bits of the fractional parts of the square roots of the first 8 primes)
uint32_t hsh[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};

// Initialize keys (first 32 bits of the fractional parts of the cube roots of the first 64 primes)
uint32_t k[64] = {
  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
  0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
  0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
  0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
  0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
  0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
  0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
  0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};

struct ChunkData {
  uint32_t numOfChunks;
  uint32_t* chunks;
};

// UTILITY FUNCTIONS
uint32_t rrot(uint32_t input, uint8_t shift) {
  return (input >> shift) | (input << (32-shift));
}

// SHA256 FUNCTIONS
ChunkData preProcess(char* input) {

  // Convert input to arrays containing 64 8bit ints each, all contained in a main array
  uint32_t numOfArrays = ceil((float)(strlen(input)+9)/64); // padded 9 bytes for 128 and last 8 length ints
  uint8_t mainArray [numOfArrays*64];
  
  for (int i = 0; i < numOfArrays; i++) {
    for (int j = 0; j < 64; j++) {
      if ((i*64)+j < strlen(input)) {
        mainArray[(i*64)+j] = input[(i*64)+j];
      } else if ((i*64)+j == strlen(input)) {
        mainArray[(i*64)+j] = 128;
      } else {
        mainArray[(i*64)+j] = 0;
      }
    }
  }
  
  // Set last 64 bits to big-endian integer representing length of input 
  static uint64_t inputBits = strlen(input) * 8;
  for (int i = 0; i < 8; i++) {
    mainArray[((numOfArrays-1)*64)+(63-i)] = (inputBits >> i * 8) & 255;
  }
  
  uint32_t chunks[(numOfArrays*64)+64];

  for (int i = 0; i < numOfArrays; i++) {
    for (int j = 0; j < 16; j++) {
      uint32_t element = 0;
      for (int l = 0; l < 4; l++) {
        element = element + (mainArray[(i*64)+(j*4)+l] << ((3-l)*8));
      }
      chunks[(i*64)+j] = element;
    }
    for (int j = 16; j < 64; j++) {
      uint32_t s0 = rrot(chunks[(i*64)+j-15], 7) ^ rrot(chunks[(i*64)+j-15], 18) ^ (chunks[(i*64)+j-15] >> 3);
      uint32_t s1 = rrot(chunks[(i*64)+j-2], 17) ^ rrot(chunks[(i*64)+j-2], 19) ^ (chunks[(i*64)+j-2] >> 10);
      chunks[(i*64)+j] = chunks[(i*64)+j-16] + s0 + chunks[(i*64)+j-7] + s1;
    }
  } 

  
  
  struct ChunkData cd = {numOfArrays, chunks};
  
  return cd;
}

char* compress(uint32_t* chunks, uint32_t numOfChunks) {

  for (int i = 0; i < numOfChunks; i++) {
    uint32_t a=hsh[0], b=hsh[1], c=hsh[2], d=hsh[3], e=hsh[4], f=hsh[5], g=hsh[6], h=hsh[7];
    for (int j = 0; j < 64; j++) {
      uint32_t S1 = rrot(e, 6) ^ rrot(e, 11) ^ rrot(e, 25);
      uint32_t ch = (e & f) ^ ((~e) & g);
      uint32_t temp1 = h + S1 + ch + k[j] + chunks[(i*64)+j];
      uint32_t S0 = rrot(a, 2) ^ rrot(a, 13) ^ rrot(a, 22);
      uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
      uint32_t temp2 = S0 + maj;
      h = g;
      g = f;
      f = e;
      e = d + temp1;
      d = c;
      c = b;
      b = a;
      a = temp1 + temp2;
    }
    hsh[0] = hsh[0] + a;
    hsh[1] = hsh[1] + b;
    hsh[2] = hsh[2] + c;
    hsh[3] = hsh[3] + d;
    hsh[4] = hsh[4] + e;
    hsh[5] = hsh[5] + f;
    hsh[6] = hsh[6] + g;
    hsh[7] = hsh[7] + h;
  }

  static char digest[65];
  sprintf(digest,"%x%x%x%x%x%x%x%x",hsh[0],hsh[1],hsh[2],hsh[3],hsh[4],hsh[5],hsh[6],hsh[7]);
  return digest;
}

int main() {
  
  ChunkData processed = preProcess((char *)"abcdefg");
  uint32_t* chunks = processed.chunks;
  uint32_t numOfChunks = processed.numOfChunks;
  static char* compressed = compress(chunks, numOfChunks); 
  
  cout << compressed << endl;
}

.ino code run on Arduino Nano

#include "SHA256.h"

SHA256 sha256;

void setup() {
  Serial.begin(115200);
}

void loop() {
  static char test[] = "abcdefg";
  Serial.println(sha256.hash256(test));
  delay(1000);
}

.cpp code run on Arduino Nano:

#include "SHA256.h"
#include "Arduino.h"

// Initialize hash values (first 32 bits of the fractional parts of the square roots of the first 8 primes)
uint32_t hsh[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};

// Initialize keys (first 32 bits of the fractional parts of the cube roots of the first 64 primes)
uint32_t k[64] = {
  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
  0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
  0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
  0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
  0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
  0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
  0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
  0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};

// UTILITY FUNCTIONS
uint32_t SHA256::rrot(uint32_t input, uint8_t shift) {
  return (input >> shift) | (input << (32-shift));
}

// SHA256 FUNCTIONS
SHA256::ChunkData SHA256::preProcess(char* input) {

  // Convert input to arrays containing 64 8bit ints each, all contained in a main array
  uint32_t numOfArrays = ceil((float)(strlen(input)+9)/64); // padded 9 bytes for 128 and last 8 length ints
  uint8_t mainArray [numOfArrays*64];
  
  for (int i = 0; i < numOfArrays; i++) {
    for (int j = 0; j < 64; j++) {
      if ((i*64)+j < strlen(input)) {
        mainArray[(i*64)+j] = input[(i*64)+j];
      } else if ((i*64)+j == strlen(input)) {
        mainArray[(i*64)+j] = 128;
      } else {
        mainArray[(i*64)+j] = 0;
      }
    }
  }
  
  // Set last 64 bits to big-endian integer representing length of input 
  static uint64_t inputBits = strlen(input) * 8;
  for (int i = 0; i < 8; i++) {
    mainArray[((numOfArrays-1)*64)+(63-i)] = (inputBits >> i * 8) & 255;
  }
  
  uint32_t chunks[(numOfArrays*64)+64];

  for (int i = 0; i < numOfArrays; i++) {
    for (int j = 0; j < 16; j++) {
      uint32_t element = 0;
      for (int l = 0; l < 4; l++) {
        element = element + (mainArray[(i*64)+(j*4)+l] << ((3-l)*8));
      }
      chunks[(i*64)+j] = element;
    }
    for (int j = 16; j < 64; j++) {
      uint32_t s0 = rrot(chunks[(i*64)+j-15], 7) ^ rrot(chunks[(i*64)+j-15], 18) ^ (chunks[(i*64)+j-15] >> 3);
      uint32_t s1 = rrot(chunks[(i*64)+j-2], 17) ^ rrot(chunks[(i*64)+j-2], 19) ^ (chunks[(i*64)+j-2] >> 10);
      chunks[(i*64)+j] = chunks[(i*64)+j-16] + s0 + chunks[(i*64)+j-7] + s1;
    }
  } 

  
  
  struct SHA256::ChunkData cd = {numOfArrays, chunks};
  
  return cd;
}

char* SHA256::compress(uint32_t* chunks, uint32_t numOfChunks) {

  for (int i = 0; i < numOfChunks; i++) {
    uint32_t a=hsh[0], b=hsh[1], c=hsh[2], d=hsh[3], e=hsh[4], f=hsh[5], g=hsh[6], h=hsh[7];
    for (int j = 0; j < 64; j++) {
      uint32_t S1 = rrot(e, 6) ^ rrot(e, 11) ^ rrot(e, 25);
      uint32_t ch = (e & f) ^ ((~e) & g);
      uint32_t temp1 = h + S1 + ch + k[j] + chunks[(i*64)+j];
      uint32_t S0 = rrot(a, 2) ^ rrot(a, 13) ^ rrot(a, 22);
      uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
      uint32_t temp2 = S0 + maj;
      h = g;
      g = f;
      f = e;
      e = d + temp1;
      d = c;
      c = b;
      b = a;
      a = temp1 + temp2;
    }
    hsh[0] = hsh[0] + a;
    hsh[1] = hsh[1] + b;
    hsh[2] = hsh[2] + c;
    hsh[3] = hsh[3] + d;
    hsh[4] = hsh[4] + e;
    hsh[5] = hsh[5] + f;
    hsh[6] = hsh[6] + g;
    hsh[7] = hsh[7] + h;
  }

  static char digest[65];
  sprintf(digest,"%x%x%x%x%x%x%x%x",hsh[0],hsh[1],hsh[2],hsh[3],hsh[4],hsh[5],hsh[6],hsh[7]);
  return digest;
}

char* SHA256::hash256(char* input) {
  
  SHA256::ChunkData processed = preProcess((char *)"abcdefg");//input);
  uint32_t* chunks = processed.chunks;
  uint32_t numOfChunks = processed.numOfChunks;
  static char* compressed = compress(chunks, numOfChunks); 
  
  return compressed;
}

.h code run on Arduino Nano:

#ifndef SHA256_h
#define SHA256_h

#include "Arduino.h"

class SHA256 {
  public:
    char* hash256(char* input);
    char* testptr;
  private:
    struct ChunkData {
      uint32_t numOfChunks;
      uint32_t* chunks;
    };
    ChunkData preProcess(char* input);
    char* compress(uint32_t chunks[], uint32_t numOfChunks);
    uint32_t rrot(uint32_t input, uint8_t shift);
};

#endif

Here is a link to a diff check of the cpp files to show that only Arduino-porting related code was changed. Thank you to anyone who takes the time to reply to this question, and have a great day.

I think that the struct returns a pointer to a local variable chunks who's lifetime is restricted to the function only.

1 Like

Why are you ignoring 'input' and casting a const char * to (char *)?

Part of your problem is the way you convert the result to hex:
sprintf(digest,"%x%x%x%x%x%x%x%x",hsh[0],hsh[1],hsh[2],hsh[3],hsh[4],hsh[5],hsh[6],hsh[7]);

You are using an 'int' format (%x) with 'uint32_t' data. You are also dropping leading zeroes. Use "%08lx" instead of "%x".

When I run your Mac sketch on an UNO, changing only the Serial I/O, it produces a 64 character result:
"a234ee2e8c2c88de5e70d89c73af854526d665d88adbec24f6657d6d3067d0d3"

My guess is that the problem is somewhere doing 16-bit math where 32-bit math is required.

#include <stdint.h>
#ifndef ARDUINO
#include <iostream>
#endif
#include <math.h>
using namespace std;

// Initialize hash values (first 32 bits of the fractional parts of the square roots of the first 8 primes)
uint32_t hsh[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};

// Initialize keys (first 32 bits of the fractional parts of the cube roots of the first 64 primes)
uint32_t k[64] =
{
  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
  0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
  0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
  0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
  0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
  0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
  0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
  0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};

struct ChunkData
{
  uint32_t numOfChunks;
  uint32_t* chunks;
};

// UTILITY FUNCTIONS
uint32_t rrot(uint32_t input, uint8_t shift)
{
  return (input >> shift) | (input << (32 - shift));
}

// SHA256 FUNCTIONS
ChunkData preProcess(const char * input)
{
  // Convert input to arrays containing 64 8bit ints each, all contained in a main array
  uint32_t numOfArrays = ceil((float)(strlen(input) + 9) / 64); // padded 9 bytes for 128 and last 8 length ints
  uint8_t mainArray [numOfArrays * 64];

  for (size_t i = 0; i < numOfArrays; i++)
  {
    for (int j = 0; j < 64; j++)
    {
      if ((i * 64) + j < strlen(input))
      {
        mainArray[(i * 64) + j] = input[(i * 64) + j];
      }
      else if ((i * 64) + j == strlen(input))
      {
        mainArray[(i * 64) + j] = 128;
      }
      else
      {
        mainArray[(i * 64) + j] = 0;
      }
    }
  }

  // Set last 64 bits to big-endian integer representing length of input
  static uint64_t inputBits = strlen(input) * 8;
  for (int i = 0; i < 8; i++)
  {
    mainArray[((numOfArrays - 1) * 64) + (63 - i)] = (inputBits >> i * 8) & 255;
  }

  uint32_t chunks[(numOfArrays * 64) + 64];

  for (size_t i = 0; i < numOfArrays; i++)
  {
    for (int j = 0; j < 16; j++)
    {
      uint32_t element = 0;
      for (int l = 0; l < 4; l++)
      {
        element = element + (mainArray[(i * 64) + (j * 4) + l] << ((3 - l) * 8));
      }
      chunks[(i * 64) + j] = element;
    }
    for (int j = 16; j < 64; j++)
    {
      uint32_t s0 = rrot(chunks[(i * 64) + j - 15], 7) ^ rrot(chunks[(i * 64) + j - 15], 18) ^ (chunks[(i * 64) + j - 15] >> 3);
      uint32_t s1 = rrot(chunks[(i * 64) + j - 2], 17) ^ rrot(chunks[(i * 64) + j - 2], 19) ^ (chunks[(i * 64) + j - 2] >> 10);
      chunks[(i * 64) + j] = chunks[(i * 64) + j - 16] + s0 + chunks[(i * 64) + j - 7] + s1;
    }
  }

  static struct ChunkData cd = {numOfArrays, chunks};

  return cd;
}

char* compress(uint32_t* chunks, uint32_t numOfChunks)
{
  for (size_t i = 0; i < numOfChunks; i++)
  {
    uint32_t a = hsh[0], b = hsh[1], c = hsh[2], d = hsh[3], e = hsh[4], f = hsh[5], g = hsh[6], h = hsh[7];
    for (int j = 0; j < 64; j++)
    {
      uint32_t S1 = rrot(e, 6) ^ rrot(e, 11) ^ rrot(e, 25);
      uint32_t ch = (e & f) ^ ((~e) & g);
      uint32_t temp1 = h + S1 + ch + k[j] + chunks[(i * 64) + j];
      uint32_t S0 = rrot(a, 2) ^ rrot(a, 13) ^ rrot(a, 22);
      uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
      uint32_t temp2 = S0 + maj;
      h = g;
      g = f;
      f = e;
      e = d + temp1;
      d = c;
      c = b;
      b = a;
      a = temp1 + temp2;
    }
    hsh[0] = hsh[0] + a;
    hsh[1] = hsh[1] + b;
    hsh[2] = hsh[2] + c;
    hsh[3] = hsh[3] + d;
    hsh[4] = hsh[4] + e;
    hsh[5] = hsh[5] + f;
    hsh[6] = hsh[6] + g;
    hsh[7] = hsh[7] + h;
  }

  static char digest[65];
  sprintf(digest, "%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx",
          hsh[0], hsh[1], hsh[2], hsh[3],
          hsh[4], hsh[5], hsh[6], hsh[7]);
  return digest;
}

#ifdef ARDUINO
void setup()
{
  Serial.begin(115200);
  delay(200);
#else
int main()
{
#endif

  ChunkData processed = preProcess("abcdefg");
  uint32_t* chunks = processed.chunks;
  uint32_t numOfChunks = processed.numOfChunks;
  char* compressed = compress(chunks, numOfChunks);

#ifdef ARDUINO
  Serial.println(compressed);
#else
  cout << compressed << endl;
#endif
}

#ifdef ARDUINO
void loop() {}
#endif
1 Like

No, it returns the struct. C++ allows passing structs like this. What you can't get away with is "return &cd;"

However I think the value of chunks that's placed in the struct is being passed out of scope. Arrays get passed as pointers so its easy to do this if your not careful. You should be passing in workspace array to the original function and allocating memory externally to it.

You need %lx on the AVR for print a 32 bit int, %x is 16 bit only.

I see places where you use static variables inside functions without a rationale - static variables are global in extent and never change on subsequent function calls. I think you aren't understanding that.

Why static?

In case @DrDiettrich was on to something in Reply 2. Remove it if you like. It produces the same result either way.

Not necessary, struct is copied either way

Unfortunately, as has already been pointed out, in that copy is a pointer to a local variable.

And making struct static solves it?

No. It's the local chunks array (whose address is in the struct) that needs to be static... but it can't be because it doesn't have a fixed size. I tried with a constant "numOfArrays" (1) but still got the same (wrong) result.

I wonder if any of the constants assume a BigEndian architecture. The UNO is LittleEndian.

And that struct contains a pointer, not an array:

You should know the difference between both data types.

Ah, yes, I misread your point, you are spot-on...

I have found the mistake.

When 'uint8_t mainArray[]' is being packed into 'uint32_t element' the byte values from 'mainArray' are being promoted to 'int' (or 'unsigned int'?) automatically before being shifted left by 0, 8, 16, or 24 bits. The Macintosh has no trouble shifting a 32-bit 'int' left by 24 bits but when the Arduino shifts a 16-bit 'int' left by 24 bits, all of the bits fall off the top. This can be fixed with an explicit cast to 'uint32_t' before shifting:

      for (int l = 0; l < 4; l++)
      {
        element = element + ((uint32_t)mainArray[(i * 64) + (j * 4) + l] << ((3-l) * 8));
      }
1 Like

That was it - after explicitly casting to uint32_t and changing "%x" to "%08lx", the code works as expected. I put static in certain cases because I was getting outputs that changed every time with the same input, and for some reason it seemed to stop when I added static to a couple variables. It seems to be working fine so far, so I don't see any need to change those, but I'll keep that in mind if any issues arise later.

Thanks so much for the help!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.