diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3742550..6ad70d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,27 @@
All notable changes to Rail are documented here.
+## Unreleased
+
+### Tests
+- **Ed25519 pinned to RFC 8032 published vectors** (#48): sign + verify
+ + tamper-rejection known-answer tests; suite 178 → 182.
+- **TLS stack pinned to published vectors** (#49): RFC 8448 §3 full
+ offline handshake (key schedule + transcript + server/client Finished),
+ ECDSA-P256 verify per RFC 6979 §A.2.5 plus a 4-way invalid-signature
+ rejection test, the CertificateVerify path (ASN.1 parse → SPKI extract
+ → DER sig parse → ECDSA), and SAN hostname validation against a real
+ captured leaf (accept exact + case-fold, reject other host and parent
+ domain); suite 182 → 187. The handshake, cert-verify, and hostname
+ paths — the code that decides who to trust — were previously covered
+ only by manual tests in `tools/tls/`.
+- **Suite harness: per-test `arena_mark`/`arena_reset`** (#49). Adding
+ the TLS tests exposed that `run_test` kept every compile's garbage
+ live across the whole run: by test ~183 the default 1GB arena was
+ full and the GC crawled — a test that compiles in seconds standalone
+ spun for 20+ minutes in-suite. Each test now compiles inside a
+ mark/reset window, so suite memory stays flat at any test count.
+
## v5.3.0 — 2026-07-08 — Independence + 3× faster self-compile
Consolidation release: 19 PRs since v5.2.0. Two headlines — a
diff --git a/README.md b/README.md
index a4655d0..dacce56 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
-
+
@@ -34,7 +34,7 @@ Rail compiles itself. The compiler — ~9,200 lines of Rail — produces a ~0.9
```
./rail_native self && cp /tmp/rail_self ./rail_native # cycle 1
./rail_native self && cmp rail_native /tmp/rail_self # cycle 2 — byte-identical
-./rail_native test # 182/182
+./rail_native test # 187/187
```
## Quick start
@@ -50,7 +50,7 @@ Apple Silicon (ARM64 macOS) is the primary target; Linux ARM64, Linux x86_64, We
```bash
./rail_native # compile to /tmp/rail_out
./rail_native run # compile + execute
-./rail_native test # run the 182-test suite
+./rail_native test # run the 187-test suite
./rail_native self # self-compile, fixed point at gen2
./rail_native x86 # cross-compile to Linux x86_64
./rail_native linux # cross-compile to Linux ARM64
diff --git a/docs/STATUS.md b/docs/STATUS.md
index c1bc915..0f7fbf8 100644
--- a/docs/STATUS.md
+++ b/docs/STATUS.md
@@ -1,6 +1,6 @@
# Rail - verified status (v5.3.0)
-> Generated by `tools/deploy/gen_status.rail` on 2026-07-08 19:37 UTC.
+> Generated by `tools/deploy/gen_status.rail` on 2026-07-09 01:06 UTC.
> Every number is recomputed from the source tree on each run, so this file
> cannot drift. Where a claim needs proof, the **command** is given - run it
> yourself. These are reproducible provenance facts, not a proof of correctness.
@@ -8,9 +8,9 @@
| Fact | Value | Verify it yourself |
|---|---|---|
| Release | v5.3.0 | `git describe --tags` |
-| Compiler | 9232 lines of Rail | `wc -l tools/compile.rail` |
+| Compiler | 9245 lines of Rail | `wc -l tools/compile.rail` |
| Seed binary | 908360 bytes | `wc -c rail_native` |
-| Seed sha256 | `44e28d4780658866d82201aa6cbc6efcc94704e191ac70a9587e19f240eaf35c` | `shasum -a 256 rail_native` |
+| Seed sha256 | `ba9344da55733580a04c9593e9062fded821c86829fa326219de5a39297889f2` | `shasum -a 256 rail_native` |
| Stdlib | 102 modules | `ls stdlib/*.rail \| wc -l` |
| Non-Rail surface | 31 in-tree files | `SHIMS.md` |
| Tests | runner self-reports N/N | `./rail_native test` |
diff --git a/rail_native b/rail_native
index 5132414..0401d43 100755
Binary files a/rail_native and b/rail_native differ
diff --git a/rail_safe b/rail_safe
index 5132414..0401d43 100755
Binary files a/rail_safe and b/rail_safe differ
diff --git a/rail_safe.sha256 b/rail_safe.sha256
index 0deb46d..ea22c42 100644
--- a/rail_safe.sha256
+++ b/rail_safe.sha256
@@ -1 +1 @@
-44e28d4780658866d82201aa6cbc6efcc94704e191ac70a9587e19f240eaf35c
+ba9344da55733580a04c9593e9062fded821c86829fa326219de5a39297889f2
diff --git a/tools/compile.rail b/tools/compile.rail
index 02537da..42c1a08 100644
--- a/tools/compile.rail
+++ b/tools/compile.rail
@@ -5852,6 +5852,12 @@ strip_trailing_ws s = strip_trailing_ws_loop s (length s)
run_test name src expected =
let _ = print (cat [" --- ", name, " ---"])
+ -- Mark/reset per test: each compile_program allocates ~MBs that nothing
+ -- references after the verdict. Without the reset the arena fills over
+ -- ~180 in-process compiles and the GC crawls (a late test that compiles
+ -- in seconds standalone spins for 20+ minutes in-suite at the default
+ -- 1GB arena). result is an unboxed bool, so nothing escapes the window.
+ let mk = arena_mark 0
let asm = compile_program src
let path = trim (shell "mktemp -u /tmp/rail_out_XXXXXX")
let _ = build path asm
@@ -5860,6 +5866,7 @@ run_test name src expected =
-- Get the last line (exit code) and everything before it (stdout)
let result = get_test_result lines expected
let _ = shell (cat ["rm -f ", path, " ", path, ".s ", path, ".o"])
+ let _ = arena_reset mk
result
get_test_result lines expected =
@@ -6332,9 +6339,15 @@ run_tests _ =
let t176 = run_test "ed25519_reject_tampered" "import \"stdlib/sha256.rail\"\nimport \"stdlib/sha512.rail\"\nimport \"stdlib/x25519.rail\"\nimport \"stdlib/ed25519.rail\"\nmain =\n let pub = hex_to_bytes \"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a\"\n let msg = arr_new 0 0\n let sig_r = hex_to_bytes \"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b\"\n let _ = arr_set sig_r 0 (bit_and (arr_get sig_r 0 + 1) 255)\n let sig_s = hex_to_bytes \"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b\"\n let _ = arr_set sig_s 32 (bit_and (arr_get sig_s 32 + 1) 255)\n let _ = print (cat [show (ed25519_verify pub msg 0 sig_r), \" \", show (ed25519_verify pub msg 0 sig_s)])\n 0" "0 0"
let t177 = run_test "ed25519_sign_rfc8032_t3" "import \"stdlib/sha256.rail\"\nimport \"stdlib/sha512.rail\"\nimport \"stdlib/x25519.rail\"\nimport \"stdlib/ed25519.rail\"\nimport \"stdlib/ed25519_scalar.rail\"\nimport \"stdlib/ed25519_sign.rail\"\nmain =\n let sk = hex_to_bytes \"c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7\"\n let msg = hex_to_bytes \"af82\"\n let pk = ed25519_pk_from_sk sk\n let sig = ed25519_sign sk msg 2\n let _ = print (cat [bytes_to_hex pk 32, \":\", bytes_to_hex sig 64])\n 0" "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025:6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a"
let s_ed = (if t174 then 1 else 0) + (if t175 then 1 else 0) + (if t176 then 1 else 0) + (if t177 then 1 else 0)
- let total = s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10 + s11 + s12 + s_inf + s_bx + s_ed
- let _ = print (cat [show total, "/182 tests passed"])
- if total == 182 then 0 else 1
+ let t178 = run_test "tls13_rfc8448_handshake" "import \"stdlib/tls13_client.rail\"\nmain =\n let ch = hex_to_bytes \"010000c00303cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece7000006130113031302010000910000000b0009000006736572766572ff01000100000a00140012001d0017001800190100010101020103010400230000003300260024001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001\"\n let sh = hex_to_bytes \"020000560303a6af06a4121860dc5e6e60249cd34c95930c8ac5cb1434dac155772ed3e2692800130100002e00330024001d0020c9828876112095fe66762bdbf7c672e156d6cc253b833df1dd69b1b04e751f0f002b00020304\"\n let sf = hex_to_bytes \"080000240022000a00140012001d00170018001901000101010201030104001c00024001000000000b0001b9000001b50001b0308201ac30820115a003020102020102300d06092a864886f70d01010b0500300e310c300a06035504031303727361301e170d3136303733303031323335395a170d3236303733303031323335395a300e310c300a0603550403130372736130819f300d06092a864886f70d010101050003818d0030818902818100b4bb498f8279303d980836399b36c6988c0c68de55e1bdb826d3901a2461eafd2de49a91d015abbc9a95137ace6c1af19eaa6af98c7ced43120998e187a80ee0ccb0524b1b018c3e0b63264d449a6d38e22a5fda430846748030530ef0461c8ca9d9efbfae8ea6d1d03e2bd193eff0ab9a8002c47428a6d35a8d88d79f7f1e3f0203010001a31a301830090603551d1304023000300b0603551d0f0404030205a0300d06092a864886f70d01010b05000381810085aad2a0e5b9276b908c65f73a7267170618a54c5f8a7b337d2df7a594365417f2eae8f8a58c8f8172f9319cf36b7fd6c55b80f21a03015156726096fd335e5e67f2dbf102702e608ccae6bec1fc63a42a99be5c3eb7107c3c54e9b9eb2bd5203b1c3b84e0a8b2f759409ba3eac9d91d402dcc0cc8f8961229ac9187b42b4de100000f000084080400805a747c5d88fa9bd2e55ab085a61015b7211f824cd484145ab3ff52f1fda8477b0b7abc90db78e2d33a5c141a078653fa6bef780c5ea248eeaaa785c4f394cab6d30bbe8d4859ee511f602957b15411ac027671459e46445c9ea58c181e818e95b8c3fb0bf3278409d3be152a3da5043e063dda65cdf5aea20d53dfacd42f74f3140000209b9b141d906337fbd2cbdce71df4deda4ab42c309572cb7fffee5454b78f0718\"\n let cp = hex_to_bytes \"49af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005\"\n let r = tls13_client_handshake_offline cp ch 196 sh 90 sf 657 0 \"\"\n let _ = print (cat [show (arr_get r 4), \":\", bytes_to_hex (arr_get r 0) 32, \":\", bytes_to_hex (arr_get r 1) 32, \":\", bytes_to_hex (arr_get r 2) (arr_get r 3)])\n 0" "1:9e40646ce79a7f9dc05af8889bce6552875afa0b06df0087f792ebb7c17504a5:a11af9f05531f856ad47116b45a950328204b4f44bfb6b3a4b4f1f3fcb631643:14000020a8ec436d677634ae525ac1fcebe11a039ec17694fac6e98527b642f2edd5ce61"
+ let t179 = run_test "ecdsa_p256_rfc6979_verify" "import \"stdlib/sha256.rail\"\nimport \"stdlib/ecdsa_p256.rail\"\nmain =\n let qx = hex_to_bytes \"60fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb6\"\n let qy = hex_to_bytes \"7903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299\"\n let h = hex_to_bytes \"af2bdbe1aa9b6ec1e2ade1d694f41fc71a831d0268e9891562113d8a62add1bf\"\n let r = hex_to_bytes \"efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716\"\n let s = hex_to_bytes \"f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8\"\n let _ = print (show (ecdsa_p256_verify qx qy h r s))\n 0" "1"
+ let t180 = run_test "ecdsa_p256_reject_invalid" "import \"stdlib/sha256.rail\"\nimport \"stdlib/ecdsa_p256.rail\"\nmain =\n let qx = hex_to_bytes \"60fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb6\"\n let qy = hex_to_bytes \"7903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299\"\n let h = hex_to_bytes \"af2bdbe1aa9b6ec1e2ade1d694f41fc71a831d0268e9891562113d8a62add1bf\"\n let r_bad = hex_to_bytes \"efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3717\"\n let s = hex_to_bytes \"f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8\"\n let h_bad = hex_to_bytes \"af2bdbe1aa9b6ec1e2ade1d694f41fc71a831d0268e9891562113d8a62add1be\"\n let r_good = hex_to_bytes \"efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716\"\n let z = arr_new 32 0\n let e1 = ecdsa_p256_verify qx qy h r_bad s\n let e2 = ecdsa_p256_verify qx qy h_bad r_good s\n let e3 = ecdsa_p256_verify qx qy h z s\n let e4 = ecdsa_p256_verify qx qy h r_good z\n let _ = print (cat [show e1, \" \", show e2, \" \", show e3, \" \", show e4])\n 0" "0 0 0 0"
+ let t181 = run_test "tls13_cert_verify_sig" "import \"stdlib/tls13_client.rail\"\nmain =\n let cert = hex_to_bytes \"3082011b3081c1a003020102020101300a06082a8648ce3d04030230173115301306035504030c0c746573742e6578616d706c65301e170d3236303431373031353031395a170d3237303431383031353031395a30173115301306035504030c0c746573742e6578616d706c653059301306072a8648ce3d020106082a8648ce3d03010703420004a2a0b43696b4451b5bdc9a3ae9ba68000f66f317586f19342da4987ca638f19e5b786809e58a7f6bd33c09da3c39e9e176c85286962434b79a8611646e8cd023300a06082a8648ce3d0403020349003046022100f0af1d0eaa7f47a57e80609f979014487bf39b2d06f770a7e9c30704b14f60e7022100b5fa084c52d995ac2f755f216a9bd98e85ff95840ad72f2a7e61478e8760048b\"\n let sig = hex_to_bytes \"304502203a241f6c75320a14bca59d59696f1b0ec80d0f40557a96a28b95aa863f1ccda00221008508b92136ac6b53b8147b4f21a5150318f9fd61a65edf658d780cee72ddd051\"\n let ts = hex_to_bytes \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n let _ = print (show (tls13_cert_verify_sig cert 287 1027 sig 71 ts))\n 0" "1"
+ let t182 = run_test "cert_hostname_san" "import \"stdlib/tls13_client.rail\"\nmain =\n let cert = hex_to_bytes \"3082039f30820346a00302010202105a2eb9c74ccea8470e184bd70d6c085f300a06082a8648ce3d040302303b310b3009060355040613025553311e301c060355040a1315476f6f676c65205472757374205365727669636573310c300a06035504031303574531301e170d3236303332383137313733335a170d3236303632363138313733305a301c311a3018060355040313116170692e616e7468726f7069632e636f6d3059301306072a8648ce3d020106082a8648ce3d03010703420004f15b46f29627170a03dfe671cacca9d84c200924577cbe89e362f0a1dd516d2a93a0b495332444fe5b9ed75cd62dfaa9ce3d95d4913213a9893369a4df41be0fa382024930820245300e0603551d0f0101ff04040302078030130603551d25040c300a06082b06010505070301300c0603551d130101ff04023000301d0603551d0e04160414ad1df0b855983a5719e9d6bde97c72425dec55c3301f0603551d230418301680149077923567c4ffa8cca9e67bd980797bcc93f938305e06082b0601050507010104523050302706082b06010505073001861b687474703a2f2f6f2e706b692e676f6f672f732f7765312f576934302506082b060105050730028619687474703a2f2f692e706b692e676f6f672f7765312e637274301c0603551d110415301382116170692e616e7468726f7069632e636f6d30130603551d20040c300a3008060667810c01020130360603551d1f042f302d302ba029a0278625687474703a2f2f632e706b692e676f6f672f7765312f33474c426c74304c3436512e63726c30820103060a2b06010401d6790204020481f40481f100ef007500499c9b69de1d7cecfc36decd8764a6b85baf0a878019d15552fbe9eb29ddf8c30000019d35aa6ec4000004030046304402203182860d5c3baf671e5870f04e4b5a5c8a784950f7cd8c9e9e82866e19383ca502202a303ec43bac6b52a92bc79655f1c40be9d73f163518026a6592c090076af6f50076000e5794bcf3aea93e331b2c9907b3f790df9bc23d713225dd21a925ac61c54e210000019d35aa6ace0000040300473045022045ed6976393b807764edd9b0f55e94258a7ece08b8083d811122bd1ae2974c52022100d6fb2755bbda522e57b12faa64cabe64589c57f4df90547a125fa18cfb43666f300a06082a8648ce3d040302034700304402205cd2321541e590fc30d18eab25b4784355866f5bebed4ad30b5b66699914dbdd022078ebc05c00314a35b145dbe7dc783db9094204b089cc03c876b6ade7c9b9e68b\"\n let r1 = cv_validate_hostname cert 931 \"api.anthropic.com\"\n let r2 = cv_validate_hostname cert 931 \"evil.example.com\"\n let r3 = cv_validate_hostname cert 931 \"API.ANTHROPIC.COM\"\n let r4 = cv_validate_hostname cert 931 \"anthropic.com\"\n let _ = print (cat [show r1, \" \", show r2, \" \", show r3, \" \", show r4])\n 0" "1 0 1 0"
+ let s_tls = (if t178 then 1 else 0) + (if t179 then 1 else 0) + (if t180 then 1 else 0) + (if t181 then 1 else 0) + (if t182 then 1 else 0)
+ let total = s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10 + s11 + s12 + s_inf + s_bx + s_ed + s_tls
+ let _ = print (cat [show total, "/187 tests passed"])
+ if total == 187 then 0 else 1
-- ===========================================================================
-- M6 — self-hosted emit path. Replaces as/ld/codesign with the pure-Rail