feat(crypto): ChaCha20-Poly1305 AEAD (rv2 8 phase A, ids 111/112)

- hand-rolled ChaCha20 + poly1305-donna-32 + RFC 8439 §2.8 AEAD in crypto.c;
  constant-time (add/xor/rotate + limb math, no tables, no data-dep branches),
  constant-time tag compare
- two bare-name crypto-family builtins beside sha256/hmac:
  chacha20poly1305_seal(key,nonce,aad,pt) -> Bytes (ct||tag)
  chacha20poly1305_open(key,nonce,aad,ct||tag) -> ?Bytes (nil on auth fail)
  key 32B, nonce 12B (caller-supplied, per TLS's per-record nonce need)
- wiring: wob.h enum + WO_B_MAX 112; builtin.c crypto dispatch range; loader.c
  arity 4; emit.ml (ids, arity_of 4-case, return type, is_builtin_name,
  name->id); types.ml (registration + return type)
- VERIFIED: matches RFC 8439 §2.8.2 byte-for-byte (vs python cryptography +
  the RFC vector); test_crypto 24/0 (Poly1305 §2.5.2 + AEAD seal/open/tamper);
  ASan/UBSan clean; runtime battery + compiler 557/0 green
- first rung of the TLS ladder (rv2 9 phase A)

(cherry picked from commit 961854a8f4e9e632b6fa17f7f2e519e2d08f4936)
This commit is contained in:
shoney.arickathil 2026-09-08 12:54:39 +02:00
parent 7f7a601e2f
commit ac52c3fdb5
8 changed files with 354 additions and 3 deletions

View file

@ -294,6 +294,9 @@ let b_text_of_bytes = 83
let b_sha1 = 85
let b_sha256 = 86
let b_hmac_sha256 = 87
(* runtime-v2 8 phase A: ChaCha20-Poly1305 AEAD (ids match wob.h 111/112) *)
let b_chacha20poly1305_seal = 111
let b_chacha20poly1305_open = 112
let b_call = 88
let b_monitor = 89
let b_split = 28
@ -1099,6 +1102,8 @@ let builtin_ret (name : string) (argty : Ast.field_ty option) : Ast.field_ty opt
| "bytes_eq" -> Some (Scalar "Bool")
| "bytes_slice" | "bytes_concat" | "bytes_of_text" -> Some (Scalar "Bytes")
| "sha1" | "sha256" | "hmac_sha256" -> Some (Scalar "Bytes")
| "chacha20poly1305_seal" -> Some (Scalar "Bytes")
| "chacha20poly1305_open" -> Some (Nullable (Scalar "Bytes"))
| "base64_decode" -> Some (Nullable (Scalar "Bytes"))
| _ -> None
@ -1117,7 +1122,9 @@ let is_builtin_name (n : string) =
"bytes_slice"; "bytes_eq"; "bytes_concat"; "base64_encode"; "base64_decode";
"bytes_of_text"; "text_of_bytes";
(* iteration 34: digests *)
"sha1"; "sha256"; "hmac_sha256" ]
"sha1"; "sha256"; "hmac_sha256";
(* runtime-v2 8 phase A: AEAD *)
"chacha20poly1305_seal"; "chacha20poly1305_open" ]
(* ---- unions and variants (haxe-parity Task 4) ------------------------
@ -3696,6 +3703,8 @@ and emit_builtin (p : pctx) (f : fstate) (v : views) ~(dst : int) ?expected (e :
(* iteration 24, two arguments *)
|| id = b_call
then 2
else if id = b_chacha20poly1305_seal || id = b_chacha20poly1305_open then 4
(* rv2 8: (key, nonce, aad, plaintext|ciphertext) *)
else 3 (* b_bytes_slice lands here with substr's shape: (value, start, len) *)
in
let container_id first_arg on_multi on_map =
@ -3815,6 +3824,8 @@ and emit_builtin (p : pctx) (f : fstate) (v : views) ~(dst : int) ?expected (e :
| "sha1" -> fixed b_sha1
| "sha256" -> fixed b_sha256
| "hmac_sha256" -> fixed b_hmac_sha256
| "chacha20poly1305_seal" -> fixed b_chacha20poly1305_seal
| "chacha20poly1305_open" -> fixed b_chacha20poly1305_open
| "multi_new" | "map_new" ->
let is_map = name = "map_new" in
if args <> [] then bad (Printf.sprintf "builtin `%s` takes no arguments" name)

View file

@ -953,6 +953,10 @@ let builtin_signatures : (string * int * builtin_arg_req list) list =
("sha1", 1, [ ReqBytes ]);
("sha256", 1, [ ReqBytes ]);
("hmac_sha256", 2, [ ReqBytes; ReqBytes ]);
(* runtime-v2 8 phase A: ChaCha20-Poly1305 AEAD. (key, nonce, aad,
plaintext|ciphertext). seal -> Bytes; open -> ?Bytes (nil on auth fail). *)
("chacha20poly1305_seal", 4, [ ReqBytes; ReqBytes; ReqBytes; ReqBytes ]);
("chacha20poly1305_open", 4, [ ReqBytes; ReqBytes; ReqBytes; ReqBytes ]);
]
let rec unwrap_nullable (t : typ) : typ =
@ -1159,6 +1163,8 @@ let builtin_confident_ret (name : string) (arg0 : typ option) : typ option =
| "bytes_eq" -> Some (TScalar "Bool")
| "bytes_slice" | "bytes_concat" | "bytes_of_text" -> Some (TScalar "Bytes")
| "sha1" | "sha256" | "hmac_sha256" -> Some (TScalar "Bytes")
| "chacha20poly1305_seal" -> Some (TScalar "Bytes")
| "chacha20poly1305_open" -> Some (TNullable (TScalar "Bytes"))
(* malformed base64 is nil, not a trap: it arrives from the network *)
| "base64_decode" -> Some (TNullable (TScalar "Bytes"))
| _ -> None

View file

@ -174,7 +174,8 @@ int wo_builtin(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg) {
if ((C >= WO_B_SYS_FIRST && C <= WO_B_PROC_RUN) || C == WO_B_TIME_TICKS
|| (C >= WO_B_NET_READ_DL && C <= WO_B_NET_CONNECT))
return wo_builtin_sys(vm, R, ins, msg);
if (C >= WO_B_SHA1 && C <= WO_B_HMAC_SHA256)
if ((C >= WO_B_SHA1 && C <= WO_B_HMAC_SHA256)
|| (C >= WO_B_CHACHA20POLY1305_SEAL && C <= WO_B_CHACHA20POLY1305_OPEN))
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

View file

@ -6,6 +6,7 @@
* (Sec-WebSocket-Accept is SHA-1 by RFC 6455, not a choice). */
#include "crypto.h"
#include <stdlib.h>
#include <string.h>
#include "obj.h"
@ -198,6 +199,200 @@ void wo_hmac_sha256(const uint8_t *key, size_t klen, const uint8_t *msg,
wo_sha256(outer, 96, out);
}
/* ---- ChaCha20-Poly1305 AEAD (rv2 8 phase A, RFC 8439) ------------------
* Hand-rolled, libc-only, constant-time by construction (add/xor/rotate and
* limb arithmetic; no data-dependent branches, no table lookups). The
* reference is RFC 8439; the paper .dev/reference/cryptography-06-00030.pdf
* describes the same algorithm. Vectors pinned in test/test_crypto.c. */
static uint32_t rd32le(const uint8_t *p) {
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) |
((uint32_t)p[3] << 24);
}
static void wr32le(uint8_t *p, uint32_t v) {
p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8);
p[2] = (uint8_t)(v >> 16); p[3] = (uint8_t)(v >> 24);
}
static void wr64le(uint8_t *p, uint64_t v) {
for (int i = 0; i < 8; i++) p[i] = (uint8_t)(v >> (8 * i));
}
#define CHACHA_QR(x, a, b, c, d) \
do { \
x[a] += x[b]; x[d] ^= x[a]; x[d] = rotl32(x[d], 16); \
x[c] += x[d]; x[b] ^= x[c]; x[b] = rotl32(x[b], 12); \
x[a] += x[b]; x[d] ^= x[a]; x[d] = rotl32(x[d], 8); \
x[c] += x[d]; x[b] ^= x[c]; x[b] = rotl32(x[b], 7); \
} while (0)
static void chacha20_block(const uint8_t key[32], uint32_t counter,
const uint8_t nonce[12], uint8_t out[64]) {
uint32_t s[16], x[16];
s[0] = 0x61707865u; s[1] = 0x3320646eu;
s[2] = 0x79622d32u; s[3] = 0x6b206574u;
for (int i = 0; i < 8; i++) s[4 + i] = rd32le(key + 4 * i);
s[12] = counter;
s[13] = rd32le(nonce); s[14] = rd32le(nonce + 4); s[15] = rd32le(nonce + 8);
for (int i = 0; i < 16; i++) x[i] = s[i];
for (int i = 0; i < 10; i++) {
CHACHA_QR(x, 0, 4, 8, 12); CHACHA_QR(x, 1, 5, 9, 13);
CHACHA_QR(x, 2, 6, 10, 14); CHACHA_QR(x, 3, 7, 11, 15);
CHACHA_QR(x, 0, 5, 10, 15); CHACHA_QR(x, 1, 6, 11, 12);
CHACHA_QR(x, 2, 7, 8, 13); CHACHA_QR(x, 3, 4, 9, 14);
}
for (int i = 0; i < 16; i++) wr32le(out + 4 * i, x[i] + s[i]);
}
/* XOR the ChaCha20 keystream (from `counter`) over `len` bytes. in==out safe. */
static void chacha20_xor(const uint8_t key[32], const uint8_t nonce[12],
uint32_t counter, const uint8_t *in, size_t len,
uint8_t *out) {
uint8_t blk[64];
size_t off = 0;
while (len > 0) {
chacha20_block(key, counter, nonce, blk);
size_t n = len < 64 ? len : 64;
for (size_t i = 0; i < n; i++) out[off + i] = in[off + i] ^ blk[i];
off += n; len -= n; counter++;
}
}
/* Poly1305 one-shot (poly1305-donna 32-bit, RFC 8439 §2.5). key = r||s. */
void wo_poly1305(const uint8_t key[32], const uint8_t *m, size_t bytes,
uint8_t mac[16]) {
uint32_t t0 = rd32le(key), t1 = rd32le(key + 4),
t2 = rd32le(key + 8), t3 = rd32le(key + 12);
uint32_t r0 = t0 & 0x3ffffffu;
uint32_t r1 = ((t0 >> 26) | (t1 << 6)) & 0x3ffff03u;
uint32_t r2 = ((t1 >> 20) | (t2 << 12)) & 0x3ffc0ffu;
uint32_t r3 = ((t2 >> 14) | (t3 << 18)) & 0x3f03fffu;
uint32_t r4 = (t3 >> 8) & 0x00fffffu;
uint32_t s1 = r1 * 5, s2 = r2 * 5, s3 = r3 * 5, s4 = r4 * 5;
uint32_t h0 = 0, h1 = 0, h2 = 0, h3 = 0, h4 = 0, c;
while (bytes > 0) {
uint8_t block[16];
size_t n = bytes < 16 ? bytes : 16;
uint32_t hibit;
if (n < 16) {
memset(block, 0, 16);
memcpy(block, m, n);
block[n] = 1;
hibit = 0;
} else {
memcpy(block, m, 16);
hibit = 1u << 24;
}
t0 = rd32le(block); t1 = rd32le(block + 4);
t2 = rd32le(block + 8); t3 = rd32le(block + 12);
h0 += t0 & 0x3ffffffu;
h1 += ((t0 >> 26) | (t1 << 6)) & 0x3ffffffu;
h2 += ((t1 >> 20) | (t2 << 12)) & 0x3ffffffu;
h3 += ((t2 >> 14) | (t3 << 18)) & 0x3ffffffu;
h4 += (t3 >> 8) | hibit;
uint64_t d0 = (uint64_t)h0 * r0 + (uint64_t)h1 * s4 + (uint64_t)h2 * s3 +
(uint64_t)h3 * s2 + (uint64_t)h4 * s1;
uint64_t d1 = (uint64_t)h0 * r1 + (uint64_t)h1 * r0 + (uint64_t)h2 * s4 +
(uint64_t)h3 * s3 + (uint64_t)h4 * s2;
uint64_t d2 = (uint64_t)h0 * r2 + (uint64_t)h1 * r1 + (uint64_t)h2 * r0 +
(uint64_t)h3 * s4 + (uint64_t)h4 * s3;
uint64_t d3 = (uint64_t)h0 * r3 + (uint64_t)h1 * r2 + (uint64_t)h2 * r1 +
(uint64_t)h3 * r0 + (uint64_t)h4 * s4;
uint64_t d4 = (uint64_t)h0 * r4 + (uint64_t)h1 * r3 + (uint64_t)h2 * r2 +
(uint64_t)h3 * r1 + (uint64_t)h4 * r0;
c = (uint32_t)(d0 >> 26); h0 = (uint32_t)d0 & 0x3ffffffu;
d1 += c; c = (uint32_t)(d1 >> 26); h1 = (uint32_t)d1 & 0x3ffffffu;
d2 += c; c = (uint32_t)(d2 >> 26); h2 = (uint32_t)d2 & 0x3ffffffu;
d3 += c; c = (uint32_t)(d3 >> 26); h3 = (uint32_t)d3 & 0x3ffffffu;
d4 += c; c = (uint32_t)(d4 >> 26); h4 = (uint32_t)d4 & 0x3ffffffu;
h0 += c * 5; c = h0 >> 26; h0 &= 0x3ffffffu; h1 += c;
m += n; bytes -= n;
}
c = h1 >> 26; h1 &= 0x3ffffffu; h2 += c;
c = h2 >> 26; h2 &= 0x3ffffffu; h3 += c;
c = h3 >> 26; h3 &= 0x3ffffffu; h4 += c;
c = h4 >> 26; h4 &= 0x3ffffffu; h0 += c * 5;
c = h0 >> 26; h0 &= 0x3ffffffu; h1 += c;
uint32_t g0 = h0 + 5; c = g0 >> 26; g0 &= 0x3ffffffu;
uint32_t g1 = h1 + c; c = g1 >> 26; g1 &= 0x3ffffffu;
uint32_t g2 = h2 + c; c = g2 >> 26; g2 &= 0x3ffffffu;
uint32_t g3 = h3 + c; c = g3 >> 26; g3 &= 0x3ffffffu;
uint32_t g4 = h4 + c - (1u << 26);
uint32_t mask = (g4 >> 31) - 1;
g0 &= mask; g1 &= mask; g2 &= mask; g3 &= mask; g4 &= mask;
mask = ~mask;
h0 = (h0 & mask) | g0; h1 = (h1 & mask) | g1; h2 = (h2 & mask) | g2;
h3 = (h3 & mask) | g3; h4 = (h4 & mask) | g4;
h0 = (h0 | (h1 << 26));
h1 = ((h1 >> 6) | (h2 << 20));
h2 = ((h2 >> 12) | (h3 << 14));
h3 = ((h3 >> 18) | (h4 << 8));
uint64_t f = (uint64_t)h0 + rd32le(key + 16); h0 = (uint32_t)f;
f = (uint64_t)h1 + rd32le(key + 20) + (f >> 32); h1 = (uint32_t)f;
f = (uint64_t)h2 + rd32le(key + 24) + (f >> 32); h2 = (uint32_t)f;
f = (uint64_t)h3 + rd32le(key + 28) + (f >> 32); h3 = (uint32_t)f;
wr32le(mac, h0); wr32le(mac + 4, h1); wr32le(mac + 8, h2); wr32le(mac + 12, h3);
}
static int ct_memeq(const uint8_t *a, const uint8_t *b, size_t n) {
uint8_t d = 0;
for (size_t i = 0; i < n; i++) d |= (uint8_t)(a[i] ^ b[i]);
return d == 0;
}
/* The AEAD MAC: Poly1305 over aad || pad16 || ct || pad16 || le64(aadlen) ||
* le64(ctlen). Returns 0, or -1 on OOM building the (16-aligned) buffer. */
static int aead_tag(const uint8_t polykey[32], const uint8_t *aad, size_t aadlen,
const uint8_t *ct, size_t ctlen, uint8_t tag[16]) {
size_t apad = (aadlen + 15u) & ~(size_t)15u;
size_t cpad = (ctlen + 15u) & ~(size_t)15u;
size_t mlen = apad + cpad + 16u;
uint8_t *mb = (uint8_t *)calloc(1, mlen);
if (!mb) return -1;
if (aadlen) memcpy(mb, aad, aadlen);
if (ctlen) memcpy(mb + apad, ct, ctlen);
wr64le(mb + apad + cpad, (uint64_t)aadlen);
wr64le(mb + apad + cpad + 8, (uint64_t)ctlen);
wo_poly1305(polykey, mb, mlen, tag);
free(mb);
return 0;
}
/* RFC 8439 §2.8 seal: out = ciphertext || 16-byte tag (out must hold
* ptlen+16). Returns 0, or -1 on OOM. */
int wo_chacha20poly1305_seal(const uint8_t key[32], const uint8_t nonce[12],
const uint8_t *aad, size_t aadlen,
const uint8_t *pt, size_t ptlen, uint8_t *out) {
uint8_t polyblock[64];
chacha20_block(key, 0, nonce, polyblock); /* Poly1305 key = counter-0 block */
chacha20_xor(key, nonce, 1, pt, ptlen, out);
return aead_tag(polyblock, aad, aadlen, out, ptlen, out + ptlen);
}
/* Open: verify the tag over `ct` (ctlen, the ciphertext WITHOUT the tag) and
* `tag`, then decrypt into `out` (ctlen bytes). 0 = ok, 1 = auth failure,
* -1 = OOM. Constant-time tag compare; on failure `out` is not written. */
int wo_chacha20poly1305_open(const uint8_t key[32], const uint8_t nonce[12],
const uint8_t *aad, size_t aadlen,
const uint8_t *ct, size_t ctlen,
const uint8_t tag[16], uint8_t *out) {
uint8_t polyblock[64], want[16];
chacha20_block(key, 0, nonce, polyblock);
if (aead_tag(polyblock, aad, aadlen, ct, ctlen, want) != 0) return -1;
if (!ct_memeq(want, tag, 16)) return 1;
chacha20_xor(key, nonce, 1, ct, ctlen, out);
return 0;
}
/* 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) {
@ -238,6 +433,59 @@ int wo_builtin_crypto(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg) {
dlen = 32;
break;
}
case WO_B_CHACHA20POLY1305_SEAL: {
const wo_str *k = arg_bytes(R[B], msg);
const wo_str *n = k ? arg_bytes(R[B + 1], msg) : NULL;
const wo_str *a = n ? arg_bytes(R[B + 2], msg) : NULL;
const wo_str *p = a ? arg_bytes(R[B + 3], msg) : NULL;
if (!p) return WO_T_BOUNDS;
if (k->len != 32 || n->len != 12) {
*msg = "chacha20poly1305: key must be 32 bytes, nonce 12";
return WO_T_BOUNDS;
}
uint8_t *buf = (uint8_t *)malloc(p->len + 16u);
if (!buf) { *msg = "out of memory"; return WO_T_OOM; }
if (wo_chacha20poly1305_seal((const uint8_t *)k->data,
(const uint8_t *)n->data,
(const uint8_t *)a->data, a->len,
(const uint8_t *)p->data, p->len, buf) != 0) {
free(buf);
*msg = "out of memory";
return WO_T_OOM;
}
wo_str *o = wo_bytes_new(rt, (const char *)buf, (uint32_t)(p->len + 16u));
free(buf);
if (!o) { *msg = "out of memory"; return WO_T_OOM; }
R[A] = (uint64_t)(uintptr_t)o;
return 0;
}
case WO_B_CHACHA20POLY1305_OPEN: {
const wo_str *k = arg_bytes(R[B], msg);
const wo_str *n = k ? arg_bytes(R[B + 1], msg) : NULL;
const wo_str *a = n ? arg_bytes(R[B + 2], msg) : NULL;
const wo_str *ctag = a ? arg_bytes(R[B + 3], msg) : NULL;
if (!ctag) return WO_T_BOUNDS;
if (k->len != 32 || n->len != 12) {
*msg = "chacha20poly1305: key must be 32 bytes, nonce 12";
return WO_T_BOUNDS;
}
if (ctag->len < 16) { R[A] = 0; return 0; } /* no room for a tag: reject */
uint32_t bodylen = ctag->len - 16u;
uint8_t *buf = (uint8_t *)malloc(bodylen ? bodylen : 1u);
if (!buf) { *msg = "out of memory"; return WO_T_OOM; }
int rc = wo_chacha20poly1305_open(
(const uint8_t *)k->data, (const uint8_t *)n->data,
(const uint8_t *)a->data, a->len,
(const uint8_t *)ctag->data, bodylen,
(const uint8_t *)ctag->data + bodylen, buf);
if (rc == -1) { free(buf); *msg = "out of memory"; return WO_T_OOM; }
if (rc != 0) { free(buf); R[A] = 0; return 0; } /* auth failure -> nil */
wo_str *o = wo_bytes_new(rt, (const char *)buf, bodylen);
free(buf);
if (!o) { *msg = "out of memory"; return WO_T_OOM; }
R[A] = (uint64_t)(uintptr_t)o;
return 0;
}
default:
*msg = "unknown crypto builtin";
return WO_T_BOUNDS;

View file

@ -14,6 +14,18 @@ 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]);
/* ChaCha20-Poly1305 AEAD (rv2 8 phase A, RFC 8439). Raw cores exposed for
* the unit test; the VM enters through wo_builtin_crypto. */
void wo_poly1305(const uint8_t key[32], const uint8_t *m, size_t bytes,
uint8_t mac[16]);
int wo_chacha20poly1305_seal(const uint8_t key[32], const uint8_t nonce[12],
const uint8_t *aad, size_t aadlen,
const uint8_t *pt, size_t ptlen, uint8_t *out);
int wo_chacha20poly1305_open(const uint8_t key[32], const uint8_t nonce[12],
const uint8_t *aad, size_t aadlen,
const uint8_t *ct, size_t ctlen,
const uint8_t tag[16], uint8_t *out);
int wo_builtin_crypto(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg);
#endif

View file

@ -79,6 +79,7 @@ static const uint8_t b_arity[WO_B_MAX + 1] = {
[WO_B_SIGNAL_ON] = 3, [WO_B_TERM_RAW] = 1, [WO_B_TERM_RESTORE] = 1,
[WO_B_NET_SEND_FD] = 2, [WO_B_NET_RECV_FD] = 1, [WO_B_NET_CONNECT_UNIX] = 1,
[WO_B_TERM_SIZE] = 2, [WO_B_TERM_WIDTH] = 1, [WO_B_NET_CONNECT] = 2,
[WO_B_CHACHA20POLY1305_SEAL] = 4, [WO_B_CHACHA20POLY1305_OPEN] = 4,
/* json (json.c): encode takes the value's static kind, decode the class
id to build */
[WO_B_JSON_ENCODE] = 2, [WO_B_JSON_DECODE] = 2, [WO_B_MAP_GET_OPT] = 2,

View file

@ -552,9 +552,15 @@ enum {
* for the plane, mirroring WO_B_NET_CONNECT_UNIX. A _dl deadline/park
* variant is the next slice. Underneath runtime-v2 9's outbound TLS. */
WO_B_NET_CONNECT = 110, /* (host, port) -> Int: outbound TCP client fd */
/* ---- runtime-v2 8 phase A: ChaCha20-Poly1305 AEAD (RFC 8439). Bare-name
* crypto-family builtins beside sha256/hmac; key 32B, nonce 12B, caller-
* supplied. seal -> ciphertext||tag (Bytes); open -> ?Bytes (nil on auth
* failure or a too-short input). */
WO_B_CHACHA20POLY1305_SEAL = 111, /* (key, nonce, aad, plaintext) -> Bytes */
WO_B_CHACHA20POLY1305_OPEN = 112, /* (key, nonce, aad, ct||tag) -> ?Bytes */
};
#define WO_B_MAX 110u
#define WO_B_MAX 112u
/* ids at or above this one live in sysio.c, not builtin.c */
#define WO_B_SYS_FIRST WO_B_FS_EXISTS

View file

@ -42,6 +42,16 @@ static void t_hmac(const uint8_t *key, size_t klen, const char *msg,
T_CHECK(strcmp(got, want) == 0);
}
/* rv2 8 phase A: ChaCha20-Poly1305 (RFC 8439 §2.5.2 Poly1305 + §2.8.2 AEAD) */
static void t_poly1305(const uint8_t key[32], const char *msg, size_t mlen,
const char *want) {
uint8_t tag[16];
char got[33];
wo_poly1305(key, (const uint8_t *)msg, mlen, tag);
hex(tag, 16, got);
T_CHECK(strcmp(got, want) == 0);
}
int main(void) {
/* RFC 3174 */
t_sha1("abc", 3, "a9993e364706816aba3e25717850c26c9cd0d89d");
@ -108,5 +118,61 @@ int main(void) {
"60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54");
}
/* RFC 8439 §2.5.2 — Poly1305 */
{
uint8_t pk[32];
for (int i = 0; i < 32; i++) pk[i] = 0;
static const uint8_t pkv[32] = {
0x85, 0xd6, 0xbe, 0x78, 0x57, 0x55, 0x6d, 0x33, 0x7f, 0x44, 0x52,
0xfe, 0x42, 0xd5, 0x06, 0xa8, 0x01, 0x03, 0x80, 0x8a, 0xfb, 0x0d,
0xb2, 0xfd, 0x4a, 0xbf, 0xf6, 0xaf, 0x41, 0x49, 0xf5, 0x1b };
memcpy(pk, pkv, 32);
t_poly1305(pk, "Cryptographic Forum Research Group", 34,
"a8061dc1305136c6c22b8baf0c0127a9");
}
/* RFC 8439 §2.8.2 — ChaCha20-Poly1305 AEAD: seal matches the vector,
* open round-trips, and a tampered tag is rejected. */
{
uint8_t key[32], nonce[12], aad[12];
for (int i = 0; i < 32; i++) key[i] = (uint8_t)(0x80 + i);
static const uint8_t nv[12] = { 0x07, 0x00, 0x00, 0x00, 0x40, 0x41,
0x42, 0x43, 0x44, 0x45, 0x46, 0x47 };
static const uint8_t av[12] = { 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1,
0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 };
memcpy(nonce, nv, 12);
memcpy(aad, av, 12);
const char *pt =
"Ladies and Gentlemen of the class of '99: If I could offer you "
"only one tip for the future, sunscreen would be it.";
size_t ptlen = strlen(pt);
uint8_t out[114 + 16];
int rc = wo_chacha20poly1305_seal(key, nonce, aad, 12,
(const uint8_t *)pt, ptlen, out);
T_CHECK(rc == 0);
char got[(114 + 16) * 2 + 1];
hex(out, ptlen + 16, got);
T_CHECK(strcmp(got,
"d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6"
"3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36"
"92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc"
"3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060"
"0691") == 0);
uint8_t back[114];
rc = wo_chacha20poly1305_open(key, nonce, aad, 12, out, ptlen,
out + ptlen, back);
T_CHECK(rc == 0);
T_CHECK(memcmp(back, pt, ptlen) == 0);
/* flip one tag bit — must be rejected (rc == 1), not decrypted */
uint8_t tampered[16];
memcpy(tampered, out + ptlen, 16);
tampered[0] ^= 0x01;
rc = wo_chacha20poly1305_open(key, nonce, aad, 12, out, ptlen,
tampered, back);
T_CHECK(rc == 1);
}
return t_report("test_crypto");
}