docs: story 34 — crypto builtins (digests + HMAC)

- SHA-1 (WS-handshake hard req, RFC 6455 worked example as acceptance),
  SHA-256, HMAC-SHA256 over Bytes; hand-rolled C per doctrine, FIPS/RFC
  vector fixtures; four forks recorded (namespace, shape, source, file)
- gates chain item 24; digest floor for held 21 + ETag row; 24's
  dependency note repointed; board + table rows (held seqs bumped)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
shoney.arickathil 2026-08-22 17:34:27 +02:00
parent b8416f0416
commit 5ec60e8e77
3 changed files with 111 additions and 8 deletions

View file

@ -243,6 +243,7 @@ that sequences its tasks. Read one, approve, then the next starts.
| 23 | [io_uring group-commit](language-runtime-database/refine/23-io-uring-commit.md) | ⬜ fifth in chain, after stage 3 + 22 |
| 32 | [WAL checkpoint](language-runtime-database/refine/32-wal-checkpoint.md) | ⬜ last in chain, after 23 — disk reclamation + bounded replay (story written 2026-08-21) |
| 33 | [Single-file store](language-runtime-database/refine/33-single-file-db.md) | ⬜ off-chain, small — `WO_DATA=<path>.db` file form; driver-only (story written 2026-08-22) |
| 34 | [Crypto builtins](language-runtime-database/refine/34-crypto-builtins.md) | ⬜ off-chain but GATES 24 (WS handshake needs SHA-1) — digests + HMAC as vector-verified C builtins (story written 2026-08-22) |
| 20 | [Cross-program tables](language-runtime-database/hold/20-cross-program-tables.md) | ⏸ hold (2026-08-21); channel done (branch ipc-attach keeps its manifest) |
| 21 | [Keypair attach auth](language-runtime-database/hold/21-keypair-attach-auth.md) | ⏸ hold (2026-08-21); crypto+handshake done (branch keypair-auth keeps its manifest) |
| 25 | [HTTP service layer](../superpowers/plans/2026-08-01-http-service-layer.md) | ⏸ hold (2026-08-21) — story file removed; the plan doc remains |

View file

@ -108,14 +108,15 @@ still pending IS the runtime-concurrency chain; order:
| 19 | 23 | [io_uring group-commit](refine/23-io-uring-commit.md) | WAL WRITE+FSYNC chains on the arc's per-shard rings; fsync fallback kept (after 22 + the arc). *(was 9f)* |
| 20 | 32 | [WAL checkpoint](refine/32-wal-checkpoint.md) | **NEW 2026-08-21** (stage-3 guarantee refinement found the hole) — the WAL is append-only forever: snapshot + truncate reclaims disk and bounds replay time; every durability guarantee byte-identical; crash mid-checkpoint recovers from the previous snapshot + full tail. After 23 (composes with group-commit); RAM slot-reuse already contracted in `04-db-binding.md`. |
| 21 | 33 | [Single-file store](refine/33-single-file-db.md) | **NEW 2026-08-22** — `WO_DATA=<path>.db`: a file path IS the wal (the store already lives in exactly one file; this makes the surface say so). Driver-only, independent of the chain; composes with 32's rename-swap. |
| 22 | 25 | [HTTP service layer](../../superpowers/plans/2026-08-01-http-service-layer.md) | `service` blocks lower onto the framework (after 9b + 20 by their own precedence notes). **HELD 2026-08-21** — story file removed; the plan doc remains. *(was 10)* |
| 23 | 18 | [framework v2: memory-rich features](hold/18-memory-db-features.md) | spec+plan approved: TTL cache, @table flags, durable job queue, `transaction { }` over the WAL's staged batch. **Demoted from seq 14**: more surface on a framework with one consumer, and the cache still stores `Text` because there are no generics |
| 24 | 27 | [Query grammar corpus](hold/27-query-grammar-corpus.md) | grow the query grammar from real corpora; likely collapses to "confirm `len(query)` + add `exists`"; precedes 28. *(was 9g)* |
| 25 | 26 | [Blue-green deploy](hold/26-blue-green-deploy.md) | two VM slots, in-runtime compile, atomic switch, resident rollback (plan authored after 9 + 25). *(was 12)* |
| 26 | 20 | [Cross-program tables](hold/20-cross-program-tables.md) | attach to a running program's database over local IPC; owner stays the single writer (channel half-built). **Demoted from seq 16**: new distribution surface while there is no TLS, no crypto, and the multi-shard DB still traps. *(was 9c)* |
| 27 | 21 | [Keypair attach auth](hold/21-keypair-attach-auth.md) | program identity is a keypair; mutual challenge–response at attach (crypto half-built; plan folds into 20's). **Demoted with 20** — and it needs crypto primitives that do not exist. *(was 9d)* |
| 28 | 28 | [skillhost host workload](hold/28-skillhost-host-workload.md) | host-shaped driving workload naming runtime gaps — demoted with the framework goal. *(was 14)* |
| 29 | 29 | [Compile-time metaprogramming](hold/29-compile-time-metaprogramming.md) | `@derive(...)` from class-table metadata; held with the parked drain by the 2026-08-08 scope directive. *(was 13)* |
| 22 | 34 | [Crypto builtins](refine/34-crypto-builtins.md) | **NEW 2026-08-22** — SHA-1/SHA-256/HMAC-SHA256 as C builtins over Bytes (no bitwise ops in the language, hand-rolled per doctrine, vector-verified). GATES 24's WS handshake; digest floor for held 21 and the ETag row. |
| 23 | 25 | [HTTP service layer](../../superpowers/plans/2026-08-01-http-service-layer.md) | `service` blocks lower onto the framework (after 9b + 20 by their own precedence notes). **HELD 2026-08-21** — story file removed; the plan doc remains. *(was 10)* |
| 24 | 18 | [framework v2: memory-rich features](hold/18-memory-db-features.md) | spec+plan approved: TTL cache, @table flags, durable job queue, `transaction { }` over the WAL's staged batch. **Demoted from seq 14**: more surface on a framework with one consumer, and the cache still stores `Text` because there are no generics |
| 25 | 27 | [Query grammar corpus](hold/27-query-grammar-corpus.md) | grow the query grammar from real corpora; likely collapses to "confirm `len(query)` + add `exists`"; precedes 28. *(was 9g)* |
| 26 | 26 | [Blue-green deploy](hold/26-blue-green-deploy.md) | two VM slots, in-runtime compile, atomic switch, resident rollback (plan authored after 9 + 25). *(was 12)* |
| 27 | 20 | [Cross-program tables](hold/20-cross-program-tables.md) | attach to a running program's database over local IPC; owner stays the single writer (channel half-built). **Demoted from seq 16**: new distribution surface while there is no TLS, no crypto, and the multi-shard DB still traps. *(was 9c)* |
| 28 | 21 | [Keypair attach auth](hold/21-keypair-attach-auth.md) | program identity is a keypair; mutual challenge–response at attach (crypto half-built; plan folds into 20's). **Demoted with 20** — and it needs crypto primitives that do not exist. *(was 9d)* |
| 29 | 28 | [skillhost host workload](hold/28-skillhost-host-workload.md) | host-shaped driving workload naming runtime gaps — demoted with the framework goal. *(was 14)* |
| 30 | 29 | [Compile-time metaprogramming](hold/29-compile-time-metaprogramming.md) | `@derive(...)` from class-table metadata; held with the parked drain by the 2026-08-08 scope directive. *(was 13)* |
| ✅ | 17 | [library projects + `internal/`](done/17-library-projects-internal.md) | **LANDED 2026-08-20** — `kind = "library"` + entry-less check mode (retires the `--emit` workaround) and Go's `internal/` rule as WO-E108 at the consumer's `use`; driver-only, VM/GC untouched. `just web-app` 26/0 |

View file

@ -0,0 +1,101 @@
---
iteration: "34"
status: refine
---
# Iteration 34 — crypto builtins: digests and HMAC in the runtime
> Format: fiberloom `product/story-iteration-template`. Part of
> [Story — one language, one runtime, one database, one binary](../00-story.md).
>
> **Inserted 2026-08-22** — the framework ledger's oldest unowned gap
> gets an owner. The language has NO bitwise operators (a settled
> surface decision), so digests cannot be written in `.wo`; the
> ledger's recorded resolution stands: hand-rolled C builtins in the
> runtime — the libc-only doctrine permits hand-rolled crypto, and the
> code is bounded and well-specified. Off the concurrency chain but
> **gates chain position 4**: iteration 24's WebSocket handshake needs
> SHA-1 before chat can land.
## Why this iteration exists
Four consumers already wait on it, none able to proceed:
[iteration 24](24-chat-websocket-workload.md)'s upgrade handshake
(`Sec-WebSocket-Accept` = base64(SHA-1(key + GUID)) — SHA-1
specifically, not a choice); the framework's ETag/conditional-request
row (wants a content hash); HMAC-signed tokens the auth core can grow;
and held [iteration 21](../hold/21-keypair-attach-auth.md), whose
challenge–response needs primitives that "do not exist" (its demotion
note). Bytes and base64 landed with iteration 19 — the carriers exist,
only the digests are missing.
## Goals
- **Digest builtins over Bytes**: SHA-1 (the WS handshake's hard
requirement), SHA-256 (the modern default for ETag/HMAC), each
`Bytes -> Bytes`, streaming not required (whole-value, like every
existing builtin).
- **HMAC-SHA256** (`key: Bytes, msg: Bytes -> Bytes`) — the one
composition real services need (signed tokens, webhook signatures);
writing HMAC in `.wo` is impossible for the same no-bitwise reason.
- **Test vectors are the acceptance**: FIPS 180 / RFC 2202 / RFC 4231
vectors in a corpus fixture — a digest that "looks right" is worth
nothing.
- **The contract doc row**: names, arities, Bytes-in/Bytes-out, and the
explicit note that SHA-1 exists for protocol compatibility (WS), not
for new designs.
## Acceptance Criteria (draft — the spec refines)
- **Given** the published test vectors for SHA-1, SHA-256, and
HMAC-SHA256, **when** the corpus fixture runs them through the
builtins, **then** every output matches byte-for-byte (via the
existing base64/Bytes surface).
- **Given** the WS handshake's worked example from RFC 6455
(`dGhlIHNhbXBsZSBub25jZQ==` → `s3pPLMBiTxaQ9kYGzzhZRbK+xOo=`),
**when** composed in pure `.wo` from `sha1` + `base64_encode`,
**then** the exact accept token comes out — iteration 24's handshake
is provably one expression away.
- **Given** the full battery, **when** it runs, **then** nothing
regresses — new builtin ids only, no opcode, no `.wob` version bump
(the iteration-19/`time.ticks` precedent).
## Out Of Scope
- Asymmetric crypto (ed25519 signatures/keypairs) — held iteration 21's
spec decides what it needs when it unholds; this iteration lays the
digest floor it will stand on.
- TLS — permanently the proxy's job (framework doctrine).
- CRC32 — the ledger lists it, but no consumer is blocked on it; it
joins only if 24's spec finds a real need (rejecting speculative
surface).
- A password-hashing story (bcrypt/argon2) — no workload asks yet.
- Bitwise operators in the language — a separate, bigger surface
decision this iteration deliberately routes around.
## Info
Forks the spec must settle:
1. **The namespace.** The reserved stdlib namespaces are exactly
`fs`/`proc`/`net`/`time`/`json`/`env` (compiler-enforced list) — a
new `crypto` namespace touches that list plus the typechecker
table, or the functions ride an existing namespace. Leaning: a real
`crypto` namespace (the list exists to be grown deliberately; this
is deliberate).
2. **Surface shape**: `crypto.sha1(b: Bytes) -> Bytes`,
`crypto.sha256(b: Bytes) -> Bytes`,
`crypto.hmac_sha256(key: Bytes, msg: Bytes) -> Bytes` — Text
convenience overloads rejected (the caller has `bytes_of_text`).
3. **Implementation source**: hand-rolled C from the FIPS pseudocode
(~200 lines for both digests; well-trodden, vector-verified) — the
doctrine's shape. No linking against OpenSSL, ever.
4. **Where the code lives**: `runtime/src/crypto.c` beside sysio, or
inside builtin.c — file layout, the executor decides.
## Proposed Solution
Brainstorm → (small) spec settling the four forks → implement with the
`time.ticks` slice's shape (ids, one table row per compiler surface,
contract-doc rows, vector fixtures). Lands any time before iteration
24's spec; independent of 31/22/23.