/* SPDX-License-Identifier: Apache-2.0 */ #include "settle.h" #include "sha256.h" #include "tweetnacl.h" #include #include #include #include static const uint8_t *g_seed; /* deterministic key derivation: randombytes hands TweetNaCl the seed */ void randombytes(unsigned char *x, unsigned long long n){ unsigned long long i; for (i = 0; i < n; i++) x[i] = g_seed ? g_seed[i % 32] : 0; } static void question_key(char cls, uint32_t id, uint8_t key[32]){ char q[48]; int n = snprintf(q, sizeof q, "%c@1|%u", cls, id); sha256(q, (size_t)n, key); } static void record_hash(const settle_record_t *r, char out[72]){ char keyhex[65], s[128]; uint8_t h[32]; hex32(r->key, keyhex); int n = snprintf(s, sizeof s, "%s|%s|%.1f", keyhex, r->answer, r->created); sha256(s, (size_t)n, h); memcpy(out, "sha256:", 7); hex32(h, out + 7); } static const char *rules(char cls, uint32_t id){ if (cls == 'A') { uint32_t r = id % 7; return r == 0 ? "decline" : (r < 3 ? "authorize_capped" : "authorize"); } uint32_t q = id % 11; return q == 0 ? "alert" : (q < 4 ? "inspect" : "normal"); } static settle_record_t *lookup(settle_ctx_t *c, const uint8_t key[32], double now){ uint32_t i, h = ((uint32_t)key[0] << 24 | (uint32_t)key[1] << 16 | (uint32_t)key[2] << 8 | key[3]) % SETTLE_RECORDS; for (i = 0; i < SETTLE_RECORDS; i++) { settle_record_t *r = &c->rec[(h + i) % SETTLE_RECORDS]; if (!r->used) return NULL; if (!memcmp(r->key, key, 32)) { if (r->valid_until <= now) { r->used = 0; return NULL; } return r; } } return NULL; } static settle_record_t *insert(settle_ctx_t *c, const uint8_t key[32]){ uint32_t i, h = ((uint32_t)key[0] << 24 | (uint32_t)key[1] << 16 | (uint32_t)key[2] << 8 | key[3]) % SETTLE_RECORDS; for (i = 0; i < SETTLE_RECORDS; i++) { settle_record_t *r = &c->rec[(h + i) % SETTLE_RECORDS]; if (!r->used || !memcmp(r->key, key, 32)) { memset(r, 0, sizeof *r); memcpy(r->key, key, 32); r->used = 1; r->id = ++c->rec_next; return r; } } return NULL; } int settle_init(settle_ctx_t *c, const uint8_t seed[32], double temperature_k, double validity_s){ memset(c, 0, sizeof *c); g_seed = seed; crypto_sign_keypair(c->pk, c->sk); g_seed = NULL; memcpy(c->prev_hash, "sha256:0000000000000000000000000000000000000000000000000000000000000000", 72); c->temperature_k = temperature_k; c->validity_s = validity_s; c->cycle_j = 5e-6 * 1.8 / 1e6 / 0.85; c->bits_committed = 510; c->boundary = "node device, wired charger link, backend share, network"; c->rules_hash = "sha256:fc5b42fd1881fb0f66bac5e59447050c09477baff2a45fcf5c28e007616b0bf9"; return 0; } void settle_pk_hex(const settle_ctx_t *c, char out[65]){ hex32(c->pk, out); } /* canonical decimal: six significant digits in scientific form, the same text Python's "%.6e" gives */ static const char *dec(char *buf, double v){ snprintf(buf, 24, "%.6e", v); return buf; } #define PUT(...) do { int _n = snprintf(p, (size_t)(end - p), __VA_ARGS__); if (_n < 0 || p + _n >= end) return -1; p += _n; } while (0) int settle_decide(settle_ctx_t *c, char cls, uint32_t id, double now, const settle_meter_t *m, char *out, size_t outlen, settle_result_t *res){ uint8_t key[32]; char keyhex[65], rechash[72] = "", d1[24], d2[24], d3[24], d4[24], d5[24], d6[24], d7[24], d8[24], d9[24]; question_key(cls, id, key); hex32(key, keyhex); settle_record_t *r = lookup(c, key, now); int served = r != NULL; double deriv_j; if (served) { r->reuse++; deriv_j = r->deriv_j; } else { r = insert(c, key); if (!r) return -2; strncpy(r->answer, rules(cls, id), SETTLE_ANSWER - 1); r->created = now; r->valid_until = now + c->validity_s; r->deriv_j = 100000.0 * c->cycle_j; deriv_j = r->deriv_j; } record_hash(r, rechash); /* joules_total is the sum of the six-digit breakdown values as printed, so verifiers add the same numbers */ double parts[5] = { m->compute_j, m->movement_j, m->idle_alloc_j, m->shared_alloc_j, m->upstream_j }, total = 0; int i; for (i = 0; i < 5; i++) { char b[24]; total += strtod(dec(b, parts[i]), NULL); } double floor_j = c->bits_committed * 1.380649e-23 * c->temperature_k * 0.6931471805599453, decades = total > 0 ? log10(total / floor_j) : 0; /* core: every field between the version and the algorithm names, in canonical order */ char core[2048], *p = core, *end = core + sizeof core; PUT("\"kind\":\"decision\",\"capability\":\"oer.decision.%s\",\"intent\":\"%s\",", cls == 'A' ? "ev_charging_authorization" : "pipeline_sensor_check", served ? "replicate" : "create"); PUT("\"subject\":{\"inputs\":[\"sha256:%s\",\"%s\"],\"output\":\"%s\"},", keyhex, c->rules_hash, r->answer); PUT("\"work\":{\"status\":1,\"outcome\":\"%s\",\"latency_s\":\"%s\"},", served ? "served_from_record" : "derive", dec(d1, m->latency_s)); PUT("\"energy\":{\"joules_total\":\"%.12e\",\"boundary\":\"%s\",\"breakdown\":{\"compute_j\":\"%s\",\"movement_j\":\"%s\",\"idle_alloc_j\":\"%s\",\"shared_alloc_j\":\"%s\",\"upstream_j\":\"%s\"},\"method\":\"%s\",\"temperature_k\":\"%s\"},", total, c->boundary, dec(d2, m->compute_j), dec(d3, m->movement_j), dec(d4, m->idle_alloc_j), dec(d5, m->shared_alloc_j), dec(d6, m->upstream_j), m->method, dec(d7, c->temperature_k)); PUT("\"provenance\":{\"how\":\"measured\",\"device\":\"hardware\",\"weakest_input\":{\"how\":\"%s\",\"input\":\"%s\"}},", m->weakest_how, m->weakest_input); PUT("\"settlement\":{\"question_key\":\"sha256:%s\",\"served_from_record\":%s,\"record_id\":\"r%u\",\"record_hash\":\"%s\",\"reuse_count\":%u,\"derivation_j\":\"%s\",\"valid_until\":%lld},", keyhex, served ? "true" : "false", r->id, rechash, r->reuse, dec(d8, deriv_j), (long long)r->valid_until); PUT("\"floor\":{\"bits_committed\":%u,\"bits_delivered\":\"1.585000e+00\",\"floor_j\":\"%s\",\"decades_above_floor\":\"%.6e\"},", c->bits_committed, dec(d9, floor_j), decades); /* body = canonical bytes: version, core, algorithm names */ char body[2304]; int blen = snprintf(body, sizeof body, "{\"oer\":\"2.0\",%s\"hash_alg\":\"sha-256\",\"sig_alg\":\"ed25519\"}", core); if (blen < 0 || blen >= (int)sizeof body) return -1; uint8_t h[32]; char rid[72] = "sha256:", rhash[72] = "sha256:"; sha256(body, (size_t)blen, h); hex32(h, rid + 7); sha256_t s; sha256_init(&s); sha256_update(&s, c->prev_hash, strlen(c->prev_hash)); sha256_update(&s, body, (size_t)blen); sha256_final(&s, h); hex32(h, rhash + 7); unsigned char sm[72 + 64]; unsigned long long smlen; crypto_sign(sm, &smlen, (const unsigned char *)rhash, 71, c->sk); char sig[129]; { static const char *hx = "0123456789abcdef"; for (i = 0; i < 64; i++) { sig[2 * i] = hx[sm[i] >> 4]; sig[2 * i + 1] = hx[sm[i] & 15]; } sig[128] = 0; } int olen = snprintf(out, outlen, "{\"oer\":\"2.0\",\"receipt_id\":\"%s\",%s\"chain\":{\"prev_hash\":\"%s\",\"receipt_hash\":\"%s\",\"signer\":\"ed25519:bench-node-01\",\"signature\":\"%s\"},\"hash_alg\":\"sha-256\",\"sig_alg\":\"ed25519\"}", rid, core, c->prev_hash, rhash, sig); if (olen < 0 || olen >= (int)outlen) return -1; memcpy(c->prev_hash, rhash, 72); c->receipt_n++; if (res) { res->status = 1; res->outcome = served ? "served_from_record" : "derive"; strncpy(res->answer, r->answer, SETTLE_ANSWER - 1); res->answer[SETTLE_ANSWER - 1] = 0; res->record_id = r->id; res->reuse = r->reuse; memcpy(res->receipt_hash, rhash, 72); res->joules_total = total; } return 0; } int settle_records_json(const settle_ctx_t *c, char *out, size_t outlen){ char *p = out, *end = out + outlen; int first = 1; uint32_t i; PUT("{"); for (i = 0; i < SETTLE_RECORDS; i++) { const settle_record_t *r = &c->rec[i]; if (!r->used) continue; char keyhex[65]; hex32(r->key, keyhex); PUT("%s\"r%u\":{\"key\":\"%s\",\"answer\":\"%s\",\"created\":%.1f,\"valid_until\":%.1f}", first ? "" : ",", r->id, keyhex, r->answer, r->created, r->valid_until); first = 0; } PUT("}"); return 0; }