Skip to content

ToyOS's own SHA-256 and SHA-512, as fast as sha2's soft backend, replace the sha2 crate in the loader's and update's image verification, swap, pkg and the build - #812

Open
Japabu wants to merge 6 commits into
mainfrom
wt/toyos-sha2
Open

Japabu wants to merge 6 commits into
mainfrom
wt/toyos-sha2

Conversation

@Japabu

@Japabu Japabu commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

The owner's ruling on the dependency audit, "Own SHA-2": every SHA-256 and SHA-512 that ToyOS's own code takes is now toyos-sha2, written here from FIPS 180-4. Head b9132ebb7.

What changed, per decision

  • toyos-sha2 is a new member: no_std, forbid(unsafe_code), 255 lines. It implements SHA-256 and SHA-512 per FIPS 180-4 §4.2, §5.1, §5.3, §6.2 and §6.4.
    • SHA-512 is in because toyos-update::sig builds the SSHSIG message over SHA-512(header).
    • It hashes whole bytes only, because no caller has a partial byte.
    • One macro does the buffering and padding for both hashes, and one does the compression, over each hash's word, block, constants and rotations.
  • The compression is as fast as sha2's soft backend. Round 1 was 1.2x slower, and the T14 showed it: the loader's ROOT hash took 2.653 s against main's 2.209 s. Three changes close the gap; they are measured under Speed.
    • The schedule is a sixteen-word ring. Each pass overwrites W(t-16) with W(t), instead of filling a 64- or 80-word array.
    • Rounds and schedule words are written out sixteen to a pass through const generics. Every index into the working variables and the schedule is then a constant; LLVM had kept round 1's rounds as a loop over memory.
    • One call compresses every whole block of an update, with the hash value held in locals between blocks.
    • Maj is written as b ^ ((a ^ b) & (b ^ c)), whose b ^ c is the previous round's a ^ b.
  • Scalar on every target, with no SHA-NI. An instruction path needs unsafe and core::arch, and this crate forbids unsafe. The loader is also a soft-float UEFI application that may not assume the SIMD/SHA registers are its own, which is why it already forced sha2 soft. The x86-64 callers that used SHA-NI/AVX2 are bounded under Speed and recorded in issues/x86-64-hashing-runs-scalar-where-the-cpu-has-sha-instructions.md (status: owner).
  • API: a drop-in for the Digest calls the tree makes, as inherent methods: new(), update(impl AsRef<[u8]>), finalize() -> [u8; N], digest(impl AsRef<[u8]>) -> [u8; N] and Default. Each caller drops its use sha2::Digest and its .into().
  • Callers moved:
    • toyos-update: sha256(), which covers the loader's and update's section hashes, floor, and signing.rs through it. Also sig's SHA-512.
    • toyos-swap.
    • userland/update's streamed ROOT hash.
    • userland/pkg.
    • The build: src/cicache.rs, sourcegate.rs, sysroot.rs, image.rs and release.rs. cicache's {:x} of a GenericArray became release::sha256_hex.
  • ed25519-dalek keeps sha2.
    • ed25519-dalek 2.2.0 depends on sha2 0.10.9 without making it optional, and its src/verifying.rs takes sha2::Sha512 for H(R‖A‖M) inside verify_strict. So sha2 0.10.9 stays in Cargo.lock.
    • The loader keeps its sha2 … features = ["force-soft"] line, with a comment saying it exists only for that.
    • sha2 0.11.0-rc.5 belongs to russh and is untouched.
    • No ToyOS source names sha2 any more, except toyos-sha2's dev-dependency.
  • NIST's byte-oriented CAVP vectors live in tests/cavp/, outside every shipped package's directory. They are ShortMsg, LongMsg and Monte for both hashes, byte for byte as upstream, CRLF included.
    • NOTICE's section tests/cavp/ gives each file's hash and size, the upstream archive's hash, and NIST-PD.
    • The licence gate reports the section as "is not shipped", so src/licence.rs is identical to main (git diff origin/main -- src/licence.rs is empty). This follows the orchestrator's ruling that this branch does not widen the licence list.
    • The bit-oriented files are gone, with padded, bit_file and their tests. They drove the compression function under the test's own padding, which the byte files already drive. The bit Monte files differ from the byte ones only by their seed.
  • issues/the-build-runs-host-tools-outside-rust-and-qemu.md's shasum row now names toyos-sha2 as what src/release.rs hashes with.

Checks of high-risk code (the signed-image trust boundary)

  • Independent oracle 1, NIST CAVP SHAVS (toyos-sha2/src/tests.rs).
    • Every ShortMsg and LongMsg vector is checked whole, and again streamed one byte at a time.
    • Every Monte Carlo checkpoint (SHAVS §6.4) is checked.
    • Each section's [L = n] header is held against its digests' length.
  • Independent oracle 2, a differential against RustCrypto sha2 0.10.9. It is a dev-dependency only and shares no code with this crate.
    • It covers every length 0..=4096 bytes of random bytes, not a sample.
    • Each length is hashed whole, then again in 3 random splits of up to 8 cuts, empty pieces included.
    • The splits come from a seeded SplitMix64, so a red is reproducible.
  • Negative controls: six mutations of the compression red the tests. Each patch was applied checked and reverted in one script at c8aad54, with the tree left clean afterwards. Every build exited 0 and every test run exited 101. The patches, the script and the reds are in the PR comment.
    • K256[0] wrong: the SHA-256 vectors, Monte and differential red, and SHA-512 stays green.
    • K512[79] wrong (the last round): the SHA-512 vectors, Monte and differential red, and SHA-256 stays green.
    • The schedule's W(t-7) tap moved by one: all five tests red.
    • Maj's b ^ c changed to a ^ c: all five red.
    • The working variables' rotation changed to I % 4: all five red.
    • Each block started from the call's initial hash value instead of the running one: the byte vectors and the differential red, through the whole-message digests that pass many blocks in one call. Monte stays green, because its messages are at most three digests long.
  • toyos-update's existing RFC 8032 and sshsig_oracle tests (OpenSSH's ssh-keygen -Y output) pass through the new SHA-512.

Speed

  • Host CPU time at 56 MiB (the default image's ROOT size). The host is an Apple M4 Pro; the build is opt-level 2 with [profile.toyos]'s debug assertions and overflow checks; each figure is the best of 21 runs' thread CPU time. The source and the log are in the PR comment.
    • SHA-256: toyos-sha2 took 99.8–102.1 ms against sha2 force-soft's 103.3–105.3 ms, a ratio of 0.97–0.98.
    • SHA-512: 62.4–63.4 ms against 67.0–68.2 ms, a ratio of 0.92–0.93.
    • sha2 default gives the same numbers on this host, because its aarch64 hardware backend needs the asm feature.
    • Round 1's head (db2e1f3), in the same session: SHA-256 took 124.95 ms and SHA-512 80.03 ms, against this head's 99.10 ms and 61.63 ms.
  • x86-64, on the T14 (comment 6086424539): the loader at this head hashed the testcases ROOT (320,864,256 bytes) in 4,757,794,201 ticks at 2,419,200,000 Hz (1.967 s, 14.83 ticks/byte) against main's 5,343,577,520 (2.209 s, 16.65 ticks/byte): 0.89x. That loader was built by the compiler before The fork's LLVM gives a loop counter's recurrence only the wrap flags proven for it, a sysroot whose compilers lose a loop's last exit is refused, and the ScalarEvolution issue is closed #790 (merge base d6298c83e); the merge queue builds with main's compiler.
  • x86-64 codegen, read rather than timed. Built for x86_64-unknown-uefi with the same profile, this head's SHA-256 block executes about 3,300 instructions: 65 to load the block, 3 schedule passes of 341, and 4 round passes of 536 plus the loop's own. That is counted from the emitted listing. sha2 soft's fully unrolled block loop is 3,427 instructions. The T14 reading below is the timed verdict.
  • The x86-64 callers that lost SHA-NI/AVX2: bounded, not measured. These are update, pkg and swap on the T14, and the build's hashing on CI. The loss is at most their scalar hash time. The T14's loader on main measured that rate: 320,864,256 bytes in 5,343,577,520 ticks at 2,419,200,000 Hz, which is 2.21 s or 138.5 MiB/s.
    • So update loses at most 2.2 s of a 306 MiB testcases ROOT. The same loader read that ROOT off the stick in 9.31 s.
    • On the default image's 56 MiB ROOT, update loses at most 0.4 s.
    • The build's largest hash is cicache's 47,906,184 tracked bytes, at most 0.33 s at that rate.
    • SHA-NI is not added here. For a 300 MB ROOT it would save at most about 2 s of an update that also downloads and writes those bytes. It would put unsafe intrinsics into the crate every signed image is verified with, and the loader could not take that path anyway. Whether that trade is wanted is the owner's call, recorded in the issue.

Gates (head b9132eb)

  • cargo run -- --ci host: EXIT=0, "Host: 78 step(s), all green".
    • The licence gate reports "6 exception(s) stand, and nothing else is refused", and "NOTICE section tests/cavp/ (NIST-PD) is not shipped".
    • Clippy passes, including both UEFI loader targets.
  • cargo test -p toyos-sha2: EXIT=0, 5 tests.
  • cargo run -- --build-only: EXIT=0.
  • The whole guest suite, cargo test --test toyos-build: EXIT=0, "41 passed, 41 total" over 44 guests. uptime load averages were 8.82 / 9.75 / 13.08 before and 15.01 / 11.18 / 13.45 after.
  • T14: boot:testcases at b9132ebb7, run by the orchestrator: both image hashes matched request.txt, both toyos-metal --fat32-check exit 0; judge cargo test --test toyos-build -- --metal --metal-readback <dir> boot:testcases EXIT=0, [metal] 249 passed, 0 failed, 2 boot(s).

No new guest test: the hash is a pure function, and the host tiers above cover it completely.

Net lines: 2785 insertions and 36 deletions.

  • 2190 are NIST data.
  • 225 are tests.
  • 255 are toyos-sha2/src/lib.rs.
  • 65 are NOTICE and issues.
  • The rest, about 50, are caller and manifest changes.

Coordination with #808 (wt/toyos-pkgrepo)

#808 had not landed at this head. Whichever branch lands second resolves these, and nothing else:

  • toyos-update/Cargo.toml: keep toyos-sha2, with no sha2 line.
  • Root Cargo.toml: keep toyos-sha2 in place of sha2 = "0.10", never both.
  • sig.rs: use sha2::{Digest as _, Sha512}; becomes use toyos_sha2::Sha512;.
  • repo.rs: delete use sha2::Digest as _; and write sha2::Sha256 as toyos_sha2::Sha256 in both places. Digest::from(self.hash.finalize()) compiles unchanged.
  • Regenerate Cargo.lock.

Unsure

  • Run time on x86-64 outside the loader (update, pkg, swap) is not measured; the loader's reading above bounds the scalar path.

🤖 Generated with Claude Code

https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C

Japabu and others added 3 commits October 9, 2026 17:58
…n the loader's and update's image verification, swap, pkg and the build

The owner's ruling on the dependency audit ("Own SHA-2"): the hash the
signed-image trust boundary rests on is ours. `toyos-sha2` is SHA-256 and
SHA-512 written from FIPS 180-4 (§4.2, §5.1, §5.3, §6.2, §6.4), no_std and
forbid(unsafe_code), scalar on every target. SHA-512 is in because
`toyos-update::sig` builds the SSHSIG message over SHA-512(header).

Its API is the subset of sha2's `Digest` calls the tree makes, as inherent
methods: `new`, `update(impl AsRef<[u8]>)`, `finalize() -> [u8; N]` and
`digest(impl AsRef<[u8]>) -> [u8; N]`. Every caller drops its
`use sha2::Digest` and its `.into()`; cicache's `{:x}` of a GenericArray
becomes `release::sha256_hex`.

Callers moved: toyos-update (`sha256`, and `sig`'s SHA-512), toyos-swap,
userland/update's streamed ROOT hash, userland/pkg, and the build's
cicache, sourcegate, sysroot, image and release.

`ed25519-dalek` 2.2 still hashes with `sha2` 0.10.9 inside a verification
(its `verifying.rs` takes `sha2::Sha512` for H(R || A || M)), so `sha2`
stays in the lock and the loader keeps its `force-soft` line, recommented
as being for that alone.

Oracles: NIST's CAVP SHAVS response files, committed under
toyos-sha2/cavp/ and recorded in NOTICE (byte-oriented ShortMsg, LongMsg
and Monte for both hashes, bit-oriented ShortMsg and Monte; the
bit-oriented LongMsg files, 16.8 MB, are not), and RustCrypto's sha2 as a
dev-dependency over every length 0..=4096 in random splits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
…IST-PD

Clippy's chunks_exact_to_as_chunks and manual_is_multiple_of, under
`--ci host`'s -D warnings: a block's words and the whole blocks of an
update are `as_chunks`, which leaves no `expect` on a length the type
already fixes.

The licence gate judges every file under a shipped path package's
directory as shipped, so the CAVP vectors under toyos-sha2/cavp/ are judged
by their NOTICE line, NIST-PD: NIST's notice that a work of the US
government is public domain (17 U.S.C. §105), allowed beside CC0-1.0, the
other public-domain dedication the list carries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
… a pass

The rounds moved all eight working variables every round; the scalar hash
measured 1.47x the CPU time of sha2's force-soft backend over a 56 MiB
message on the host. Each round now writes only the two it changes (d and
h, §6.2.2 step 3), and the loop takes eight rounds a pass with the names
rotated, which is the same computation: 1.19x to 1.25x.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Negative controls, applied with git apply --check then git apply, built, cargo test -p toyos-sha2 run, and reverted with git apply -R in one script at head db2e1f3 (tree clean after: git status --porcelain empty).

patch build test reds, by name
mutation-k256-first (K256[0] 0x428a2f98 -> 0x428a2f99) EXIT=0 EXIT=101 sha256_byte_vectors shabytetestvectors/SHA256ShortMsg.rsp: Len = 0; sha256_bit_vectors shabittestvectors/SHA256ShortMsg.rsp: Len = 0; sha256_monte_carlo shabytetestvectors/SHA256Monte.rsp: COUNT = 0; differential SHA-256 of 0 bytes. All three SHA-512 tests stay green.
mutation-k512-last (K512[79] ...817 -> ...816) EXIT=0 EXIT=101 sha512_byte_vectors shabytetestvectors/SHA512ShortMsg.rsp: Len = 0; sha512_bit_vectors shabittestvectors/SHA512ShortMsg.rsp: Len = 0; sha512_monte_carlo shabytetestvectors/SHA512Monte.rsp: COUNT = 0; differential SHA-512 of 0 bytes. All three SHA-256 tests stay green.

mutation-k256-first.patch

--- a/toyos-sha2/src/lib.rs
+++ b/toyos-sha2/src/lib.rs
@@ -24,7 +24,7 @@
 /// SHA-256's round constants, §4.2.2.
 #[rustfmt::skip]
 const K256: [u32; 64] = [
-    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+    0x428a2f99, 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,

mutation-k512-last.patch

--- a/toyos-sha2/src/lib.rs
+++ b/toyos-sha2/src/lib.rs
@@ -62,7 +62,7 @@
     0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
     0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
     0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
-    0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817,
+    0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475816,
 ];
 
 /// SHA-512's initial hash value, §5.3.5.

The script:

#!/bin/sh
# Apply one checked mutation patch, run toyos-sha2's tests, report, restore.
set -u
tree=<path>
logs=<path>
for m in mutation-k256-first mutation-k512-last; do
  cd "$tree" || exit 9
  git apply --check "$logs/$m.patch" || { echo "$m: does not apply"; exit 9; }
  git apply "$logs/$m.patch"
  cargo build -p toyos-sha2 > "$logs/$m.build.log" 2>&1; echo "$m build EXIT=$?"
  cargo test -p toyos-sha2 > "$logs/$m.test.log" 2>&1; echo "$m test EXIT=$?"
  git apply -R "$logs/$m.patch"
  git status --porcelain
done

Host benchmark source (scratch crate, [profile.release] opt-level = 2; built twice, once with --features soft = sha2/force-soft; thread CPU time, best of 21):

[package]
name = "sha2bench"
version = "0.0.0"
edition = "2024"
publish = false

[features]
soft = ["sha2/force-soft"]

[dependencies]
toyos-sha2 = { path = "<worktree>/toyos-sha2" }
sha2 = "0.10"
libc = "0.2"

[profile.release]
opt-level = 2

[workspace]
use sha2::Digest as _;
fn cpu() -> f64 {
    let mut t = libc::timespec { tv_sec: 0, tv_nsec: 0 };
    assert_eq!(unsafe { libc::clock_gettime(libc::CLOCK_THREAD_CPUTIME_ID, &mut t) }, 0);
    t.tv_sec as f64 + t.tv_nsec as f64 * 1e-9
}

fn best<const N: usize>(f: impl Fn() -> [u8; N]) -> (f64, [u8; N]) {
    let mut best = f64::MAX;
    let mut out = [0; N];
    for _ in 0..21 {
        let t = cpu();
        out = std::hint::black_box(f());
        best = best.min(cpu() - t);
    }
    (best, out)
}

fn main() {
    let size: usize = std::env::args().nth(1).expect("bytes").parse().expect("a size");
    let data: Vec<u8> = (0..size).map(|i| (i as u32).wrapping_mul(2654435761) as u8).collect();
    let mib = size as f64 / (1 << 20) as f64;
    let (a, da) = best(|| toyos_sha2::Sha256::digest(&data));
    let (b, db) = best(|| sha2::Sha256::digest(&data).into());
    assert_eq!(da, db);
    let (c, dc) = best(|| toyos_sha2::Sha512::digest(&data));
    let (d, dd) = best(|| sha2::Sha512::digest(&data).into());
    assert_eq!(dc, dd);
    let soft = if cfg!(feature = "soft") { "sha2 force-soft" } else { "sha2 default" };
    println!("{size} bytes ({mib:.1} MiB), best of 21, thread CPU time");
    println!("SHA-256 toyos-sha2 {:8.2} ms {:7.1} MiB/s | {soft} {:8.2} ms {:7.1} MiB/s | ratio {:.2}", a * 1e3, mib / a, b * 1e3, mib / b, a / b);
    println!("SHA-512 toyos-sha2 {:8.2} ms {:7.1} MiB/s | {soft} {:8.2} ms {:7.1} MiB/s | ratio {:.2}", c * 1e3, mib / c, d * 1e3, mib / d, c / d);
}

Output at db2e1f3 (Apple M4 Pro, load average 40-48 during it):

18:49  up 10 days,  6:33, 6 users, load averages: 40.80 78.48 84.39
58720256 bytes (56.0 MiB), best of 21, thread CPU time
SHA-256 toyos-sha2   130.21 ms   430.1 MiB/s | sha2 default   107.52 ms   520.9 MiB/s | ratio 1.21
SHA-512 toyos-sha2    83.35 ms   671.9 MiB/s | sha2 default    68.29 ms   820.0 MiB/s | ratio 1.22
58720256 bytes (56.0 MiB), best of 21, thread CPU time
SHA-256 toyos-sha2   138.69 ms   403.8 MiB/s | sha2 force-soft   108.89 ms   514.3 MiB/s | ratio 1.27
SHA-512 toyos-sha2    86.41 ms   648.1 MiB/s | sha2 force-soft    73.37 ms   763.3 MiB/s | ratio 1.18
6291456 bytes (6.0 MiB), best of 21, thread CPU time
SHA-256 toyos-sha2    13.58 ms   441.7 MiB/s | sha2 force-soft    11.26 ms   532.9 MiB/s | ratio 1.21
SHA-512 toyos-sha2     8.77 ms   684.0 MiB/s | sha2 force-soft     7.29 ms   823.5 MiB/s | ratio 1.20
58720256 bytes (56.0 MiB), best of 21, thread CPU time
SHA-256 toyos-sha2   130.76 ms   428.2 MiB/s | sha2 default   108.11 ms   518.0 MiB/s | ratio 1.21
SHA-512 toyos-sha2    81.93 ms   683.5 MiB/s | sha2 default    68.22 ms   820.9 MiB/s | ratio 1.20
58720256 bytes (56.0 MiB), best of 21, thread CPU time
SHA-256 toyos-sha2   128.53 ms   435.7 MiB/s | sha2 force-soft   105.30 ms   531.8 MiB/s | ratio 1.22
SHA-512 toyos-sha2    81.90 ms   683.7 MiB/s | sha2 force-soft    68.24 ms   820.7 MiB/s | ratio 1.20
6291456 bytes (6.0 MiB), best of 21, thread CPU time
SHA-256 toyos-sha2    13.59 ms   441.4 MiB/s | sha2 force-soft    11.27 ms   532.3 MiB/s | ratio 1.21
SHA-512 toyos-sha2     8.77 ms   684.1 MiB/s | sha2 force-soft     7.30 ms   822.0 MiB/s | ratio 1.20
18:51  up 10 days,  6:35, 6 users, load averages: 48.20 69.18 79.98

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #812 at head db2e1f337, against .claude/agents/reviewer.md. This is round 1.

Net lines (git diff --shortstat origin/main...db2e1f337): 32 files, +9649/−35.

  • Production: toyos-sha2/src/lib.rs +258, plus about 50 lines of callers and manifests. That replaces a dependency on the owner's "Own SHA-2", so the growth is accepted.
  • Tests: toyos-sha2/src/tests.rs +322.
  • Data: 8974 lines of NIST .rsp.
  • NOTICE: +44.

Correctness against FIPS 180-4, read by hand at this head:

  • K256, H256, K512 and H512 match §4.2.2, §4.2.3, §5.3.3 and §5.3.5.
  • Σ/σ rotations match §4.1.2 and §4.1.3 (256: 2/13/22, 6/11/25, 7/18/>>3, 17/19/>>10; 512: 28/34/39, 14/18/41, 1/8/>>7, 19/61/>>6).
  • Ch and Maj are in their standard equivalent forms.
  • The renamed-in-place round order is right for all eight rounds of a pass.
  • The pad length (B−L−1+B−filled)%B+1 gives 1..=B bytes and ends at B−L.

The oracles (CAVP byte ShortMsg, LongMsg and Monte, plus the sha2 0.10.9 differential over every length 0..=4096 in random splits) are independent of this code, and the K256[0] and K512[79] mutations red them by name (mutate.log: build EXIT=0, test EXIT=101 for both). I found no mutation of the padding or buffering that these tests would miss.

BLOCKER

  • src/licence.rs:67 — "NIST-PD" is added to ALLOWED. That is outside the brief, and the orchestrator has ruled that this branch does not widen the licence list.
    • Fix: move the vectors out of every shipped path package's directory, so that Shipping::ships (src/licence.rs:975, under(&self.packages, …)) does not count them. For example, put them in a test-data directory that no shipped package's manifest sits above, and have tests.rs read them from there.
    • In the same diff: move the NOTICE section heading (NOTICE:417, toyos-sha2/cavp/) to the new path and leave ALLOWED byte-identical to main.
    • Evidence owed: cargo run -- --ci host green at the new head, with the licence gate's line in the log and git diff origin/main -- src/licence.rs empty.
  • PR body "Speed" — the cost is measured only where neither arm had an instruction path. On aarch64, sha2 0.10.9 takes its hardware backend only under its asm feature (sha2-0.10.9/src/sha256.rs:19). That is why "default gives the same numbers as force-soft" on the M4.
    • On x86-64, every caller except the loader used sha2's x86 backend: SHA-NI, and AVX2 for SHA-512, through cpufeatures. Those callers are update's streamed ROOT hash and pkg/swap on the T14, and the build's cicache source hashing, image.rs root_uuid and sysroot on the x86-64 CI runners.
    • The "1.2x" therefore does not describe those paths, and the x86-64 regression there is unmeasured. The body's "Unsure" says the T14 cost is not measured.
    • Measure on an x86-64 host that has SHA-NI: the same benchmark, toyos-sha2 against sha2 default, at 56 MiB. Then either record the lost instruction path in issues/ with an owner, the numbers and an exit, or show it is immaterial against what those callers hash.
  • Evidence, metal — the change is on the loader's boot path, and the T14 reading is owed (metal-stage.log: EXIT=2, staged, the machine not touched).
    • Needed: the loader's verify line and its ROOT hashed in … counter ticks at this head.
    • Also needed, for comparison: the same line from main at d6298c8 on the same machine. The loader was already scalar there (force-soft), so this pair is the honest x86-64 reading of the loader's per-boot cost.
    • The two QEMU loader lines in the body ran at different host loads and are no verdict, as the body says.

NOTE

SEND BACK

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

T14 at db2e1f337, run by the orchestrator: boot:testcases, two boots (testcases 56f84f55…60fd600e, testcases-watchdog 22b7d25c…6e7913, hashes checked), each toyos-metal --fat32-check exit 0; judge EXIT=0, [metal] 249 passed, 0 failed, 2 boot(s). The loader, at the counter's stated 2419200000 Hz: Slot A: signed header … verifies under this loader's key on both; Slot A: ROOT hashed in 6418027444 counter ticks (testcases, 2.653 s) and 188011514 (watchdog, 77.7 ms). Main's comparison, the same lines from #807's boots at 0ad87a593 (base d6298c83e, the loader unchanged from main, scalar sha2 under force-soft): 5343577520 (2.209 s) and 166696118 (68.9 ms). So the loader's ROOT hash takes about 1.20x and 1.13x as long: +0.44 s on every testcases boot. One boot each, different images; no timing verdict, but the direction matches the host's 1.2x.

Japabu and others added 3 commits October 9, 2026 19:40
…and the bit-oriented ones go

The licence gate counts every file under a shipped path package's directory
as shipped, so vectors under toyos-sha2/cavp/ needed NIST-PD in ALLOWED. They
are test input only: under tests/, the build's own directory, no shipped
package holds them, NOTICE's section says NIST-PD as before, and ALLOWED is
main's again.

The bit-oriented ShortMsg and Monte files go with the test code that read
them. The crate hashes whole bytes; the bit test padded in its own helper and
drove the compression function directly, which the byte ShortMsg, LongMsg and
Monte files already drive over thousands of blocks, and the bit Monte files
differ from the byte ones only by their seed.

The host-tools issue named sha2 as what src/release.rs hashes with; it is
toyos-sha2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
…schedule computed a pass ahead, every round's indices constant, and the hash value held across a run of blocks

The loader's ROOT hash took 2.653 s on the T14 against main's 2.209 s with
sha2's soft backend. Three changes close it, measured on the host at
opt-level 2 with [profile.toyos]'s debug assertions and overflow checks, 56 MiB,
best of 21 runs' thread CPU time:

- The schedule is a sixteen-word ring, W(t) written over W(t-16) a pass at a
  time, in place of a 64- or 80-word array.
- Rounds and schedule words are written out sixteen to a pass through const
  generics, so every index into the working variables and the schedule is a
  constant; LLVM had kept the rounds a loop over memory.
- One call compresses every whole block of an update, the hash value in
  locals between them; Maj is b ^ ((a ^ b) & (b ^ c)), whose b ^ c is the
  previous round's a ^ b.

SHA-256 went from 124.95 ms to 99.10 ms and SHA-512 from 80.03 ms to 61.63 ms,
against sha2 force-soft's 103.33-105.29 ms and 66.98-68.22 ms. The two
compression functions are now one macro over their word, block, constants and
rotations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
update's ROOT hash, pkg and swap on the T14 and the build's hashing on x86-64
CI runners took sha2's x86 backend; toyos-sha2 is scalar and forbids unsafe.
The loss is at most the scalar time, which the T14's loader measured on main:
2.21 s for the testcases image's 306 MiB ROOT. Whether that is worth unsafe
intrinsics in the crate every signed image is verified with is the owner's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
@Japabu Japabu changed the title ToyOS's own SHA-256 and SHA-512 replace the sha2 crate in the loader's and update's image verification, swap, pkg and the build ToyOS's own SHA-256 and SHA-512, as fast as sha2's soft backend, replace the sha2 crate in the loader's and update's image verification, swap, pkg and the build Oct 9, 2026
@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2 evidence for #812 at b9132eb (the code at c8aad54; b9132eb adds only the issue file). Paths are shown relative to the worktree or as <scratch>.

Host benchmark: toyos-sha2 against sha2 0.10.9, 56 MiB

Apple M4 Pro (aarch64; sha2's default there is its soft backend, its hardware one needing the asm feature). opt-level 2 with [profile.toyos]'s debug assertions and overflow checks, best of 21 runs' thread CPU time. The last two lines are the round-1 head's toyos-sha2 (db2e1f3, as a path crate old-sha2) against this head, same session.

19:40  up 10 days,  7:25, 6 users, load averages: 12.51 11.45 16.05
58720256 bytes (56.0 MiB), best of 21, thread CPU time
SHA-256 toyos-sha2   102.09 ms   548.6 MiB/s | sha2 force-soft   105.29 ms   531.8 MiB/s | ratio 0.97
SHA-512 toyos-sha2    63.40 ms   883.3 MiB/s | sha2 force-soft    68.22 ms   820.9 MiB/s | ratio 0.93
58720256 bytes (56.0 MiB), best of 21, thread CPU time
SHA-256 toyos-sha2   101.94 ms   549.3 MiB/s | sha2 force-soft   104.51 ms   535.8 MiB/s | ratio 0.98
SHA-512 toyos-sha2    62.99 ms   889.0 MiB/s | sha2 force-soft    68.22 ms   820.9 MiB/s | ratio 0.92
58720256 bytes (56.0 MiB), best of 21, thread CPU time
SHA-256 toyos-sha2    99.77 ms   561.3 MiB/s | sha2 force-soft   103.33 ms   541.9 MiB/s | ratio 0.97
SHA-512 toyos-sha2    62.37 ms   897.9 MiB/s | sha2 force-soft    66.98 ms   836.1 MiB/s | ratio 0.93
58720256 bytes (56.0 MiB), best of 21, thread CPU time
SHA-256 toyos-sha2   100.07 ms   559.6 MiB/s | sha2 default   102.45 ms   546.6 MiB/s | ratio 0.98
SHA-512 toyos-sha2    62.06 ms   902.4 MiB/s | sha2 default    67.18 ms   833.5 MiB/s | ratio 0.92
19:41  up 10 days,  7:25, 6 users, load averages: 12.74 11.71 15.97
19:41  up 10 days,  7:25, 6 users, load averages: 12.54 11.70 15.91
SHA-256 db2e1f337   124.95 ms | this head    99.10 ms
SHA-512 db2e1f337    80.03 ms | this head    61.63 ms
Cargo.toml
[package]
name = "sha2bench"
version = "0.0.0"
edition = "2024"
publish = false

[features]
soft = ["sha2/force-soft"]

[dependencies]
toyos-sha2 = { path = "<worktree>/toyos-sha2" }
sha2 = "0.10"
libc = "0.2"
old-sha2 = { path = "old" }

# As `[profile.toyos]`, which the loader, update, swap and pkg build with.
[profile.release]
opt-level = 2
debug-assertions = true
overflow-checks = true

[workspace]
src/main.rs
use sha2::Digest as _;
fn cpu() -> f64 {
    let mut t = libc::timespec { tv_sec: 0, tv_nsec: 0 };
    assert_eq!(unsafe { libc::clock_gettime(libc::CLOCK_THREAD_CPUTIME_ID, &mut t) }, 0);
    t.tv_sec as f64 + t.tv_nsec as f64 * 1e-9
}

fn best<const N: usize>(f: impl Fn() -> [u8; N]) -> (f64, [u8; N]) {
    let mut best = f64::MAX;
    let mut out = [0; N];
    for _ in 0..21 {
        let t = cpu();
        out = std::hint::black_box(f());
        best = best.min(cpu() - t);
    }
    (best, out)
}

fn main() {
    let size: usize = std::env::args().nth(1).expect("bytes").parse().expect("a size");
    let data: Vec<u8> = (0..size).map(|i| (i as u32).wrapping_mul(2654435761) as u8).collect();
    let mib = size as f64 / (1 << 20) as f64;
    let (a, da) = best(|| toyos_sha2::Sha256::digest(&data));
    let (b, db) = best(|| sha2::Sha256::digest(&data).into());
    assert_eq!(da, db);
    let (c, dc) = best(|| toyos_sha2::Sha512::digest(&data));
    let (d, dd) = best(|| sha2::Sha512::digest(&data).into());
    assert_eq!(dc, dd);
    if std::env::args().nth(2).as_deref() == Some("old") {
        let (a, _) = best(|| old_sha2::Sha256::digest(&data));
        let (b, _) = best(|| toyos_sha2::Sha256::digest(&data));
        let (c, _) = best(|| old_sha2::Sha512::digest(&data));
        let (d, _) = best(|| toyos_sha2::Sha512::digest(&data));
        println!("SHA-256 db2e1f337 {:8.2} ms | this head {:8.2} ms", a * 1e3, b * 1e3);
        println!("SHA-512 db2e1f337 {:8.2} ms | this head {:8.2} ms", c * 1e3, d * 1e3);
        return;
    }
    let soft = if cfg!(feature = "soft") { "sha2 force-soft" } else { "sha2 default" };
    println!("{size} bytes ({mib:.1} MiB), best of 21, thread CPU time");
    println!("SHA-256 toyos-sha2 {:8.2} ms {:7.1} MiB/s | {soft} {:8.2} ms {:7.1} MiB/s | ratio {:.2}", a * 1e3, mib / a, b * 1e3, mib / b, a / b);
    println!("SHA-512 toyos-sha2 {:8.2} ms {:7.1} MiB/s | {soft} {:8.2} ms {:7.1} MiB/s | ratio {:.2}", c * 1e3, mib / c, d * 1e3, mib / d, c / d);
}

Mutations at c8aad54

Each patch applied checked, toyos-sha2 built and its tests run, the patch reverted, the tree left clean, all in one script.

head c8aad54b1
mutation-k256-first build EXIT=0
mutation-k256-first test EXIT=101
mutation-k256-first restored, porcelain: []
mutation-k512-last build EXIT=0
mutation-k512-last test EXIT=101
mutation-k512-last restored, porcelain: []
mutation-schedule-tap build EXIT=0
mutation-schedule-tap test EXIT=101
mutation-schedule-tap restored, porcelain: []
mutation-maj build EXIT=0
mutation-maj test EXIT=101
mutation-maj restored, porcelain: []
mutation-hash-across-blocks build EXIT=0
mutation-hash-across-blocks test EXIT=101
mutation-hash-across-blocks restored, porcelain: []
mutation-names-rotate build EXIT=0
mutation-names-rotate test EXIT=101
mutation-names-rotate restored, porcelain: []
mutation-k256-first: the patch and which tests red
diff --git a/toyos-sha2/src/lib.rs b/toyos-sha2/src/lib.rs
index 3e85b56fa..5c4b74ec1 100644
--- a/toyos-sha2/src/lib.rs
+++ b/toyos-sha2/src/lib.rs
@@ -24,7 +24,7 @@ mod tests;
 /// SHA-256's round constants, §4.2.2.
 #[rustfmt::skip]
 const K256: [u32; 64] = [
-    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+    0x428a2f99, 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,
test tests::every_length_to_4096_in_random_splits_agrees_with_sha2 ... FAILED
test tests::sha256_byte_vectors ... FAILED
test tests::sha256_monte_carlo ... FAILED
test tests::sha512_byte_vectors ... ok
test tests::sha512_monte_carlo ... ok
test result: FAILED. 2 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
mutation-k512-last: the patch and which tests red
diff --git a/toyos-sha2/src/lib.rs b/toyos-sha2/src/lib.rs
index 3e85b56fa..f8231a48a 100644
--- a/toyos-sha2/src/lib.rs
+++ b/toyos-sha2/src/lib.rs
@@ -62,7 +62,7 @@ const K512: [u64; 80] = [
     0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
     0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
     0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
-    0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817,
+    0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475818,
 ];
 
 /// SHA-512's initial hash value, §5.3.5.
test tests::every_length_to_4096_in_random_splits_agrees_with_sha2 ... FAILED
test tests::sha512_byte_vectors ... FAILED
test tests::sha512_monte_carlo ... FAILED
test tests::sha256_byte_vectors ... ok
test tests::sha256_monte_carlo ... ok
test result: FAILED. 2 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
mutation-schedule-tap: the patch and which tests red
diff --git a/toyos-sha2/src/lib.rs b/toyos-sha2/src/lib.rs
index 3e85b56fa..9b7a40f06 100644
--- a/toyos-sha2/src/lib.rs
+++ b/toyos-sha2/src/lib.rs
@@ -123,7 +123,7 @@ macro_rules! compress {
                 let s1 = w2.rotate_right(x) ^ w2.rotate_right(y) ^ (w2 >> z);
                 w[I] = w[I]
                     .wrapping_add(s0)
-                    .wrapping_add(w[(I + 9) % 16])
+                    .wrapping_add(w[(I + 8) % 16])
                     .wrapping_add(s1);
             }
 
test tests::every_length_to_4096_in_random_splits_agrees_with_sha2 ... FAILED
test tests::sha256_byte_vectors ... FAILED
test tests::sha512_byte_vectors ... FAILED
test tests::sha256_monte_carlo ... FAILED
test tests::sha512_monte_carlo ... FAILED
test result: FAILED. 0 passed; 5 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
mutation-maj: the patch and which tests red
diff --git a/toyos-sha2/src/lib.rs b/toyos-sha2/src/lib.rs
index 3e85b56fa..97eb746f1 100644
--- a/toyos-sha2/src/lib.rs
+++ b/toyos-sha2/src/lib.rs
@@ -107,7 +107,7 @@ macro_rules! compress {
                     .wrapping_add(e.rotate_right(x) ^ e.rotate_right(y) ^ e.rotate_right(z));
                 let [x, y, z] = $s0;
                 let t2 = (a.rotate_right(x) ^ a.rotate_right(y) ^ a.rotate_right(z))
-                    .wrapping_add(b ^ ((a ^ b) & (b ^ c)));
+                    .wrapping_add(b ^ ((a ^ b) & (a ^ c)));
                 v[at(3)] = v[at(3)].wrapping_add(t1);
                 v[at(7)] = t1.wrapping_add(t2);
             }
test tests::every_length_to_4096_in_random_splits_agrees_with_sha2 ... FAILED
test tests::sha256_byte_vectors ... FAILED
test tests::sha512_byte_vectors ... FAILED
test tests::sha256_monte_carlo ... FAILED
test tests::sha512_monte_carlo ... FAILED
test result: FAILED. 0 passed; 5 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
mutation-hash-across-blocks: the patch and which tests red
diff --git a/toyos-sha2/src/lib.rs b/toyos-sha2/src/lib.rs
index 3e85b56fa..e89e06dce 100644
--- a/toyos-sha2/src/lib.rs
+++ b/toyos-sha2/src/lib.rs
@@ -133,7 +133,7 @@ macro_rules! compress {
                 for (word, bytes) in w.iter_mut().zip(block.as_chunks().0) {
                     *word = <$word>::from_be_bytes(*bytes);
                 }
-                let mut v = hash;
+                let mut v = *state;
                 for (pass, k) in $k.as_chunks::<16>().0.iter().enumerate() {
                     if pass > 0 {
                         sixteen!(schedule(&mut w));
test tests::every_length_to_4096_in_random_splits_agrees_with_sha2 ... FAILED
test tests::sha256_byte_vectors ... FAILED
test tests::sha512_byte_vectors ... FAILED
test tests::sha256_monte_carlo ... ok
test tests::sha512_monte_carlo ... ok
test result: FAILED. 2 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
mutation-names-rotate: the patch and which tests red
diff --git a/toyos-sha2/src/lib.rs b/toyos-sha2/src/lib.rs
index 3e85b56fa..e2b975595 100644
--- a/toyos-sha2/src/lib.rs
+++ b/toyos-sha2/src/lib.rs
@@ -96,7 +96,7 @@ macro_rules! compress {
             /// `v[(8 - I % 8) % 8]`, and it writes only its new `e` and `a`.
             #[inline(always)]
             fn round<const I: usize>(v: &mut [$word; 8], k: &[$word; 16], w: &[$word; 16]) {
-                let at = |n: usize| (n + 8 - I % 8) % 8;
+                let at = |n: usize| (n + 8 - I % 4) % 8;
                 let (a, b, c) = (v[at(0)], v[at(1)], v[at(2)]);
                 let (e, f, g) = (v[at(4)], v[at(5)], v[at(6)]);
                 let [x, y, z] = $s1;
test tests::every_length_to_4096_in_random_splits_agrees_with_sha2 ... FAILED
test tests::sha256_byte_vectors ... FAILED
test tests::sha512_byte_vectors ... FAILED
test tests::sha256_monte_carlo ... FAILED
test tests::sha512_monte_carlo ... FAILED
test result: FAILED. 0 passed; 5 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
mutate.sh
#!/bin/sh
# Apply one checked mutation patch, build and run toyos-sha2's tests, report, restore.
set -u
tree=<worktree>
logs=<scratch>
cd "$tree" || exit 9
echo "head $(git rev-parse --short HEAD)"
for m in mutation-k256-first mutation-k512-last mutation-schedule-tap mutation-maj mutation-hash-across-blocks mutation-names-rotate; do
  git apply --check "$logs/$m.patch" || { echo "$m: does not apply"; exit 9; }
  git apply "$logs/$m.patch"
  cargo build -p toyos-sha2 > "$logs/$m.build.log" 2>&1; echo "$m build EXIT=$?"
  cargo test -p toyos-sha2 > "$logs/$m.test.log" 2>&1; echo "$m test EXIT=$?"
  git apply -R "$logs/$m.patch"
  echo "$m restored, porcelain: [$(git status --porcelain --ignore-submodules=none)]"
done

x86-64 listing count

A staticlib crate calling toyos_sha2::Sha256::digest and sha2::Sha256::digest (force-soft), built with RUSTFLAGS=--emit=asm,link cargo build --release --target x86_64-unknown-uefi under the profile above. Instruction lines counted in each compress256: this head's block load 65, schedule pass 341 (run 3 times), round pass 536 (run 4 times); sha2 soft's per-block loop 3,427.

Cargo.toml and src/lib.rs
[package]
name = "sha2x86"
version = "0.0.0"
edition = "2024"
publish = false

[lib]
crate-type = ["staticlib"]

[dependencies]
toyos-sha2 = { path = "<worktree>/toyos-sha2" }
sha2 = { version = "0.10", default-features = false, features = ["force-soft"] }

# As `[profile.toyos]`, which the loader builds with.
[profile.release]
opt-level = 2
debug-assertions = true
overflow-checks = true
panic = "abort"

[workspace]
#![no_std]
use sha2::Digest as _;
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! { loop {} }
#[unsafe(no_mangle)]
pub extern "C" fn ours(m: &[u8], out: &mut [u8; 32]) { *out = toyos_sha2::Sha256::digest(m); }
#[unsafe(no_mangle)]
pub extern "C" fn theirs(m: &[u8], out: &mut [u8; 32]) { *out = sha2::Sha256::digest(m).into(); }

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #812 at head b9132ebb7, against .claude/agents/reviewer.md. This is round 2.

Round-1 BLOCKERs

  • src/licence.rs, NIST-PD in ALLOWED: CLOSED.
    • git diff origin/main...b9132ebb7 -- src/licence.rs is empty.
    • The vectors are now in tests/cavp/, and NOTICE's section heading moved with them.
    • ci-host.log (gates.head b9132ebb7, EXIT=0, "Host: 78 step(s), all green") reads "NOTICE section tests/cavp/ (NIST-PD) is not shipped" and "6 exception(s) stand, and nothing else is refused".
    • The FAILED and SIGABRT lines in that log all sit inside [ci] control steps, each of which reached its verdict.
  • Speed: the x86-64 callers that lost SHA-NI/AVX2: CLOSED, as a recorded compromise.
    • issues/x86-64-hashing-runs-scalar-where-the-cpu-has-sha-instructions.md is kind: question / status: owner, which is legal under issues/README.md.
    • It names the callers. It bounds the loss by the scalar time main's T14 loader measured: 5,343,577,520 ticks for 320,864,256 bytes at 2,419,200,000 Hz. It says update's own time is unmeasured, and its exit is readable.
    • The bound is an upper bound only while toyos-sha2's x86-64 scalar rate is at least sha2 soft's. The metal reading below holds that.
    • The host CPU-time claim stands on bench-r2.log. The source, best of 21 runs of thread CPU time, assert_eq! between the two digests, and round 1 against this head in the same session are all in comment 6086316086. The ratios are 0.97–0.98 for SHA-256 and 0.92–0.93 for SHA-512.
  • Evidence, metal: OPEN. metal-stage.log is EXIT=2: staged, and the machine was not touched.

I re-read the c8aad54 compression against FIPS 180-4 §6.2.2 and §6.4.2.

  • In the ring schedule, w[I] holds W(t-16) going in. (I+1)%16, (I+14)%16 and (I+9)%16 are W(t-15), W(t-2) and W(t-7) at every I of a pass, before and after the wrap.
  • In round::<I>, at(n) makes round I+1's a, b, e the new a, the old a and the new e. 64 and 80 are both multiples of 16, so each pass ends aligned.
  • Maj written as b ^ ((a^b)&(b^c)) is Maj, and g ^ (e&(f^g)) is Ch.
  • The six mutations red the tests as stated (mutate.log: every build EXIT=0, every test EXIT=101, porcelain clean). I found no mutation of the schedule, the rounds, the buffering or the padding that the byte vectors, Monte and the 0..=4096 split differential would miss.

Net lines (git diff --shortstat origin/main...b9132ebb7): 29 files, +2785/−36.

  • Production: toyos-sha2/src/lib.rs +255 and about 50 lines of callers and manifests. This replaces a dependency on the owner's ruling, and is accepted.
  • Tests: +225.
  • NIST data: +2190.
  • NOTICE and issues: +65.

Round 1's NOTEs are closed: the bit vectors and padded/bit_file are deleted, and the shasum row names toyos-sha2.

BLOCKER

  • bootloader/src/slot.rs:149-150 — the T14 reading at this head is owed. The change is on the loader's boot path. The PR title claims "as fast as sha2's soft backend", and on x86-64 nothing has timed that. The x86-64 listing count (65 + 3×341 + 4×536) is a static count from a listing with no exit code or log posted, and it is not a timing. The reading must show all of the following:
    • The toyos-metal run's command, its exit code and its log, for the testcases image whose sha256 is request.txt's b792b6d7…b012d, built from b9132ebb7.
    • The loader's section and signature verification passing, and boot:testcases green on that boot.
    • The line Slot A: ROOT hashed in N counter ticks, with the ROOT's byte count from the same log.
    • N/bytes no greater than main's 5,343,577,520 / 320,864,256 ticks per byte (16.65), stated as a ratio. If the ratio is above 1.00, also a second main boot at d6298c8 on the T14. If the ratio exceeds that pair's spread, the speed claim in the title is false, the issue's "at most 2.2 s" bound understates, and the branch goes back to the compression.
    • For reference, round 1 read 6,418,027,444 ticks, a ratio of 1.20.

NOTE

SEND BACK

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

T14 at b9132ebb7, run by the orchestrator (loader built by the pre-#790 compiler this branch sits on): boot:testcases, two boots, each image's sha256 checked against request.txt before its flash (testcases b792b6d7…37eb012d, testcases-watchdog d06af5d8…8d1edc), each toyos-metal --fat32-check exit 0; judge cargo test --test toyos-build -- --metal --metal-readback <dir> boot:testcases: EXIT=0, [metal] 249 passed, 0 failed, 2 boot(s). The loader, at 2419200000 Hz: Slot A: signed header … verifies under this loader's key on both; testcases: ROOT: read into memory … from LBA 212992+626688 (320,864,256 bytes) and Slot A: ROOT hashed in 4757794201 counter ticks (1.967 s, 14.83 ticks/byte) against main's 5343577520 over the same 320,864,256 bytes (2.209 s, 16.65 ticks/byte): 0.89x main; testcases-watchdog: 139726606 against main's 166696118, 0.84x. One boot each; the ratio is below 1.00, so no second main boot is owed by the review's rule.

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #812 at head b9132ebb7 (unchanged since round 2), against .claude/agents/reviewer.md. This is round 3.

Earlier BLOCKERs

  • bootloader/src/slot.rs:149-150, the T14 reading: CLOSED. I read the orchestrator's readback, and each of round 2's conditions holds.
    • The image is the one requested. t14-sha2-r2.log reads testcases boot rc=0 sha=b792b6d71c9d4a3077db16ed146ef5e309d3203fd6bd8a62b10b3b2937eb012d. That is request.txt's sha256, and the request names wt/toyos-sha2 at b9132ebb7. The run was toyos-metal --image … --fat32-check.
    • The loader's verification passed. testcases/loader.log has Slot A: signed header bc472d6e… verifies under this loader's key and Slot A: kernel, cmdline and ROOT are the bytes the signed header names. The judge passed: t14-sha2-r2.log reads judge EXIT=0 [metal] 249 passed, 0 failed, 2 boot(s), and both verdict.txt files read passed.
    • The same log gives the ROOT hash time and the bytes it covers. ROOT: read into memory at 0x3b970000+0x13200000 is 320,864,256 bytes, and Slot A: ROOT hashed in 4757794201 counter ticks follows at the stated 2419200000 Hz. The line timestamps run from 20.068 to 22.051, which agrees.
    • Against main: 4,757,794,201 / 320,864,256 = 14.83 ticks per byte, and main's 5,343,577,520 over the same byte count is 16.65. The ratio is 0.89. It is below 1.00, so no second main boot is owed.
    • The title's speed claim and the issue's "at most 2.2 s" bound therefore stand on x86-64 too.

Main has moved to 198a9d38e. The new commits are #804, which touches src/metal.rs and tests/, and none of it is a sha2 caller. git merge-tree against this head is clean. crate::release::sha256_hex, which main's src/sdkversion.rs calls, still exists at src/release.rs:85 on this head.

Net lines (git diff --shortstat origin/main...b9132ebb7): 29 files, +2785/−36. Production is toyos-sha2/src/lib.rs at +255, plus about 50 lines of callers and manifests. Tests are +225 and NIST data +2190. All of this is unchanged since round 2 and accepted there.

NOTE

LAND

@Japabu
Japabu marked this pull request as ready for review October 9, 2026 18:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant