/* * ledge.c — LEDGE (SEIT product recreation) * Copyright (C) 2026 Ahmad Ali Parr / SnapKitty Research Lab * SPDX-License-Identifier: LicenseRef-SEIT-FSL-Donation-1.0 * * Functional source, donation-gated runtime: you may read, share, and * modify this source freely; running it requires a qualifying donation * to the licensor. See LICENSE.txt for the full terms. No warranty. */ /* * LEDGE - compact recreation of SEIT's LEDGE product: * "Open-source cryptographic WORM chain - every AI decision sealed and * auditable in under 10ms." * * Append-only hash chain. Each entry: index, timestamp, text, prev_hash, hash * where hash = SHA-256(prev_hash || timestamp || text). * * SHA-256 below is implemented fresh from the FIPS 180-4 specification. * Chain format and design decisions are documented in README.md. */ #define _POSIX_C_SOURCE 199309L #include #include #include #include #include /* ------------------------------------------------------------------ */ /* SHA-256 (FIPS 180-4), implemented from the spec. */ /* ------------------------------------------------------------------ */ static uint32_t sha256_rotr(uint32_t x, unsigned n) { return (x >> n) | (x << (32u - n)); } /* First 64 prime numbers' cube-root fractional parts (FIPS 180-4 4.2.2). */ static const uint32_t 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 }; /* Initial hash value H(0): square-root fractional parts of first 8 primes. */ static const uint32_t sha256_h0[8] = { 0x6a09e667u, 0xbb67ae85u, 0x3c6ef372u, 0xa54ff53au, 0x510e527fu, 0x9b05688cu, 0x1f83d9abu, 0x5be0cd19u }; typedef struct { uint32_t h[8]; uint64_t total_len; /* bytes hashed so far */ uint8_t buf[64]; /* partial block */ size_t buf_len; } sha256_ctx; static void sha256_block(sha256_ctx *c, const uint8_t blk[64]) { uint32_t w[64]; uint32_t a, b, cc, d, e, f, g, hh, t1, t2; int t; for (t = 0; t < 16; t++) { w[t] = ((uint32_t)blk[t * 4] << 24) | ((uint32_t)blk[t * 4 + 1] << 16) | ((uint32_t)blk[t * 4 + 2] << 8) | (uint32_t)blk[t * 4 + 3]; } for (t = 16; t < 64; t++) { uint32_t s0 = sha256_rotr(w[t - 15], 7) ^ sha256_rotr(w[t - 15], 18) ^ (w[t - 15] >> 3); uint32_t s1 = sha256_rotr(w[t - 2], 17) ^ sha256_rotr(w[t - 2], 19) ^ (w[t - 2] >> 10); w[t] = w[t - 16] + s0 + w[t - 7] + s1; } a = c->h[0]; b = c->h[1]; cc = c->h[2]; d = c->h[3]; e = c->h[4]; f = c->h[5]; g = c->h[6]; hh = c->h[7]; for (t = 0; t < 64; t++) { uint32_t S1 = sha256_rotr(e, 6) ^ sha256_rotr(e, 11) ^ sha256_rotr(e, 25); uint32_t ch = (e & f) ^ (~e & g); t1 = hh + S1 + ch + sha256_k[t] + w[t]; uint32_t S0 = sha256_rotr(a, 2) ^ sha256_rotr(a, 13) ^ sha256_rotr(a, 22); uint32_t maj = (a & b) ^ (a & cc) ^ (b & cc); t2 = S0 + maj; hh = g; g = f; f = e; e = d + t1; d = cc; cc = b; b = a; a = t1 + t2; } c->h[0] += a; c->h[1] += b; c->h[2] += cc; c->h[3] += d; c->h[4] += e; c->h[5] += f; c->h[6] += g; c->h[7] += hh; } static void sha256_init(sha256_ctx *c) { memcpy(c->h, sha256_h0, sizeof(c->h)); c->total_len = 0; c->buf_len = 0; } static void sha256_update(sha256_ctx *c, const void *data, size_t len) { const uint8_t *p = (const uint8_t *)data; c->total_len += len; while (len > 0) { size_t take = 64 - c->buf_len; if (take > len) { take = len; } memcpy(c->buf + c->buf_len, p, take); c->buf_len += take; p += take; len -= take; if (c->buf_len == 64) { sha256_block(c, c->buf); c->buf_len = 0; } } } /* FIPS 180-4 padding: 0x80, zeros, then 64-bit big-endian bit length. */ static void sha256_final(sha256_ctx *c, uint8_t out[32]) { uint8_t pad[64]; uint64_t bit_len = c->total_len * 8u; size_t pad_len; int i; pad[0] = 0x80u; memset(pad + 1, 0, sizeof(pad) - 1); /* We need buf_len + 1 + k to leave exactly 8 bytes for the length. */ pad_len = (c->buf_len < 56) ? (56 - c->buf_len) : (120 - c->buf_len); sha256_update(c, pad, pad_len); for (i = 7; i >= 0; i--) { pad[i] = (uint8_t)(bit_len & 0xffu); bit_len >>= 8; } sha256_update(c, pad, 8); for (i = 0; i < 8; i++) { out[i * 4] = (uint8_t)(c->h[i] >> 24); out[i * 4 + 1] = (uint8_t)(c->h[i] >> 16); out[i * 4 + 2] = (uint8_t)(c->h[i] >> 8); out[i * 4 + 3] = (uint8_t)c->h[i]; } } static void sha256(const void *data, size_t len, uint8_t out[32]) { sha256_ctx c; sha256_init(&c); sha256_update(&c, data, len); sha256_final(&c, out); } /* ------------------------------------------------------------------ */ /* Chain storage. */ /* ------------------------------------------------------------------ */ #define CHAIN_FILE "chain.dat" #define HASH_LEN 32 typedef struct { uint32_t index; int64_t ts_ns; /* CLOCK_REALTIME, nanoseconds since epoch */ uint32_t text_len; uint8_t *text; uint8_t prev_hash[HASH_LEN]; uint8_t hash[HASH_LEN]; } entry_t; static void write_u32_be(uint8_t *p, uint32_t v) { p[0] = (uint8_t)(v >> 24); p[1] = (uint8_t)(v >> 16); p[2] = (uint8_t)(v >> 8); p[3] = (uint8_t)v; } static uint32_t read_u32_be(const uint8_t *p) { return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; } static void write_i64_be(uint8_t *p, int64_t v) { int i; for (i = 7; i >= 0; i--) { p[i] = (uint8_t)(v & 0xff); v >>= 8; } } static int64_t read_i64_be(const uint8_t *p) { uint64_t v = 0; int i; for (i = 0; i < 8; i++) { v = (v << 8) | p[i]; } return (int64_t)v; } /* Entry serialization: * u32be index | i64be ts_ns | u32be text_len | text | 32B prev_hash | 32B hash */ static int write_entry(FILE *f, const entry_t *e) { uint8_t hdr[16]; write_u32_be(hdr, e->index); write_i64_be(hdr + 4, e->ts_ns); write_u32_be(hdr + 12, e->text_len); if (fwrite(hdr, 1, sizeof(hdr), f) != sizeof(hdr)) { return -1; } if (e->text_len > 0 && fwrite(e->text, 1, e->text_len, f) != e->text_len) { return -1; } if (fwrite(e->prev_hash, 1, HASH_LEN, f) != HASH_LEN) { return -1; } if (fwrite(e->hash, 1, HASH_LEN, f) != HASH_LEN) { return -1; } return 0; } static void entry_free(entry_t *e) { free(e->text); e->text = NULL; } /* Returns: 1 = entry read, 0 = clean EOF, -1 = corrupt/truncated. */ static int read_entry(FILE *f, entry_t *e) { uint8_t hdr[16]; size_t n = fread(hdr, 1, sizeof(hdr), f); if (n == 0) { return 0; } if (n != sizeof(hdr)) { return -1; } e->index = read_u32_be(hdr); e->ts_ns = read_i64_be(hdr + 4); e->text_len = read_u32_be(hdr + 12); if (e->text_len > 1024 * 1024) { /* sanity cap: 1 MiB decision text */ return -1; } e->text = e->text_len ? malloc(e->text_len) : NULL; if (e->text_len && !e->text) { return -1; } if (e->text_len > 0 && fread(e->text, 1, e->text_len, f) != e->text_len) { entry_free(e); return -1; } if (fread(e->prev_hash, 1, HASH_LEN, f) != HASH_LEN || fread(e->hash, 1, HASH_LEN, f) != HASH_LEN) { entry_free(e); return -1; } return 1; } /* hash = SHA-256(prev_hash || ts_ns_be || text) */ static void seal_hash(const uint8_t prev[HASH_LEN], int64_t ts_ns, const uint8_t *text, uint32_t text_len, uint8_t out[HASH_LEN]) { sha256_ctx c; uint8_t tsbuf[8]; sha256_init(&c); sha256_update(&c, prev, HASH_LEN); write_i64_be(tsbuf, ts_ns); sha256_update(&c, tsbuf, sizeof(tsbuf)); sha256_update(&c, text, text_len); sha256_final(&c, out); } static void hex_of(const uint8_t in[HASH_LEN], char out[2 * HASH_LEN + 1]) { static const char digits[] = "0123456789abcdef"; int i; for (i = 0; i < HASH_LEN; i++) { out[2 * i] = digits[in[i] >> 4]; out[2 * i + 1] = digits[in[i] & 0xfu]; } out[2 * HASH_LEN] = '\0'; } static double elapsed_ms(const struct timespec *a, const struct timespec *b) { return (double)(b->tv_sec - a->tv_sec) * 1000.0 + (double)(b->tv_nsec - a->tv_nsec) / 1e6; } static int cmd_append(const char *text) { FILE *f; entry_t last = { 0 }; entry_t e; struct timespec t0, t1; char hex[2 * HASH_LEN + 1]; int have_last = 0; int rc; f = fopen(CHAIN_FILE, "rb"); if (f) { entry_t cur; for (;;) { rc = read_entry(f, &cur); if (rc < 0) { fclose(f); fprintf(stderr, "error: chain.dat is corrupt\n"); return 1; } if (rc == 0) { break; } entry_free(&last); last = cur; have_last = 1; } fclose(f); } e.index = have_last ? last.index + 1 : 0; e.text = (uint8_t *)text; e.text_len = (uint32_t)strlen(text); if (have_last) { memcpy(e.prev_hash, last.hash, HASH_LEN); entry_free(&last); } else { memset(e.prev_hash, 0, HASH_LEN); /* genesis */ } clock_gettime(CLOCK_REALTIME, &t0); /* latency window starts */ e.ts_ns = (int64_t)t0.tv_sec * 1000000000LL + (int64_t)t0.tv_nsec; seal_hash(e.prev_hash, e.ts_ns, e.text, e.text_len, e.hash); f = fopen(CHAIN_FILE, "ab"); if (!f) { fprintf(stderr, "error: cannot open chain.dat for append\n"); return 1; } if (write_entry(f, &e) != 0) { fprintf(stderr, "error: failed to write entry\n"); fclose(f); return 1; } fflush(f); fclose(f); clock_gettime(CLOCK_REALTIME, &t1); /* latency window ends */ hex_of(e.hash, hex); printf("sealed index=%u hash=%s latency=%.3fms\n", e.index, hex, elapsed_ms(&t0, &t1)); return 0; } static int cmd_verify(void) { FILE *f = fopen(CHAIN_FILE, "rb"); uint32_t expect_index = 0; uint8_t expect_prev[HASH_LEN]; unsigned long count = 0; int r = 0; if (!f) { printf("VERIFY FAIL: no chain file found\n"); return 1; } memset(expect_prev, 0, HASH_LEN); /* genesis prev_hash is all zeros */ for (;;) { entry_t e; uint8_t expect[HASH_LEN]; int rc = read_entry(f, &e); if (rc < 0) { printf("VERIFY FAIL: truncated/corrupt chain at entry %lu\n", count); r = 1; break; } if (rc == 0) { break; } if (e.index != expect_index) { printf("VERIFY FAIL: index gap at entry %lu (got %u, want %u)\n", count, e.index, expect_index); entry_free(&e); r = 1; break; } if (memcmp(e.prev_hash, expect_prev, HASH_LEN) != 0) { printf("VERIFY FAIL: prev_hash mismatch at index %u\n", e.index); entry_free(&e); r = 1; break; } seal_hash(e.prev_hash, e.ts_ns, e.text, e.text_len, expect); if (memcmp(e.hash, expect, HASH_LEN) != 0) { printf("VERIFY FAIL: hash mismatch at index %u\n", e.index); entry_free(&e); r = 1; break; } memcpy(expect_prev, e.hash, HASH_LEN); expect_index = e.index + 1; count++; entry_free(&e); } fclose(f); if (r == 0) { printf("VERIFY OK: %lu entries, chain intact\n", count); } return r; } static int cmd_selftest(void) { /* FIPS 180-4 / standard test vectors for SHA-256. */ static const struct { const char *msg; const char *want; } vecs[] = { { "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, { "abc", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" }, { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" } }; size_t i; for (i = 0; i < sizeof(vecs) / sizeof(vecs[0]); i++) { uint8_t out[HASH_LEN]; char hex[2 * HASH_LEN + 1]; sha256(vecs[i].msg, strlen(vecs[i].msg), out); hex_of(out, hex); if (strcmp(hex, vecs[i].want) != 0) { printf("SELFTEST FAIL: vector %zu\n got %s\n want %s\n", i, hex, vecs[i].want); return 1; } } printf("SELFTEST OK: SHA-256 matches %zu standard test vectors\n", sizeof(vecs) / sizeof(vecs[0])); return 0; } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "usage: %s append | verify | selftest\n", argv[0]); return 2; } if (strcmp(argv[1], "append") == 0) { if (argc < 3) { fprintf(stderr, "usage: %s append \n", argv[0]); return 2; } return cmd_append(argv[2]); } if (strcmp(argv[1], "verify") == 0) { return cmd_verify(); } if (strcmp(argv[1], "selftest") == 0) { return cmd_selftest(); } fprintf(stderr, "unknown command: %s\n", argv[1]); return 2; }