semq-sys 1.1.0

Raw FFI bindings to the SEMQ native library.
Documentation
/*
 * Copyright (c) 2026 The SEMQ Group Inc.
 * Licensed under the PolyForm Noncommercial License 1.0.0. See LICENSE.md for terms.
 */

/*
 * semq_sha256.c — SHA-256 as specified in FIPS 180-4.
 *
 * Freestanding: no libc beyond memcpy/memset. Conformance vector 0 pins
 * this implementation with the FIPS test messages.
 */

#include "semq_sha256.h"
#include "core/semq_dispatch.h"

#include <string.h>

#include "semq.h"

const uint32_t semqi_sha256_k[64] = {
    0x428a2f98u, 0x71374491u, 0xb5c0fbcfu, 0xe9b5dba5u, 0x3956c25bu, 0x59f111f1u,
    0x923f82a4u, 0xab1c5ed5u, 0xd807aa98u, 0x12835b01u, 0x243185beu, 0x550c7dc3u,
    0x72be5d74u, 0x80deb1feu, 0x9bdc06a7u, 0xc19bf174u, 0xe49b69c1u, 0xefbe4786u,
    0x0fc19dc6u, 0x240ca1ccu, 0x2de92c6fu, 0x4a7484aau, 0x5cb0a9dcu, 0x76f988dau,
    0x983e5152u, 0xa831c66du, 0xb00327c8u, 0xbf597fc7u, 0xc6e00bf3u, 0xd5a79147u,
    0x06ca6351u, 0x14292967u, 0x27b70a85u, 0x2e1b2138u, 0x4d2c6dfcu, 0x53380d13u,
    0x650a7354u, 0x766a0abbu, 0x81c2c92eu, 0x92722c85u, 0xa2bfe8a1u, 0xa81a664bu,
    0xc24b8b70u, 0xc76c51a3u, 0xd192e819u, 0xd6990624u, 0xf40e3585u, 0x106aa070u,
    0x19a4c116u, 0x1e376c08u, 0x2748774cu, 0x34b0bcb5u, 0x391c0cb3u, 0x4ed8aa4au,
    0x5b9cca4fu, 0x682e6ff3u, 0x748f82eeu, 0x78a5636fu, 0x84c87814u, 0x8cc70208u,
    0x90befffau, 0xa4506cebu, 0xbef9a3f7u, 0xc67178f2u
};

static inline uint32_t rotr(uint32_t x, uint32_t n) {
    return (x >> n) | (x << (32u - n));
}

static void sha256_block_scalar(uint32_t state[8], const uint8_t block[64]) {
    uint32_t w[64];
    for (size_t i = 0u; i < 16u; i++) {
        w[i] = ((uint32_t)block[i * 4u] << 24) | ((uint32_t)block[i * 4u + 1u] << 16)
             | ((uint32_t)block[i * 4u + 2u] << 8) | (uint32_t)block[i * 4u + 3u];
    }
    for (uint32_t i = 16u; i < 64u; i++) {
        const uint32_t s0 = rotr(w[i - 15u], 7) ^ rotr(w[i - 15u], 18) ^ (w[i - 15u] >> 3);
        const uint32_t s1 = rotr(w[i - 2u], 17) ^ rotr(w[i - 2u], 19) ^ (w[i - 2u] >> 10);
        w[i] = w[i - 16u] + s0 + w[i - 7u] + s1;
    }
    uint32_t a = state[0], b = state[1], c = state[2], d = state[3];
    uint32_t e = state[4], f = state[5], g = state[6], h = state[7];
    for (uint32_t i = 0u; i < 64u; i++) {
        const uint32_t S1  = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
        const uint32_t ch  = (e & f) ^ (~e & g);
        const uint32_t t1  = h + S1 + ch + semqi_sha256_k[i] + w[i];
        const uint32_t S0  = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
        const uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
        const uint32_t t2  = S0 + maj;
        h = g; g = f; f = e; e = d + t1;
        d = c; c = b; b = a; a = t1 + t2;
    }
    state[0] += a; state[1] += b; state[2] += c; state[3] += d;
    state[4] += e; state[5] += f; state[6] += g; state[7] += h;
}

void semq_sha256_init(semq_sha256_ctx_t* ctx) {
    ctx->state[0] = 0x6a09e667u; ctx->state[1] = 0xbb67ae85u;
    ctx->state[2] = 0x3c6ef372u; ctx->state[3] = 0xa54ff53au;
    ctx->state[4] = 0x510e527fu; ctx->state[5] = 0x9b05688cu;
    ctx->state[6] = 0x1f83d9abu; ctx->state[7] = 0x5be0cd19u;
    ctx->bit_len  = 0u;
    ctx->buffered = 0u;
    ctx->sha_checked = 0u;
    ctx->block = sha256_block_scalar;
}

void semq_sha256_update(semq_sha256_ctx_t* ctx, const uint8_t* data, size_t len) {
    ctx->bit_len += (uint64_t)len * 8u;
#if defined(__aarch64__) || defined(_M_ARM64)
    /* Feature probing can dominate short hashes. Switching block functions
     * after earlier scalar blocks preserves the same SHA state. */
    if (!ctx->sha_checked && ctx->bit_len >= UINT64_C(512) * 8u) {
        ctx->sha_checked = 1u;
        if (semq_dispatch_has_sha256()) ctx->block = semqi_sha256_block_arm;
    }
#endif
    if (ctx->buffered > 0u) {
        const size_t take = (64u - ctx->buffered < len) ? (64u - ctx->buffered) : len;
        memcpy(ctx->buffer + ctx->buffered, data, take);
        ctx->buffered += (uint32_t)take;
        data += take;
        len -= take;
        if (ctx->buffered == 64u) {
            ctx->block(ctx->state, ctx->buffer);
            ctx->buffered = 0u;
        }
    }
    while (len >= 64u) {
        ctx->block(ctx->state, data);
        data += 64u;
        len -= 64u;
    }
    if (len > 0u) {
        memcpy(ctx->buffer, data, len);
        ctx->buffered = (uint32_t)len;
    }
}

void semq_sha256_final(semq_sha256_ctx_t* ctx, uint8_t out[32]) {
    const uint64_t bits = ctx->bit_len;
    uint8_t pad[72];
    memset(pad, 0, sizeof(pad));
    pad[0] = 0x80u;
    /* Pad to 56 mod 64, then append the 64-bit big-endian length. */
    const uint32_t pad_len = (ctx->buffered < 56u) ? (56u - ctx->buffered) : (120u - ctx->buffered);
    uint8_t len_be[8];
    for (uint32_t i = 0u; i < 8u; i++) {
        len_be[i] = (uint8_t)(bits >> (56u - 8u * i));
    }
    /* bit_len must not count the padding: update directly on the blocks. */
    const uint64_t saved = ctx->bit_len;
    semq_sha256_update(ctx, pad, pad_len);
    semq_sha256_update(ctx, len_be, 8u);
    ctx->bit_len = saved;
    for (size_t i = 0u; i < 8u; i++) {
        out[i * 4u]      = (uint8_t)(ctx->state[i] >> 24);
        out[i * 4u + 1u] = (uint8_t)(ctx->state[i] >> 16);
        out[i * 4u + 2u] = (uint8_t)(ctx->state[i] >> 8);
        out[i * 4u + 3u] = (uint8_t)(ctx->state[i]);
    }
}

SEMQ_API void semq_sha256(const uint8_t* data, uint64_t len, uint8_t out[32]) {
    semq_sha256_ctx_t ctx;
    semq_sha256_init(&ctx);
    /* Feed in size_t-sized chunks so wasm32 handles buffers above 4 GiB
     * consistently (they cannot exist there, but the loop is uniform). */
    while (len > 0u) {
        const size_t chunk = (len > (uint64_t)SIZE_MAX) ? SIZE_MAX : (size_t)len;
        semq_sha256_update(&ctx, data, chunk);
        data += chunk;
        len -= chunk;
    }
    semq_sha256_final(&ctx, out);
}