Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ members = [
"toyos-random",
"toyos-rootimage",
"toyos-sha2",
"toyos-sha2-hw",
"toyos-smmu",
"toyos-ssh",
"toyos-swap",
Expand Down Expand Up @@ -213,7 +214,7 @@ image = { version = "0.25", default-features = false, features = ["jpeg"] }
# Every SHA-256 the build takes: `NOTICE`'s record of a committed file, a
# store key, a ROOT's name, a release asset's digest, and the harness's of a
# body a guest fetches over TLS.
toyos-sha2 = { path = "toyos-sha2" }
toyos-sha2-hw = { path = "toyos-sha2-hw" }
# The toolchain release's tarball, packed in-process (`src/release.rs`): Rust's
# own tar and gzip, where the binaries are hosts' tools.
tar = { version = "0.4.46", default-features = false }
Expand Down
8 changes: 4 additions & 4 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -451,10 +451,10 @@ tests/cavp/ — NIST's SHA-2 test vectors, CAVP SHAVS

The response files NIST's Cryptographic Algorithm Validation Program publishes
for the SHA Validation System: messages and the digests FIPS 180-4 gives them,
the external oracle `toyos-sha2`'s tests hold it to. A work of the United States
government, not under copyright in the United States (17 U.S.C. §105). Test
input only: nothing built from this repository carries them, and no shipped
package's directory holds them.
the external oracle `toyos-sha2`'s and `toyos-sha2-hw`'s tests hold them to. A
work of the United States government, not under copyright in the United States
(17 U.S.C. §105). Test input only: nothing built from this repository carries
them, and no shipped package's directory holds them.

Of the archive, only SHA-256's and SHA-512's files, the two hashes the crate
has.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
status: open
kind: tooling
opened: 2026-10-09
---

# No T14 reading of `update`'s ROOT hash before and after SHA-NI

`update`'s streamed ROOT hash (`userland/update/src/main.rs`, `stream_root`)
moved from `toyos-sha2`'s scalar compression to `toyos-sha2-hw`'s SHA-NI one,
and no machine has timed it on either. `update` reports no hash time, and no
metal row runs `update`.

The loader's ROOT hash, the same function on the same CPU, is not this
reading: it runs on a soft-float UEFI target before ExitBootServices, and
`update` runs in userland under the ToyOS kernel, streaming a ROOT it also
writes.

Exit: the T14's reading of `update`'s ROOT hash on one image, with
`toyos-sha2`'s scalar compression and with `toyos-sha2-hw`'s.
2 changes: 1 addition & 1 deletion issues/the-build-runs-host-tools-outside-rust-and-qemu.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ arrives and is not one. M4 and M5 are stages of `issues/toyos-builds-itself.md`.
| `cc`, `c++`, `ar` and `xcrun` on a macOS host, Apple's Command Line Tools | what the Linux row's tools do, and rustc asks `xcrun` for the SDK on every host link that names no `SDKROOT` (`rust/compiler/rustc_codegen_ssa/src/back/apple.rs`), as `src/llvm.rs` does for the LLVM's key | refused: one host OS alone | M5: no host in the loop |
| `diag/flash.sh` | the owner's flash of a stick by hand: `bash`, and the `stat`, `seq`, `tr`, `grep`, `cut` and `sync` it strings together | refused: shell of our own | `issues/the-owners-flash-script-runs-diskutil.md` |
| `diskutil` and `plutil` | `diag/flash.sh`, and `diskutil` in the README's flashing steps | refused: one host OS alone | `issues/the-owners-flash-script-runs-diskutil.md` |
| `shasum`, a Perl script on macOS | `diag/flash.sh` hashes the image with it | refused: a Rust tool does it, `toyos-sha2`, which `src/release.rs` hashes with | `issues/the-owners-flash-script-runs-diskutil.md` |
| `shasum`, a Perl script on macOS | `diag/flash.sh` hashes the image with it | refused: a Rust tool does it, `toyos-sha2-hw`, which `src/release.rs` hashes with | `issues/the-owners-flash-script-runs-diskutil.md` |
| `lsblk` | the README's Linux flashing steps find the stick with it | refused: one host OS alone | `issues/the-owners-flash-script-runs-diskutil.md` |
| `dd` | `diag/flash.sh` and the README's flashing steps write the stick with it | refused: a Rust tool does it, the build system can write the image itself | `issues/the-owners-flash-script-runs-diskutil.md` |
| `sync` | the README's Linux flashing steps flush the stick with it | refused: a Rust tool does it, the build system can flush what it writes | `issues/the-owners-flash-script-runs-diskutil.md` |
Expand Down

This file was deleted.

7 changes: 3 additions & 4 deletions src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use std::path::Path;
use bcachefs::{BlockBuf, BlockIO, BlockNum, Formatted, FsUuid, Superblock, VecBlockIO};

use crate::arch::Arch;
use toyos_sha2::Sha256;
use toyos_fat32::{BlockAccess, Fat32, FatTime, IoError};

/// The image that goes on the ROOT partition, named by a UUID **derived, never
Expand Down Expand Up @@ -135,7 +134,7 @@ fn format_root(
/// the bytes, so no two entries can run together into an input a different
/// split would also produce.
fn root_uuid(files: &[&(String, Vec<u8>)], symlinks: &[&(String, String)]) -> FsUuid {
let mut hasher = Sha256::new();
let mut hasher = toyos_sha2_hw::sha256();
let mut field = |bytes: &[u8]| {
hasher.update((bytes.len() as u64).to_le_bytes());
hasher.update(bytes);
Expand Down Expand Up @@ -1412,8 +1411,8 @@ mod tests {
for (name, data) in &files {
let read = fs.read_file(name).unwrap_or_else(|e| panic!("read {name}: {e:?}"));
assert_eq!(
Sha256::digest(&read),
Sha256::digest(data),
toyos_sha2_hw::sha256_digest(&read),
toyos_sha2_hw::sha256_digest(data),
"{name} reads back as {} bytes that are not the {} it was given",
read.len(),
data.len()
Expand Down
3 changes: 1 addition & 2 deletions src/release.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ use std::path::{Path, PathBuf};
use std::process::Command;

use serde_json::Value;
use toyos_sha2::Sha256;
use toyos_tmpdir::TempDir;

use crate::buildlock::Keyed;
Expand Down Expand Up @@ -83,7 +82,7 @@ pub(crate) fn named_key(text: &str) -> Option<Key> {
}

pub(crate) fn sha256_hex(bytes: &[u8]) -> String {
Sha256::digest(bytes).iter().map(|b| format!("{b:02x}")).collect()
toyos_sha2_hw::sha256_digest(bytes).iter().map(|b| format!("{b:02x}")).collect()
}

fn on_runner() -> bool {
Expand Down
2 changes: 1 addition & 1 deletion src/sourcegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ fn host_files() -> Vec<PathBuf> {
/// `bytes` as lower-case hex SHA-256, the spelling `NOTICE` records.
#[cfg(test)]
fn digest(bytes: &[u8]) -> String {
toyos_sha2::Sha256::digest(bytes).iter().map(|b| format!("{b:02x}")).collect()
toyos_sha2_hw::sha256_digest(bytes).iter().map(|b| format!("{b:02x}")).collect()
}

/// The shapes of a value that identifies a machine or the network it is on,
Expand Down
6 changes: 2 additions & 4 deletions src/sysroot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

use toyos_sha2::Sha256;

use crate::arch::Arch;
use crate::buildlock::{self, Guard, Held, Keyed};
use crate::compiler::{self, Compiler};
Expand Down Expand Up @@ -246,7 +244,7 @@ fn hex(digest: &[u8]) -> String {

/// The first 16 hex digits of the SHA-256 of `data`.
pub(crate) fn short(data: &[u8]) -> String {
hex(&Sha256::digest(data))[..16].to_string()
hex(&toyos_sha2_hw::sha256_digest(data))[..16].to_string()
}

/// Every file under `dir` a build reads, sorted: no `target/` and no dotted
Expand Down Expand Up @@ -310,7 +308,7 @@ pub(crate) fn tree_identity(base: &Path, paths: &[&str], links: Links) -> String
let mut sources = Vec::new();
source_files(base, paths, links, &mut sources);
sources.sort();
let mut hasher = Sha256::new();
let mut hasher = toyos_sha2_hw::sha256();
for (path, commit) in sources {
hasher.update(path.strip_prefix(base).unwrap_or(&path).to_string_lossy().as_bytes());
hasher.update([0]);
Expand Down
2 changes: 1 addition & 1 deletion tests/toyos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3611,7 +3611,7 @@ fn https_fetch() -> Result<(), String> {
let trusted = Authority::new("ToyOS harness test authority");
let stranger = Authority::new("ToyOS harness authority nothing trusts");
let body = std::sync::Arc::new(https::body());
let hex: String = toyos_sha2::Sha256::digest(body.as_slice()).iter().map(|b| format!("{b:02x}")).collect();
let hex: String = toyos_sha2_hw::sha256_digest(body.as_slice()).iter().map(|b| format!("{b:02x}")).collect();
let want = format!("{JOB}: ok bytes={} sha256={hex}", body.len());
let fetched = Server::start(trusted.leaf(host), body.clone())?;
let wrong_name = Server::start(trusted.leaf([192, 0, 2, 1].into()), body.clone())?;
Expand Down
12 changes: 12 additions & 0 deletions toyos-sha2-hw/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "toyos-sha2-hw"
description = "SHA-256 on the CPU's SHA instructions where CPUID offers them, toyos-sha2's scalar compression where it does not: what every SHA-256 the tree takes hashes with."
version = "0.1.0"
edition = "2024"
license = "MIT OR Apache-2.0"
publish = false

[dependencies]
# The buffering, the padding and the round constants, and the compression
# where the CPU has no instructions for it.
toyos-sha2 = { path = "../toyos-sha2" }
33 changes: 33 additions & 0 deletions toyos-sha2-hw/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! SHA-256 on the CPU's own instructions where it has them: x86-64's SHA
//! extensions, chosen by CPUID as each hash begins. Where the CPU has none, the
//! blocks are `toyos-sha2`'s scalar compression. Both compute FIPS 180-4
//! §6.2.2, so which one ran decides how fast a digest came, never what it is.
//!
//! **AArch64 is scalar.** ToyOS has no AArch64 metal, so no gain from its SHA2
//! instructions could be measured.

#![cfg_attr(not(test), no_std)]

use toyos_sha2::Sha256;

#[cfg(target_arch = "x86_64")]
mod x86_64;

#[cfg(all(test, target_arch = "x86_64"))]
mod tests;

/// A SHA-256 hash on this CPU's fastest compression.
pub fn sha256() -> Sha256 {
#[cfg(target_arch = "x86_64")]
if let Some(compress) = x86_64::compress() {
return Sha256::with(compress);
}
Sha256::new()
}

/// The SHA-256 digest of `bytes`, on this CPU's fastest compression.
pub fn sha256_digest(bytes: impl AsRef<[u8]>) -> [u8; 32] {
let mut hash = sha256();
hash.update(bytes);
hash.finalize()
}
62 changes: 62 additions & 0 deletions toyos-sha2-hw/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//! The instruction path against NIST's CAVP SHA-256 response files and, over
//! every message length to 4096 bytes in random splits, against `toyos-sha2`'s
//! scalar compression, which those files and RustCrypto's `sha2` hold.

use toyos_sha2::Sha256;

#[path = "../../toyos-sha2/src/tests/shavs.rs"]
mod shavs;
use shavs::*;

/// A hash on the instructions, which a host without them cannot test: it says
/// so rather than passing on the scalar path.
fn hashing() -> Sha256 {
let compress = super::x86_64::compress()
.expect("this host's CPU has no SHA extensions, so nothing here can test them");
Sha256::with(compress)
}

fn sha256(msg: &[u8]) -> Vec<u8> {
let mut hash = hashing();
hash.update(msg);
hash.finalize().to_vec()
}

fn sha256_bytewise(msg: &[u8]) -> Vec<u8> {
let mut hash = hashing();
msg.iter().for_each(|b| hash.update([*b]));
hash.finalize().to_vec()
}

#[test]
fn sha256_byte_vectors() {
byte_file("SHA256ShortMsg.rsp", sha256, sha256_bytewise);
byte_file("SHA256LongMsg.rsp", sha256, sha256_bytewise);
}

#[test]
fn sha256_monte_carlo() {
monte_file("SHA256Monte.rsp", sha256);
}

/// **The differential**: every length from empty to 4096 bytes, of random
/// bytes, digested whole and streamed in three random splits each.
#[test]
fn every_length_to_4096_in_random_splits_agrees_with_the_scalar_compression() {
let mut draws = Draws(0x5eed_70e0_5a2b_0002);
for len in 0..=4096usize {
let msg: Vec<u8> = (0..len).map(|_| draws.next() as u8).collect();
let want = Sha256::digest(&msg);
assert_eq!(sha256(&msg), want, "{len} bytes");
for _ in 0..3 {
let cuts = draws.splits(len);
let mut hash = hashing();
let mut at = 0;
for cut in cuts.iter().copied().chain([len]) {
hash.update(&msg[at..cut]);
at = cut;
}
assert_eq!(hash.finalize(), want, "{len} bytes cut at {cuts:?}");
}
}
}
Loading
Loading