packages feed

sha256-0.1.0.2: csrc/hs_sha256.c

/*
 * Copyright (C) 2006-2009 Vincent Hanquez <vincent@snarc.org>
 *               2016      Herbert Valerio Riedel <hvr@gnu.org>
 *               2024      Leon P Smith
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <ghcautoconf.h>

#include "hs_sha256.h"

#define ptr_uint32_aligned(ptr) (!((uintptr_t)(ptr) & 0x3))

static inline uint32_t
ror32(const uint32_t word, const unsigned shift)
{
  /* GCC usually transforms this into a 'ror'-insn */
  return (word >> shift) | (word << (32 - shift));
}

static inline uint32_t
cpu_to_be32(const uint32_t hl)
{
#if WORDS_BIGENDIAN
  return hl;
#elif __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)
  return __builtin_bswap32(hl);
#else
  /* GCC usually transforms this into a bswap insn */
  return ((hl & 0xff000000) >> 24) |
         ((hl & 0x00ff0000) >> 8)  |
         ((hl & 0x0000ff00) << 8)  |
         ( hl               << 24);
#endif
}

static inline void
cpu_to_be32_array(uint32_t *restrict dest, const uint32_t *restrict src, unsigned wordcnt)
{
  while (wordcnt--)
    *dest++ = cpu_to_be32(*src++);
}

static inline uint64_t
cpu_to_be64(const uint64_t hll)
{
#if WORDS_BIGENDIAN
  return hll;
#elif __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)
  return __builtin_bswap64(hll);
#else
  return ((uint64_t)cpu_to_be32(hll & 0xffffffff) << 32LL) | cpu_to_be32(hll >> 32);
#endif
}

const uint32_t hs_sha256_init[SHA256_STATE_LEN] = {
  0x6a09e667,
  0xbb67ae85,
  0x3c6ef372,
  0xa54ff53a,
  0x510e527f,
  0x9b05688c,
  0x1f83d9ab,
  0x5be0cd19
};

const uint8_t hs_sha256_padding[SHA256_BLOCK_SIZE + 1] = { 0x80, };

/* 232 times the cube root of the first 64 primes 2..311 */
static const uint32_t k[] = {
  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
};

#define e0(x)       (ror32(x, 2) ^ ror32(x,13) ^ ror32(x,22))
#define e1(x)       (ror32(x, 6) ^ ror32(x,11) ^ ror32(x,25))
#define s0(x)       (ror32(x, 7) ^ ror32(x,18) ^ (x >> 3))
#define s1(x)       (ror32(x,17) ^ ror32(x,19) ^ (x >> 10))

static void
sha256_do_chunk_aligned(uint32_t state[const restrict SHA256_STATE_LEN], uint32_t w[const restrict 64])
{
  int i;

  for (i = 16; i < 64; i++)
    w[i] = s1(w[i - 2]) + w[i - 7] + s0(w[i - 15]) + w[i - 16];

  uint32_t a = state[0];
  uint32_t b = state[1];
  uint32_t c = state[2];
  uint32_t d = state[3];
  uint32_t e = state[4];
  uint32_t f = state[5];
  uint32_t g = state[6];
  uint32_t h = state[7];

#define R(a, b, c, d, e, f, g, h, k, w)             \
    t1 = h + e1(e) + (g ^ (e & (f ^ g))) + k + w;   \
    t2 = e0(a) + ((a & b) | (c & (a | b)));         \
    d += t1;                                        \
    h = t1 + t2;

  for (i = 0; i < 64; i += 8) {
    uint32_t t1, t2;

    R(a, b, c, d, e, f, g, h, k[i + 0], w[i + 0]);
    R(h, a, b, c, d, e, f, g, k[i + 1], w[i + 1]);
    R(g, h, a, b, c, d, e, f, k[i + 2], w[i + 2]);
    R(f, g, h, a, b, c, d, e, k[i + 3], w[i + 3]);
    R(e, f, g, h, a, b, c, d, k[i + 4], w[i + 4]);
    R(d, e, f, g, h, a, b, c, k[i + 5], w[i + 5]);
    R(c, d, e, f, g, h, a, b, k[i + 6], w[i + 6]);
    R(b, c, d, e, f, g, h, a, k[i + 7], w[i + 7]);
  }

#undef R

  state[0] += a;
  state[1] += b;
  state[2] += c;
  state[3] += d;
  state[4] += e;
  state[5] += f;
  state[6] += g;
  state[7] += h;
}

static void
sha256_do_chunk
(uint32_t state[const restrict SHA256_STATE_LEN],
 const uint8_t buf[const restrict SHA256_BLOCK_SIZE])
{
  /*
  printf(  "state:   ");
  for (int i = 0; i < SHA256_STATE_LEN; i++)
    printf("%08x", state[i]);
  printf("\nbuffer:  ");
  for (int i = 0; i < SHA256_BLOCK_SIZE; i++)
    printf("%02x", buf[i]);
  printf("\n");
  */
  uint32_t w[64]; /* only first 16 words are filled in */
  if (ptr_uint32_aligned(buf)) { /* aligned buf */
    cpu_to_be32_array(w, (const uint32_t *)buf, 16);
  } else { /* unaligned buf */
    memcpy(w + 16, buf, 64);
#if !WORDS_BIGENDIAN
    cpu_to_be32_array(w, w + 16, 16);
#endif
  }
  sha256_do_chunk_aligned(state, w);
  explicit_bzero(&w, sizeof(w));
  /*
  printf(  "state':  ");
  for (int i = 0; i < SHA256_STATE_LEN; i++)
    printf("%08x", state[i]);
  printf("\n");
  */
}

size_t
hs_sha256_update
(const uint32_t state[const SHA256_STATE_LEN],
 const uint8_t *const data,
 size_t const datalen,
 uint32_t out[const SHA256_STATE_LEN])
{
  if (out != state) memcpy(out, state, SHA256_DIGEST_SIZE);
  if (data == NULL) return 0;
  size_t i = 0;
  size_t dataLeft = datalen;
  while (dataLeft >= SHA256_BLOCK_SIZE) {
      sha256_do_chunk(out, data + i);
      i += SHA256_BLOCK_SIZE;
      dataLeft -= SHA256_BLOCK_SIZE;
  }
  return i;
}

void
hs_sha256_update_ctx
(const sha256_ctx *const in,
 const uint8_t *const data,
 const size_t datalen,
 sha256_ctx *const out)
{
  const size_t bufferlen = in->count & 0x3F;
  if ( data == NULL || datalen == 0 ) {
    if (in != out) memcpy(out, in, sizeof(sha256_ctx) + bufferlen);
  } else if ( datalen + bufferlen < SHA256_BLOCK_SIZE ) {
    if (in != out) memcpy(out, in, sizeof(sha256_ctx) + bufferlen);
    memcpy(out->buffer + bufferlen, data, datalen);
    out->count += datalen;
  } else if ( bufferlen == 0 ) {
    const size_t processedLen = hs_sha256_update(in->state, data, datalen, out->state);
    out->count = in->count + datalen;
    memcpy(out->buffer, data + processedLen, datalen - processedLen);
  } else {
    // Don't assume the output has enough extra space for a full buffer
    uint8_t mybuf[SHA256_BLOCK_SIZE] __attribute__ ((aligned (4)));
    memcpy(mybuf, in->buffer, bufferlen);
    size_t processedDataLen = SHA256_BLOCK_SIZE - bufferlen;
    memcpy(mybuf + bufferlen, data, processedDataLen);
    hs_sha256_update(in->state, mybuf, SHA256_BLOCK_SIZE, out->state);
    processedDataLen += hs_sha256_update( out->state,
					  data + processedDataLen,
					  datalen - processedDataLen,
					  out->state );
    out->count = in->count + datalen;
    memcpy(out->buffer, data + processedDataLen, datalen - processedDataLen );
  }
}

void
hs_sha256_promote_to_ctx
(const uint32_t state[const SHA256_STATE_LEN],
 uint64_t const blockcount,
 const uint8_t *const data,
 size_t const datalen,
 sha256_ctx *const out)
{
  hs_sha256_update(state, data, datalen, out->state);
  out->count = (blockcount << 6) + datalen;
  size_t const bufferlen = datalen & 0x3F;
  if (data != NULL && bufferlen > 0) {
    memcpy(out->buffer, data + (datalen - bufferlen), bufferlen);
  }
}

void
hs_sha256_encode_state
(const uint32_t in[const SHA256_STATE_LEN],
 uint8_t out[const SHA256_DIGEST_SIZE])
{
#if WORDS_BIGENDIAN
  memcpy(out, (uint8_t *)in, SHA256_DIGEST_SIZE);
#else
  if(ptr_uint32_aligned(out)) {
    cpu_to_be32_array((uint32_t *)out, in, SHA256_STATE_LEN);
  } else {
    for (int i = 0; i < SHA256_STATE_LEN; i++) {
      int pos = i << 2;
      out[pos  ] = (in[i] >> 24) & 0xFF;
      out[pos+1] = (in[i] >> 16) & 0xFF;
      out[pos+2] = (in[i] >>  8) & 0xFF;
      out[pos+3] = (in[i]      ) & 0xFF;
    }
  }
#endif
}

void
hs_sha256_decode_state
(const uint8_t in[const SHA256_DIGEST_SIZE],
 uint32_t out[const SHA256_STATE_LEN])
{
#if WORDS_BIGENDIAN
  memcpy((uint8_t *)out, in, SHA256_DIGEST_SIZE);
#else
  if(ptr_uint32_aligned(in)) {
    cpu_to_be32_array(out, (uint32_t *)in, SHA256_STATE_LEN);
  } else {
    for (int i = 0; i < SHA256_DIGEST_SIZE; i += 4) {
      out [i >> 2]
        = ((uint32_t)in[i  ]) << 24
        | ((uint32_t)in[i+1]) << 16
        | ((uint32_t)in[i+2]) <<  8
        | ((uint32_t)in[i+3]);
    }
  }
#endif
}

// The unpinned ByteArray approach I'm taking to the FFI seems to
// necessitate this kind of accessor functions.  It'd be nice
// to simply use #{peek sha256_ctx, count} style accessors from
// haskell, but I'm not sure that works with this approach.
uint64_t
hs_sha256_get_count
(const sha256_ctx *const ctx)
{
  return ctx->count;
}

void
hs_sha256_init_ctx
(sha256_ctx *const out)
{
  hs_sha256_promote_to_ctx(hs_sha256_init, 0, NULL, 0, out);
}

void
hs_sha256_finalize_ctx_bits
(const sha256_ctx *const in,
 const uint8_t *const data,
 uint64_t const datalenbits,
 uint8_t out[const SHA256_DIGEST_SIZE])
{
  uint8_t buffer[SHA256_BLOCK_SIZE] __attribute__ ((aligned (4)));
  uint32_t state[SHA256_STATE_LEN];
  size_t bufferPos = in->count & 0x3F;
  size_t bufferLeft = SHA256_BLOCK_SIZE - bufferPos;
  size_t dataLeft = datalenbits >> 3;
  size_t dataPos;
  memcpy(buffer, in->buffer, bufferPos);
  if (data == NULL || datalenbits < 8) {
    memcpy(state, in, SHA256_DIGEST_SIZE);
    dataPos = 0;
  } else if (dataLeft < bufferLeft) {
    memcpy(state, in, SHA256_DIGEST_SIZE);
    dataPos = dataLeft;
    memcpy(buffer + bufferPos, data, dataLeft);
    bufferPos += dataLeft;
  } else {
    memcpy(buffer + bufferPos, data, bufferLeft);
    hs_sha256_update(in->state, buffer, SHA256_BLOCK_SIZE, state);
    dataPos = bufferLeft;
    dataLeft -= bufferLeft;
    size_t processedLen = hs_sha256_update(state, data + dataPos, dataLeft, state);
    dataPos += processedLen;
    dataLeft -= processedLen;
    memcpy(buffer, data + dataPos, dataLeft);
    bufferPos = dataLeft;
  }

  uint8_t bitsLeft = datalenbits & 7;

  // we need this conditional to avoid dereferencing past the end of "data"
  uint8_t lastByte = bitsLeft == 0 ? 0x80
    : (data[dataPos] & (0xFF << (8 - bitsLeft))) | 1 << (7 - bitsLeft);

  buffer[bufferPos++] = lastByte;

  if (bufferPos <= 56) {
    memset(buffer + bufferPos, 0, 56 - bufferPos);
  } else {
    memset(buffer + bufferPos, 0, 64 - bufferPos);
    sha256_do_chunk(state,buffer);
    memset(buffer, 0, 56);
  }

  uint64_t finalBitLen = (in->count << 3) + datalenbits ;

  buffer[56] = (finalBitLen >> 56) & 0xFF;
  buffer[57] = (finalBitLen >> 48) & 0xFF;
  buffer[58] = (finalBitLen >> 40) & 0xFF;
  buffer[59] = (finalBitLen >> 32) & 0xFF;
  buffer[60] = (finalBitLen >> 24) & 0xFF;
  buffer[61] = (finalBitLen >> 16) & 0xFF;
  buffer[62] = (finalBitLen >>  8) & 0xFF;
  buffer[63] = (finalBitLen      ) & 0xFF;

  sha256_do_chunk(state, buffer);

  hs_sha256_encode_state(state, out);

  explicit_bzero(&state, sizeof(state));
  explicit_bzero(&buffer, sizeof(buffer));
}

int
hs_sha256_const_memcmp_uint32be
(const uint32_t *const a,
 const uint32_t *const b,
 uint32_t const n )
{
  int d, out = 0;
  uint32_t i = n;
  while (i > 0) {
    i--;
    for (int j = 0; j < 32; j += 8 ) {
      d = ((a[i] >> j) & 0xFF) - ((b[i] >> j) & 0xFF);
      out = d == 0 ? out : d;
    }
  }
  return out;
}

/*

  FIXME:

  This function is a duplicate of hs_hashstring_const_memcmp. This duplication
  was introduced to work around an issue with hackage's build infrastructure,
  as the C compiler couldn't find hs_hashstring_memcmp.h

  A more proper fix would avoid this duplication.

*/
static inline
int
hs_sha256_const_memcmp
( const uint8_t *const a,
  const uint8_t *const b,
  size_t const n )
{
  int d, out = 0;
  size_t i = n;
  while (i > 0) {
    i--;
    d = a[i] - b[i];
    out = d == 0 ? out : d;
  }
  return out;
}

// constant-ish time memcmp, could be better, but should be pretty good
// doesn't handle nulls, must be passed a non-null argument!
int
hs_sha256_const_memcmp_ctx
(const sha256_ctx *const a,
 const sha256_ctx *const b )
{
  int d;
  if ( (d = hs_sha256_const_memcmp_uint32be(a->state, b->state, SHA256_STATE_LEN)) ) return d;
  int x = a->count & 0x3F;
  int y = b->count & 0x3F;
  int n = (x < y) ? x : y;
  if ( (d = hs_sha256_const_memcmp(a->buffer, b->buffer, n)) ) return d;
  if ( (d = x - y) ) return d;
  // does this last comparison even matter in practice?
  if (a->count == b->count) return 0;
  // It is of course trivial to reach this location if you simply take a SHA256
  // context structure and change the length, but this should be effectively
  // unreachable without constructing at least one state synthetically.
  // A difference in length implies a cryptographically non-trivial collision.
  if (a->count < b->count) return -1;
  return 1;
}