diff --git a/CHANGELOG.md b/CHANGELOG.md index d5c0b84f..7a5cc321 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,15 @@ than a number. Both are recorded in every run's `manifest.json`. ## [Unreleased] +### Changed + +- The desktop application builds against `sha2` 0.11. Its `finalize()` returns + `hybrid_array::Array` rather than the old `GenericArray`, which does not implement + `LowerHex`, so the four `format!("{:x}", ..)` sites move to a `components::hex` + helper. The strings are unchanged, and a test pins them against the canonical + SHA-256 vectors, because they are compared with published checksums and used as + cache directory names. + ## [0.2.0] - 2026-09-08 Three code reviews and their fixes (`docs/29_code_review_2026-09-07.md`, diff --git a/desktop/Cargo.lock b/desktop/Cargo.lock index cd300b9f..2d34033b 100644 --- a/desktop/Cargo.lock +++ b/desktop/Cargo.lock @@ -139,6 +139,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block2" version = "0.6.2" @@ -337,6 +346,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "cookie" version = "0.18.2" @@ -396,6 +411,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.1" @@ -430,6 +454,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "cssparser" version = "0.36.0" @@ -581,8 +614,19 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", ] [[package]] @@ -1286,6 +1330,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.11.1" @@ -1850,7 +1903,7 @@ version = "0.2.0" dependencies = [ "serde", "serde_json", - "sha2", + "sha2 0.11.0", "tauri", "tauri-build", "tauri-plugin-dialog", @@ -2883,8 +2936,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -3239,7 +3303,7 @@ dependencies = [ "semver", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "syn 2.0.119", "tauri-utils", "thiserror 2.0.20", @@ -4673,7 +4737,7 @@ dependencies = [ "once_cell", "percent-encoding", "raw-window-handle", - "sha2", + "sha2 0.10.9", "soup3", "tao-macros", "thiserror 2.0.20", diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index 8afcd9c6..48a1753c 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -31,7 +31,7 @@ serde_json = "1" ureq = { version = "3", default-features = false, features = ["rustls"] } # The downloaded bytes are executed, so they are checksummed against a pinned # digest first. -sha2 = "0.10" +sha2 = "0.11" # Unpacking the ThermoRawFileParser release, which is a zip. Pure Rust, and # `deflate` only: shelling out would mean `unzip` on Linux and `tar`/PowerShell on # Windows, neither of which is guaranteed present. diff --git a/desktop/src-tauri/src/components.rs b/desktop/src-tauri/src/components.rs index da524cbe..fd50f616 100644 --- a/desktop/src-tauri/src/components.rs +++ b/desktop/src-tauri/src/components.rs @@ -130,6 +130,22 @@ impl Env { /// Packages whose version is worth reporting, because it changes results. const REPORT_VERSIONS: &[&str] = &["deeplc", "torch", "mokapot", "ms2pip", "numpy"]; +/// Lowercase hex of a digest. +/// +/// `sha2` 0.11 returns `hybrid_array::Array` from `finalize()` rather than the old +/// `GenericArray`, and that type does not implement `LowerHex`, so the `format!("{:x}", +/// ..)` this replaced stopped compiling. Writing the bytes out keeps the string +/// identical, which matters: these digests are compared against published checksums and +/// used as cache directory names, so a changed spelling would reject good downloads and +/// miss every existing cache entry. +pub fn hex(bytes: impl AsRef<[u8]>) -> String { + use std::fmt::Write as _; + bytes.as_ref().iter().fold(String::new(), |mut s, b| { + let _ = write!(s, "{b:02x}"); + s + }) +} + /// Per-user application data, where the managed environment is created. /// /// Not beside the executable: on Windows that is under Program Files, which a @@ -572,6 +588,28 @@ pub fn install(installer: Arc, env: Env) -> Result<(), String> { mod tests { use super::*; + #[test] + fn hex_matches_the_lowerhex_spelling_it_replaced() { + // These strings are compared against published SHA-256 checksums and used as + // cache directory names, so the spelling is a compatibility surface, not a + // detail: a changed one would reject good downloads and miss every existing + // cache entry. `sha2` 0.11 stopped implementing `LowerHex` on its digest type, + // which is why the formatting moved here. + use sha2::{Digest, Sha256}; + assert_eq!( + hex(Sha256::digest(b"")), + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "the empty-input SHA-256, as every other tool prints it" + ); + assert_eq!( + hex(Sha256::digest(b"abc")), + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" + ); + // Zero-padded, lowercase, no separators, and an empty input is an empty string. + assert_eq!(hex([0x00u8, 0x0f, 0xff]), "000fff"); + assert_eq!(hex([]), ""); + } + #[test] fn both_requirement_sets_are_compiled_in_and_look_right() { // Requirement lines only. The comments legitimately discuss MS2PIP at diff --git a/desktop/src-tauri/src/diann.rs b/desktop/src-tauri/src/diann.rs index 1ac44e26..870758a7 100644 --- a/desktop/src-tauri/src/diann.rs +++ b/desktop/src-tauri/src/diann.rs @@ -700,7 +700,7 @@ fn download_verified(installer: &Arc, a: &Asset, dest: &Path) -> Resu std::io::Write::flush(&mut file).map_err(|e| e.to_string())?; drop(file); - let got = format!("{:x}", hasher.finalize()); + let got = crate::components::hex(hasher.finalize()); if got != a.sha256 { // The file is removed rather than left for someone to run by hand. let _ = std::fs::remove_file(dest); @@ -943,7 +943,7 @@ pub fn library_cache_dir(req: &BuildRequest, diann_version: &str) -> Result, a: &Asset, dest: &std::path::Path) -> Re std::io::Write::flush(&mut file).map_err(|e| e.to_string())?; drop(file); - let got = format!("{:x}", hasher.finalize()); + let got = crate::components::hex(hasher.finalize()); if got != a.sha256 { let _ = std::fs::remove_file(dest); return Err(format!(