feat: iteration 34 — digest builtins sha1/sha256/hmac_sha256 (ids 85-87)
- runtime/src/crypto.c: hand-rolled cores, whole-value over Bytes, allocation-free tails; VM half returns fresh Bytes, WO_T_BOUNDS on wrong class id (Bytes builtins' message shape) - test_crypto: RFC 3174 + FIPS 180-4 + RFC 4231 (incl. long-key case 6) + 63/64/65 block-boundary sweep + the RFC 6455 handshake input, 18/0 - compiler surface flat per house convention (sha1, not crypto.sha1 — matches base64_encode): types.ml signatures + result types, emit.ml ids/dispatch/arity/known-list/drop-table (fresh-Bytes entries so results get their drops) - corpus run/crypto-digests pins the .wo path through base64_encode - no .wob bump (ticks-84 precedent); WO_B_MAX 87; surface doc rows - slice marker + board row: iteration 24 (absorbing 31+34) executing - battery 12/12 fresh-built Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
7ca8b178ea
commit
5fc32b4926
12 changed files with 444 additions and 3 deletions
|
|
@ -285,6 +285,9 @@ let b_base64_encode = 80
|
|||
let b_base64_decode = 81
|
||||
let b_bytes_of_text = 82
|
||||
let b_text_of_bytes = 83
|
||||
let b_sha1 = 85
|
||||
let b_sha256 = 86
|
||||
let b_hmac_sha256 = 87
|
||||
let b_split = 28
|
||||
let b_split_ws = 29
|
||||
let b_join = 30
|
||||
|
|
@ -1083,6 +1086,7 @@ let builtin_ret (name : string) (argty : Ast.field_ty option) : Ast.field_ty opt
|
|||
| "float_to_text" | "base64_encode" | "text_of_bytes" -> Some (Scalar "Text")
|
||||
| "bytes_eq" -> Some (Scalar "Bool")
|
||||
| "bytes_slice" | "bytes_concat" | "bytes_of_text" -> Some (Scalar "Bytes")
|
||||
| "sha1" | "sha256" | "hmac_sha256" -> Some (Scalar "Bytes")
|
||||
| "base64_decode" -> Some (Nullable (Scalar "Bytes"))
|
||||
| _ -> None
|
||||
|
||||
|
|
@ -1099,7 +1103,9 @@ let is_builtin_name (n : string) =
|
|||
(* iteration 19: Float bridges and Bytes surface *)
|
||||
"float"; "trunc"; "parse_float"; "float_to_text"; "float_cmp"; "bytes_len"; "bytes_at";
|
||||
"bytes_slice"; "bytes_eq"; "bytes_concat"; "base64_encode"; "base64_decode";
|
||||
"bytes_of_text"; "text_of_bytes" ]
|
||||
"bytes_of_text"; "text_of_bytes";
|
||||
(* iteration 34: digests *)
|
||||
"sha1"; "sha256"; "hmac_sha256" ]
|
||||
|
||||
(* ---- unions and variants (haxe-parity Task 4) ------------------------
|
||||
|
||||
|
|
@ -3630,6 +3636,8 @@ and emit_builtin (p : pctx) (f : fstate) (v : views) ~(dst : int) ?expected (e :
|
|||
|| id = b_float_of_int || id = b_trunc || id = b_parse_float || id = b_float_to_text
|
||||
|| id = b_bytes_len || id = b_base64_encode || id = b_base64_decode
|
||||
|| id = b_bytes_of_text || id = b_text_of_bytes
|
||||
(* iteration 34, one argument *)
|
||||
|| id = b_sha1 || id = b_sha256
|
||||
then 1
|
||||
else if
|
||||
id = b_multi_push || id = b_multi_get || id = b_map_get || id = b_map_has
|
||||
|
|
@ -3640,6 +3648,8 @@ and emit_builtin (p : pctx) (f : fstate) (v : views) ~(dst : int) ?expected (e :
|
|||
|| id = b_map_key_at || id = b_map_val_at
|
||||
(* iteration 19, two arguments *)
|
||||
|| id = b_float_cmp || id = b_bytes_at || id = b_bytes_eq || id = b_bytes_concat
|
||||
(* iteration 34, two arguments *)
|
||||
|| id = b_hmac_sha256
|
||||
then 2
|
||||
else 3 (* b_bytes_slice lands here with substr's shape: (value, start, len) *)
|
||||
in
|
||||
|
|
@ -3755,6 +3765,9 @@ and emit_builtin (p : pctx) (f : fstate) (v : views) ~(dst : int) ?expected (e :
|
|||
| "base64_decode" -> fixed b_base64_decode
|
||||
| "bytes_of_text" -> fixed b_bytes_of_text
|
||||
| "text_of_bytes" -> fixed b_text_of_bytes
|
||||
| "sha1" -> fixed b_sha1
|
||||
| "sha256" -> fixed b_sha256
|
||||
| "hmac_sha256" -> fixed b_hmac_sha256
|
||||
| "multi_new" | "map_new" ->
|
||||
let is_map = name = "map_new" in
|
||||
if args <> [] then bad (Printf.sprintf "builtin `%s` takes no arguments" name)
|
||||
|
|
|
|||
|
|
@ -835,6 +835,10 @@ let builtin_signatures : (string * int * builtin_arg_req list) list =
|
|||
("base64_decode", 1, [ ReqText ]);
|
||||
("bytes_of_text", 1, [ ReqText ]);
|
||||
("text_of_bytes", 1, [ ReqBytes ]);
|
||||
(* iteration 34: digests. Bytes in, Bytes out; HMAC is key-then-message. *)
|
||||
("sha1", 1, [ ReqBytes ]);
|
||||
("sha256", 1, [ ReqBytes ]);
|
||||
("hmac_sha256", 2, [ ReqBytes; ReqBytes ]);
|
||||
]
|
||||
|
||||
let rec unwrap_nullable (t : typ) : typ =
|
||||
|
|
@ -1040,6 +1044,7 @@ let builtin_confident_ret (name : string) (arg0 : typ option) : typ option =
|
|||
| "float_to_text" | "base64_encode" | "text_of_bytes" -> Some (TScalar "Text")
|
||||
| "bytes_eq" -> Some (TScalar "Bool")
|
||||
| "bytes_slice" | "bytes_concat" | "bytes_of_text" -> Some (TScalar "Bytes")
|
||||
| "sha1" | "sha256" | "hmac_sha256" -> Some (TScalar "Bytes")
|
||||
(* malformed base64 is nil, not a trap: it arrives from the network *)
|
||||
| "base64_decode" -> Some (TNullable (TScalar "Bytes"))
|
||||
| _ -> None
|
||||
|
|
|
|||
12
docs/in-progress/2026-08-23-chat-ws-lifecycle.md
Normal file
12
docs/in-progress/2026-08-23-chat-ws-lifecycle.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# In progress — chat + actor lifecycle (iteration 24, absorbing 31 + 34)
|
||||
|
||||
Active slice, branch `chat-ws-lifecycle`. Spec:
|
||||
[`../superpowers/specs/2026-08-23-chat-websocket-actor-lifecycle-design.md`](../superpowers/specs/2026-08-23-chat-websocket-actor-lifecycle-design.md)
|
||||
· plan:
|
||||
[`../superpowers/plans/2026-08-23-chat-ws-lifecycle.md`](../superpowers/plans/2026-08-23-chat-ws-lifecycle.md).
|
||||
|
||||
Stages: 1 crypto builtins (85–87) · 2 lifecycle (cap/`call`/monitor/
|
||||
`time.after`, ids 88–90, WO_T_ACTOR, WO-E226) · 3 framework WS
|
||||
(`ws_accept` + `wsframe`) · 4 chat sample + gate · 5 closeout.
|
||||
|
||||
This file is deleted when the slice lands (board convention).
|
||||
|
|
@ -274,6 +274,9 @@ unset `env.get` are nil.
|
|||
| `time.local(ms)` | `-> TimeParts` | `{ year, month, day, hour, minute, second, dow }`, dow 0 = Sunday |
|
||||
| `time.iso(ms)` | `-> Text` | UTC, second precision |
|
||||
| `time.ticks()` | `-> Int` | CLOCK_MONOTONIC microseconds (id 84, iteration 22's bench clock) — monotone, never wall time; only differences mean anything |
|
||||
| `sha1(bytes)` | `-> Bytes` | 20-byte digest (id 85, iteration 34) — exists because RFC 6455's Sec-WebSocket-Accept demands SHA-1 |
|
||||
| `sha256(bytes)` | `-> Bytes` | 32-byte digest (id 86, iteration 34) |
|
||||
| `hmac_sha256(key, msg)` | `-> Bytes` | RFC 2104 over SHA-256, both args Bytes (id 87, iteration 34); key > 64 bytes hashed first |
|
||||
| `env.get(name)` | `-> ?Text` | unset is nil |
|
||||
| `env.stopping()` | `-> Bool` | SIGTERM/SIGINT latch, handlers installed on first use |
|
||||
| `net.listen(host, port)` | `-> Int` | IPv4, SO_REUSEADDR, backlog 64; returns an fd |
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ that sequences its tasks. Read one, approve, then the next starts.
|
|||
| -------- | --------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
||||
| Language | 🔄 [iteration 36 — operator parity](language-runtime-database/in-progress/36-operator-parity.md): `not`, bitwise `& \| ^ << >>`, hex/binary/`_` literals, compound assigns — CODE LANDED 2026-08-22 (branch operator-parity, `.wob` v6, all gates green; reference project `.dev/reference/go` drove the design). Awaiting the developer's MANUAL pass on `docs/examples/operators/` (no test fixtures by directive); unblocks story 34's pure-`.wo` HMAC question | [plan](../superpowers/plans/2026-08-22-operator-parity.md) |
|
||||
| Language | the framework v1-polish slice landed 2026-08-20 (branch framework-v1, awaiting merge); next per the order: brainstorm 20/21's forks | [order](#implementation-order-re-sequenced-2026-08-21--concurrency-chain) |
|
||||
| Runtime | nothing active — 22 landed 2026-08-21; next per the chain: iteration 31's spec brainstorm | [order](#implementation-order-re-sequenced-2026-08-21--concurrency-chain) |
|
||||
| Runtime | 🔄 **iteration 24 (absorbing 31 + 34): chat + actor lifecycle** — spec + plan approved 2026-08-23 (24 absorbs 31 by directive; 34 resolved C-builtins); executing on branch `chat-ws-lifecycle` | [marker](../in-progress/2026-08-23-chat-ws-lifecycle.md) · [plan](../superpowers/plans/2026-08-23-chat-ws-lifecycle.md) |
|
||||
|
||||
The active slice's marker doc lives in [`in-progress/`](../in-progress/) —
|
||||
one file, deleted when the slice lands. Everything else pending is the
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <time.h>
|
||||
|
||||
#include "cont.h"
|
||||
#include "crypto.h"
|
||||
#include "gc.h"
|
||||
|
||||
/* type checks on receiver headers: wrong native class traps BOUNDS */
|
||||
|
|
@ -172,6 +173,8 @@ int wo_builtin(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg) {
|
|||
return wo_builtin_json(vm, R, ins, msg);
|
||||
if ((C >= WO_B_SYS_FIRST && C <= WO_B_PROC_RUN) || C == WO_B_TIME_TICKS)
|
||||
return wo_builtin_sys(vm, R, ins, msg);
|
||||
if (C >= WO_B_SHA1 && C <= WO_B_HMAC_SHA256)
|
||||
return wo_builtin_crypto(vm, R, ins, msg);
|
||||
if (C >= WO_B_DB_INSERT && C <= WO_B_DB_PROBE) {
|
||||
/* arc stage 3: the database is an actor on shard 0. A worker shard
|
||||
* has no engine by design — its statement marshals, parks, resumes
|
||||
|
|
|
|||
252
runtime/src/crypto.c
Normal file
252
runtime/src/crypto.c
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
/* crypto.c — SHA-1, SHA-256, HMAC-SHA256 (iteration 34). Hand-rolled,
|
||||
* libc-only, whole-value: init/update/final collapsed into one pass over
|
||||
* one buffer, because every builtin here takes complete Bytes — there is
|
||||
* no streaming surface. Vectors: RFC 3174, FIPS 180-4, RFC 4231 — pinned
|
||||
* in test/test_crypto.c. SHA-1 exists for the WebSocket handshake
|
||||
* (Sec-WebSocket-Accept is SHA-1 by RFC 6455, not a choice). */
|
||||
#include "crypto.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "obj.h"
|
||||
#include "wob.h"
|
||||
|
||||
static uint32_t rotl32(uint32_t x, int n) { return (x << n) | (x >> (32 - n)); }
|
||||
static uint32_t rotr32(uint32_t x, int n) { return (x >> n) | (x << (32 - n)); }
|
||||
|
||||
/* Both digests consume the message in 64-byte blocks with the same
|
||||
* padding scheme (0x80, zeros, 64-bit big-endian bit length). The tail
|
||||
* is at most two blocks; building it on the stack keeps the cores
|
||||
* allocation-free. */
|
||||
|
||||
static void sha1_block(uint32_t h[5], const uint8_t *p) {
|
||||
uint32_t w[80];
|
||||
for (int i = 0; i < 16; i++)
|
||||
w[i] = (uint32_t)p[i * 4] << 24 | (uint32_t)p[i * 4 + 1] << 16 |
|
||||
(uint32_t)p[i * 4 + 2] << 8 | p[i * 4 + 3];
|
||||
for (int i = 16; i < 80; i++)
|
||||
w[i] = rotl32(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
|
||||
uint32_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4];
|
||||
for (int i = 0; i < 80; i++) {
|
||||
uint32_t f, k;
|
||||
if (i < 20) {
|
||||
f = (b & c) | (~b & d);
|
||||
k = 0x5A827999u;
|
||||
} else if (i < 40) {
|
||||
f = b ^ c ^ d;
|
||||
k = 0x6ED9EBA1u;
|
||||
} else if (i < 60) {
|
||||
f = (b & c) | (b & d) | (c & d);
|
||||
k = 0x8F1BBCDCu;
|
||||
} else {
|
||||
f = b ^ c ^ d;
|
||||
k = 0xCA62C1D6u;
|
||||
}
|
||||
uint32_t t = rotl32(a, 5) + f + e + k + w[i];
|
||||
e = d;
|
||||
d = c;
|
||||
c = rotl32(b, 30);
|
||||
b = a;
|
||||
a = t;
|
||||
}
|
||||
h[0] += a;
|
||||
h[1] += b;
|
||||
h[2] += c;
|
||||
h[3] += d;
|
||||
h[4] += e;
|
||||
}
|
||||
|
||||
void wo_sha1(const uint8_t *msg, size_t len, uint8_t out[20]) {
|
||||
uint32_t h[5] = {0x67452301u, 0xEFCDAB89u, 0x98BADCFEu, 0x10325476u,
|
||||
0xC3D2E1F0u};
|
||||
size_t i = 0;
|
||||
for (; i + 64 <= len; i += 64) sha1_block(h, msg + i);
|
||||
uint8_t tail[128];
|
||||
size_t rem = len - i;
|
||||
memcpy(tail, msg + i, rem);
|
||||
tail[rem] = 0x80;
|
||||
size_t tlen = rem + 1 <= 56 ? 64 : 128;
|
||||
memset(tail + rem + 1, 0, tlen - rem - 1 - 8);
|
||||
uint64_t bits = (uint64_t)len * 8;
|
||||
for (int b = 0; b < 8; b++) tail[tlen - 1 - b] = (uint8_t)(bits >> (8 * b));
|
||||
sha1_block(h, tail);
|
||||
if (tlen == 128) sha1_block(h, tail + 64);
|
||||
for (int w = 0; w < 5; w++) {
|
||||
out[w * 4] = (uint8_t)(h[w] >> 24);
|
||||
out[w * 4 + 1] = (uint8_t)(h[w] >> 16);
|
||||
out[w * 4 + 2] = (uint8_t)(h[w] >> 8);
|
||||
out[w * 4 + 3] = (uint8_t)h[w];
|
||||
}
|
||||
}
|
||||
|
||||
static const uint32_t K256[64] = {
|
||||
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};
|
||||
|
||||
static void sha256_block(uint32_t h[8], const uint8_t *p) {
|
||||
uint32_t w[64];
|
||||
for (int i = 0; i < 16; i++)
|
||||
w[i] = (uint32_t)p[i * 4] << 24 | (uint32_t)p[i * 4 + 1] << 16 |
|
||||
(uint32_t)p[i * 4 + 2] << 8 | p[i * 4 + 3];
|
||||
for (int i = 16; i < 64; i++) {
|
||||
uint32_t s0 = rotr32(w[i - 15], 7) ^ rotr32(w[i - 15], 18) ^ (w[i - 15] >> 3);
|
||||
uint32_t s1 = rotr32(w[i - 2], 17) ^ rotr32(w[i - 2], 19) ^ (w[i - 2] >> 10);
|
||||
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
|
||||
}
|
||||
uint32_t a = h[0], b = h[1], c = h[2], d = h[3];
|
||||
uint32_t e = h[4], f = h[5], g = h[6], hh = h[7];
|
||||
for (int i = 0; i < 64; i++) {
|
||||
uint32_t S1 = rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25);
|
||||
uint32_t ch = (e & f) ^ (~e & g);
|
||||
uint32_t t1 = hh + S1 + ch + K256[i] + w[i];
|
||||
uint32_t S0 = rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22);
|
||||
uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
|
||||
uint32_t t2 = S0 + maj;
|
||||
hh = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = d + t1;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = t1 + t2;
|
||||
}
|
||||
h[0] += a;
|
||||
h[1] += b;
|
||||
h[2] += c;
|
||||
h[3] += d;
|
||||
h[4] += e;
|
||||
h[5] += f;
|
||||
h[6] += g;
|
||||
h[7] += hh;
|
||||
}
|
||||
|
||||
void wo_sha256(const uint8_t *msg, size_t len, uint8_t out[32]) {
|
||||
uint32_t h[8] = {0x6a09e667u, 0xbb67ae85u, 0x3c6ef372u, 0xa54ff53au,
|
||||
0x510e527fu, 0x9b05688cu, 0x1f83d9abu, 0x5be0cd19u};
|
||||
size_t i = 0;
|
||||
for (; i + 64 <= len; i += 64) sha256_block(h, msg + i);
|
||||
uint8_t tail[128];
|
||||
size_t rem = len - i;
|
||||
memcpy(tail, msg + i, rem);
|
||||
tail[rem] = 0x80;
|
||||
size_t tlen = rem + 1 <= 56 ? 64 : 128;
|
||||
memset(tail + rem + 1, 0, tlen - rem - 1 - 8);
|
||||
uint64_t bits = (uint64_t)len * 8;
|
||||
for (int b = 0; b < 8; b++) tail[tlen - 1 - b] = (uint8_t)(bits >> (8 * b));
|
||||
sha256_block(h, tail);
|
||||
if (tlen == 128) sha256_block(h, tail + 64);
|
||||
for (int w = 0; w < 8; w++) {
|
||||
out[w * 4] = (uint8_t)(h[w] >> 24);
|
||||
out[w * 4 + 1] = (uint8_t)(h[w] >> 16);
|
||||
out[w * 4 + 2] = (uint8_t)(h[w] >> 8);
|
||||
out[w * 4 + 3] = (uint8_t)h[w];
|
||||
}
|
||||
}
|
||||
|
||||
/* RFC 2104 over SHA-256: a key longer than the 64-byte block is hashed
|
||||
* first; shorter keys zero-pad. Two passes, no allocation. */
|
||||
void wo_hmac_sha256(const uint8_t *key, size_t klen, const uint8_t *msg,
|
||||
size_t mlen, uint8_t out[32]) {
|
||||
uint8_t k[64] = {0};
|
||||
if (klen > 64) {
|
||||
wo_sha256(key, klen, k); /* leaves 32 bytes, rest stays zero */
|
||||
} else {
|
||||
memcpy(k, key, klen);
|
||||
}
|
||||
uint8_t ipad[64], opad[64];
|
||||
for (int i = 0; i < 64; i++) {
|
||||
ipad[i] = k[i] ^ 0x36;
|
||||
opad[i] = k[i] ^ 0x5c;
|
||||
}
|
||||
/* inner = sha256(ipad || msg) — the message can be arbitrarily long, so
|
||||
* the inner pass re-runs the block loop by hand instead of concatenating */
|
||||
uint32_t h[8] = {0x6a09e667u, 0xbb67ae85u, 0x3c6ef372u, 0xa54ff53au,
|
||||
0x510e527fu, 0x9b05688cu, 0x1f83d9abu, 0x5be0cd19u};
|
||||
sha256_block(h, ipad);
|
||||
size_t i = 0;
|
||||
for (; i + 64 <= mlen; i += 64) sha256_block(h, msg + i);
|
||||
uint8_t tail[128];
|
||||
size_t rem = mlen - i;
|
||||
memcpy(tail, msg + i, rem);
|
||||
tail[rem] = 0x80;
|
||||
size_t tlen = rem + 1 <= 56 ? 64 : 128;
|
||||
memset(tail + rem + 1, 0, tlen - rem - 1 - 8);
|
||||
uint64_t bits = ((uint64_t)mlen + 64) * 8; /* +64: the ipad block */
|
||||
for (int b = 0; b < 8; b++) tail[tlen - 1 - b] = (uint8_t)(bits >> (8 * b));
|
||||
sha256_block(h, tail);
|
||||
if (tlen == 128) sha256_block(h, tail + 64);
|
||||
uint8_t inner[32];
|
||||
for (int w = 0; w < 8; w++) {
|
||||
inner[w * 4] = (uint8_t)(h[w] >> 24);
|
||||
inner[w * 4 + 1] = (uint8_t)(h[w] >> 16);
|
||||
inner[w * 4 + 2] = (uint8_t)(h[w] >> 8);
|
||||
inner[w * 4 + 3] = (uint8_t)h[w];
|
||||
}
|
||||
uint8_t outer[96];
|
||||
memcpy(outer, opad, 64);
|
||||
memcpy(outer + 64, inner, 32);
|
||||
wo_sha256(outer, 96, out);
|
||||
}
|
||||
|
||||
/* The VM half: Bytes in, fresh Bytes out. Wrong class id traps
|
||||
* WO_T_BOUNDS with the Bytes builtins' message shape. */
|
||||
static const wo_str *arg_bytes(uint64_t r, const char **msg) {
|
||||
const wo_str *b = (const wo_str *)(uintptr_t)r;
|
||||
if (!b || b->h.class_id != WO_CLS_BYTES) {
|
||||
*msg = "not a bytes value";
|
||||
return NULL;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
int wo_builtin_crypto(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg) {
|
||||
wo_rt *rt = &vm->rt;
|
||||
uint8_t A = wo_ins_a(ins), B = wo_ins_b(ins), C = wo_ins_c(ins);
|
||||
uint8_t digest[32];
|
||||
uint32_t dlen;
|
||||
switch (C) {
|
||||
case WO_B_SHA1: {
|
||||
const wo_str *b = arg_bytes(R[B], msg);
|
||||
if (!b) return WO_T_BOUNDS;
|
||||
wo_sha1((const uint8_t *)b->data, b->len, digest);
|
||||
dlen = 20;
|
||||
break;
|
||||
}
|
||||
case WO_B_SHA256: {
|
||||
const wo_str *b = arg_bytes(R[B], msg);
|
||||
if (!b) return WO_T_BOUNDS;
|
||||
wo_sha256((const uint8_t *)b->data, b->len, digest);
|
||||
dlen = 32;
|
||||
break;
|
||||
}
|
||||
case WO_B_HMAC_SHA256: {
|
||||
const wo_str *k = arg_bytes(R[B], msg);
|
||||
const wo_str *m = k ? arg_bytes(R[B + 1], msg) : NULL;
|
||||
if (!m) return WO_T_BOUNDS;
|
||||
wo_hmac_sha256((const uint8_t *)k->data, k->len,
|
||||
(const uint8_t *)m->data, m->len, digest);
|
||||
dlen = 32;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
*msg = "unknown crypto builtin";
|
||||
return WO_T_BOUNDS;
|
||||
}
|
||||
wo_str *out = wo_bytes_new(rt, (const char *)digest, dlen);
|
||||
if (!out) {
|
||||
*msg = "out of memory";
|
||||
return WO_T_OOM;
|
||||
}
|
||||
R[A] = (uint64_t)(uintptr_t)out;
|
||||
return 0;
|
||||
}
|
||||
19
runtime/src/crypto.h
Normal file
19
runtime/src/crypto.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/* crypto.h — hand-rolled digests (iteration 34). Whole-value, libc-only.
|
||||
* The raw cores are exposed for the unit test; the VM enters through
|
||||
* wo_builtin_crypto (ids WO_B_SHA1..WO_B_HMAC_SHA256). */
|
||||
#ifndef WO_CRYPTO_H
|
||||
#define WO_CRYPTO_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "vm.h"
|
||||
|
||||
void wo_sha1(const uint8_t *msg, size_t len, uint8_t out[20]);
|
||||
void wo_sha256(const uint8_t *msg, size_t len, uint8_t out[32]);
|
||||
void wo_hmac_sha256(const uint8_t *key, size_t klen, const uint8_t *msg,
|
||||
size_t mlen, uint8_t out[32]);
|
||||
|
||||
int wo_builtin_crypto(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg);
|
||||
|
||||
#endif
|
||||
|
|
@ -451,9 +451,15 @@ enum {
|
|||
* the bench clock (iteration 22). Monotone,
|
||||
* never wall time: immune to NTP steps; only
|
||||
* differences mean anything. */
|
||||
/* ---- iteration 34: digests (crypto.c). Whole-value over Bytes; SHA-1
|
||||
* exists because RFC 6455's Sec-WebSocket-Accept demands it. ---- */
|
||||
WO_B_SHA1 = 85, /* (bytes) -> fresh 20-byte Bytes */
|
||||
WO_B_SHA256 = 86, /* (bytes) -> fresh 32-byte Bytes */
|
||||
WO_B_HMAC_SHA256 = 87, /* (key bytes, msg bytes) -> fresh 32-byte Bytes,
|
||||
* RFC 2104 (key > 64 bytes hashed first) */
|
||||
};
|
||||
|
||||
#define WO_B_MAX 84u
|
||||
#define WO_B_MAX 87u
|
||||
/* ids at or above this one live in sysio.c, not builtin.c */
|
||||
#define WO_B_SYS_FIRST WO_B_FS_EXISTS
|
||||
|
||||
|
|
|
|||
112
runtime/test/test_crypto.c
Normal file
112
runtime/test/test_crypto.c
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/* test_crypto — the digest cores against published vectors: SHA-1 (RFC
|
||||
* 3174), SHA-256 (FIPS 180-4), HMAC-SHA256 (RFC 4231 cases 1-4), plus a
|
||||
* 63/64/65-byte block-boundary sweep. Boundary constants precomputed with
|
||||
* python3: hashlib.sha256(b"a"*63).hexdigest() etc. */
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "crypto.h"
|
||||
#include "t.h"
|
||||
|
||||
static void hex(const uint8_t *d, size_t n, char *out) {
|
||||
static const char *h = "0123456789abcdef";
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
out[i * 2] = h[d[i] >> 4];
|
||||
out[i * 2 + 1] = h[d[i] & 15];
|
||||
}
|
||||
out[n * 2] = 0;
|
||||
}
|
||||
|
||||
static void t_sha1(const char *msg, size_t len, const char *want) {
|
||||
uint8_t d[20];
|
||||
char got[41];
|
||||
wo_sha1((const uint8_t *)msg, len, d);
|
||||
hex(d, 20, got);
|
||||
T_CHECK(strcmp(got, want) == 0);
|
||||
}
|
||||
|
||||
static void t_sha256(const char *msg, size_t len, const char *want) {
|
||||
uint8_t d[32];
|
||||
char got[65];
|
||||
wo_sha256((const uint8_t *)msg, len, d);
|
||||
hex(d, 32, got);
|
||||
T_CHECK(strcmp(got, want) == 0);
|
||||
}
|
||||
|
||||
static void t_hmac(const uint8_t *key, size_t klen, const char *msg,
|
||||
size_t mlen, const char *want) {
|
||||
uint8_t d[32];
|
||||
char got[65];
|
||||
wo_hmac_sha256(key, klen, (const uint8_t *)msg, mlen, d);
|
||||
hex(d, 32, got);
|
||||
T_CHECK(strcmp(got, want) == 0);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
/* RFC 3174 */
|
||||
t_sha1("abc", 3, "a9993e364706816aba3e25717850c26c9cd0d89d");
|
||||
t_sha1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56,
|
||||
"84983e441c3bd26ebaae4aa1f95129e5e54670f1");
|
||||
t_sha1("", 0, "da39a3ee5e6b4b0d3255bfef95601890afd80709");
|
||||
/* the WebSocket handshake's exact input (RFC 6455 §1.3 worked example):
|
||||
* sha1("dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11") */
|
||||
t_sha1("dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 60,
|
||||
"b37a4f2cc0624f1690f64606cf385945b2bec4ea");
|
||||
|
||||
/* FIPS 180-4 */
|
||||
t_sha256("abc", 3,
|
||||
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
|
||||
t_sha256("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56,
|
||||
"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1");
|
||||
t_sha256("", 0,
|
||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
|
||||
|
||||
/* block boundaries: python3 -c 'import hashlib
|
||||
[print(hashlib.sha256(b"a"*n).hexdigest()) for n in (63,64,65)]' */
|
||||
char a65[65];
|
||||
memset(a65, 'a', 65);
|
||||
t_sha256(a65, 63,
|
||||
"7d3e74a05d7db15bce4ad9ec0658ea98e3f06eeecf16b4c6fff2da457ddc2f34");
|
||||
t_sha256(a65, 64,
|
||||
"ffe054fe7ae0cb6dc65c3af9b61d5209f439851db43d0ba5997337df154668eb");
|
||||
t_sha256(a65, 65,
|
||||
"635361c48bb9eab14198e76ea8ab7f1a41685d6ad62aa9146d301d4f17eb0ae0");
|
||||
/* same sweep for sha1: hashlib.sha1 */
|
||||
t_sha1(a65, 63, "03f09f5b158a7a8cdad920bddc29b81c18a551f5");
|
||||
t_sha1(a65, 64, "0098ba824b5c16427bd7a1122a5a442a25ec644d");
|
||||
t_sha1(a65, 65, "11655326c708d70319be2610e8a57d9a5b959d3b");
|
||||
|
||||
/* RFC 4231 */
|
||||
{
|
||||
uint8_t k1[20];
|
||||
memset(k1, 0x0b, 20);
|
||||
t_hmac(k1, 20, "Hi There", 8,
|
||||
"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
|
||||
}
|
||||
t_hmac((const uint8_t *)"Jefe", 4, "what do ya want for nothing?", 28,
|
||||
"5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843");
|
||||
{
|
||||
uint8_t k3[20], m3[50];
|
||||
memset(k3, 0xaa, 20);
|
||||
memset(m3, 0xdd, 50);
|
||||
t_hmac(k3, 20, (const char *)m3, 50,
|
||||
"773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe");
|
||||
}
|
||||
{
|
||||
uint8_t k4[25], m4[50];
|
||||
for (int i = 0; i < 25; i++) k4[i] = (uint8_t)(i + 1);
|
||||
memset(m4, 0xcd, 50);
|
||||
t_hmac(k4, 25, (const char *)m4, 50,
|
||||
"82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b");
|
||||
}
|
||||
/* long-key path (key > 64 bytes is hashed first): RFC 4231 case 6 */
|
||||
{
|
||||
uint8_t k6[131];
|
||||
memset(k6, 0xaa, 131);
|
||||
t_hmac(k6, 131, "Test Using Larger Than Block-Size Key - Hash Key First",
|
||||
54,
|
||||
"60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54");
|
||||
}
|
||||
|
||||
return t_report("test_crypto");
|
||||
}
|
||||
3
tests/corpus/run/crypto-digests/fixture.out
Normal file
3
tests/corpus/run/crypto-digests/fixture.out
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
sha1: qZk+NkcGgWq6PiVxeFDCbJzQ2J0=
|
||||
sha256: ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=
|
||||
hmac: W9zBRr9gdU5qBCQmCJV1x1oAPwidJzmDnexYuWTsOEM=
|
||||
13
tests/corpus/run/crypto-digests/fixture.wo
Normal file
13
tests/corpus/run/crypto-digests/fixture.wo
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
-- iteration 34: digests end to end from .wo — sha1/sha256/hmac_sha256
|
||||
-- over Bytes, printed through base64_encode (iteration 19). Expected
|
||||
-- values are the RFC vectors: SHA-1("abc"), SHA-256("abc"), and
|
||||
-- HMAC-SHA256 key "Jefe" / RFC 4231 case 2's message.
|
||||
fn main() -> Int {
|
||||
let abc = bytes_of_text("abc");
|
||||
print("sha1: ${base64_encode(sha1(abc))}");
|
||||
print("sha256: ${base64_encode(sha256(abc))}");
|
||||
let key = bytes_of_text("Jefe");
|
||||
let msg = bytes_of_text("what do ya want for nothing?");
|
||||
print("hmac: ${base64_encode(hmac_sha256(key, msg))}");
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in a new issue