diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a8bdc9..a07f4f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -237,3 +237,18 @@ jobs: # by hand at each call site. - name: Workspace dependency versions track the workspace version run: bash scripts/check-workspace-dep-versions.sh + + # The vendored branded icon is byte-pinned; see scripts/check-icon.sh for why + # drift is made loud rather than impossible. Cheap and dependency-free, so it + # runs as its own job and reports independently of the compile gates. + icon: + name: icon asset (sha256 pin) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + # Invoked through `bash` rather than relying on the file mode: a checkout + # that loses the executable bit would otherwise fail this gate for a + # reason that has nothing to do with the icon. + - run: bash scripts/check-icon.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e6f308..18f8912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project are documented here. This project adheres to [Semantic Versioning](https://semver.org) and [Conventional Commits](https://www.conventionalcommits.org). +## [Unreleased] + +### Features +- **icon:** Embed the canonical branded DIG icon in `digs`/`dig-store`, `dig-resolver` and `dighost` on Windows, gated on a vendored sha256 pin (#2917) + ## [0.29.6] - 2026-09-06 ### Bug Fixes diff --git a/Cargo.lock b/Cargo.lock index f966ffd..f1aa3d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2550,6 +2550,7 @@ version = "0.1.0" dependencies = [ "chia-protocol", "digstore-chain", + "embed-resource", "hex", "tokio", ] @@ -2665,7 +2666,7 @@ dependencies = [ [[package]] name = "digstore-chain" -version = "0.29.9" +version = "0.29.10" dependencies = [ "aes-gcm", "anyhow", @@ -2707,7 +2708,7 @@ dependencies = [ [[package]] name = "digstore-chunker" -version = "0.29.9" +version = "0.29.10" dependencies = [ "digstore-core", "hex", @@ -2717,7 +2718,7 @@ dependencies = [ [[package]] name = "digstore-cli" -version = "0.29.9" +version = "0.29.10" dependencies = [ "anstream 0.6.21", "anstyle", @@ -2743,6 +2744,7 @@ dependencies = [ "digstore-stage", "digstore-store", "dirs", + "embed-resource", "flate2", "getrandom 0.2.17", "globset", @@ -2786,7 +2788,7 @@ dependencies = [ [[package]] name = "digstore-core" -version = "0.29.9" +version = "0.29.10" dependencies = [ "aes-gcm-siv", "hex", @@ -2798,7 +2800,7 @@ dependencies = [ [[package]] name = "digstore-crypto" -version = "0.29.9" +version = "0.29.10" dependencies = [ "chia-bls 0.36.1", "digstore-core", @@ -2832,7 +2834,7 @@ dependencies = [ [[package]] name = "digstore-host" -version = "0.29.9" +version = "0.29.10" dependencies = [ "anyhow", "clap", @@ -2840,6 +2842,7 @@ dependencies = [ "digstore-core", "digstore-crypto", "digstore-prover", + "embed-resource", "futures", "hex", "httpmock", @@ -2857,7 +2860,7 @@ dependencies = [ [[package]] name = "digstore-prover" -version = "0.29.9" +version = "0.29.10" dependencies = [ "bincode 1.3.3", "digstore-core", @@ -2873,7 +2876,7 @@ dependencies = [ [[package]] name = "digstore-remote" -version = "0.29.9" +version = "0.29.10" dependencies = [ "async-trait", "axum", @@ -2902,7 +2905,7 @@ dependencies = [ [[package]] name = "digstore-stage" -version = "0.29.9" +version = "0.29.10" dependencies = [ "digstore-chunker", "digstore-compiler", @@ -2918,7 +2921,7 @@ dependencies = [ [[package]] name = "digstore-store" -version = "0.29.9" +version = "0.29.10" dependencies = [ "digstore-chunker", "digstore-core", @@ -2933,7 +2936,7 @@ dependencies = [ [[package]] name = "digstore-subscription" -version = "0.29.9" +version = "0.29.10" dependencies = [ "async-trait", "digstore-core", @@ -3082,6 +3085,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.2+spec-1.1.0", + "vswhom", + "winreg", +] + [[package]] name = "embedded-io" version = "0.4.0" @@ -7022,6 +7039,21 @@ dependencies = [ "winnow 0.7.15", ] +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.3", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -7407,6 +7439,26 @@ version = "0.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "wait-timeout" version = "0.2.1" @@ -8180,6 +8232,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + [[package]] name = "wit-bindgen" version = "0.57.1" diff --git a/Cargo.toml b/Cargo.toml index eb1f3c1..4fe4bbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ exclude = ["crates/digstore-prover/guest", "crates/dig-client-wasm"] [workspace.package] edition = "2021" -version = "0.29.9" +version = "0.29.10" license = "GPL-2.0-only" [workspace.dependencies] @@ -28,17 +28,17 @@ license = "GPL-2.0-only" # The version MUST equal `[workspace.package].version`, because every member inherits # that value. `scripts/check-workspace-dep-versions.sh` enforces it in CI, so a release # bump can never silently leave a published crate pointing at the previous version. -digstore-core = { path = "crates/digstore-core", version = "0.29.9" } -digstore-chain = { path = "crates/digstore-chain", version = "0.29.9" } -digstore-chunker = { path = "crates/digstore-chunker", version = "0.29.9" } -digstore-crypto = { path = "crates/digstore-crypto", version = "0.29.9" } -digstore-store = { path = "crates/digstore-store", version = "0.29.9" } -digstore-prover = { path = "crates/digstore-prover", version = "0.29.9" } -digstore-host = { path = "crates/digstore-host", version = "0.29.9" } -digstore-stage = { path = "crates/digstore-stage", version = "0.29.9" } -digstore-remote = { path = "crates/digstore-remote", version = "0.29.9" } -digstore-cli = { path = "crates/digstore-cli", version = "0.29.9" } -digstore-subscription = { path = "crates/digstore-subscription", version = "0.29.9" } +digstore-core = { path = "crates/digstore-core", version = "0.29.10" } +digstore-chain = { path = "crates/digstore-chain", version = "0.29.10" } +digstore-chunker = { path = "crates/digstore-chunker", version = "0.29.10" } +digstore-crypto = { path = "crates/digstore-crypto", version = "0.29.10" } +digstore-store = { path = "crates/digstore-store", version = "0.29.10" } +digstore-prover = { path = "crates/digstore-prover", version = "0.29.10" } +digstore-host = { path = "crates/digstore-host", version = "0.29.10" } +digstore-stage = { path = "crates/digstore-stage", version = "0.29.10" } +digstore-remote = { path = "crates/digstore-remote", version = "0.29.10" } +digstore-cli = { path = "crates/digstore-cli", version = "0.29.10" } +digstore-subscription = { path = "crates/digstore-subscription", version = "0.29.10" } sha2 = "0.10" proptest = "1" diff --git a/assets/dig.ico b/assets/dig.ico new file mode 100644 index 0000000..e10b838 Binary files /dev/null and b/assets/dig.ico differ diff --git a/assets/dig.rc b/assets/dig.rc new file mode 100644 index 0000000..c074d38 --- /dev/null +++ b/assets/dig.rc @@ -0,0 +1,18 @@ +// Windows resource script for digs (dig-store / digs, dig-resolver, dighost). +// +// Windows shows the LOWEST-ORDINAL icon resource as an executable's icon in +// Explorer, the taskbar and the Alt-Tab switcher, so the branded DIG mark must +// be ordinal 1 and must stay the only ICON statement in this file. +// +// `dig.ico` is the canonical, byte-pinned DIG icon (10 frames: 16/20/24/32/40/ +// 48/64/96/128/256, with hardened alpha at <=32 so the D's counter does not +// bleed shut at taskbar size). It is vendored verbatim and gated on its sha256 +// by `scripts/check-icon.sh` — never re-save or "optimize" it, as any rewrite +// changes the hash and breaks the gate in this repo and in every sibling repo +// that vendors the same bytes. +// +// Deliberately NOT declared here: an RT_MANIFEST resource. None of this repo's +// branded binaries embed a manifest, and declaring one in the .rc would collide +// with a manifest embedded elsewhere via the linker (CVT1100/LNK1123). + +1 ICON "dig.ico" diff --git a/crates/dig-resolver/Cargo.toml b/crates/dig-resolver/Cargo.toml index 9bfaac4..4b695e5 100644 --- a/crates/dig-resolver/Cargo.toml +++ b/crates/dig-resolver/Cargo.toml @@ -9,6 +9,9 @@ description = "Resolve a DIG store's current (tip) root from coinset.org by walk name = "dig-resolver" path = "src/main.rs" +[target.'cfg(windows)'.build-dependencies] +embed-resource = "3" + [dependencies] digstore-chain = { workspace = true } chia-protocol = "0.36.1" diff --git a/crates/dig-resolver/build.rs b/crates/dig-resolver/build.rs new file mode 100644 index 0000000..f328004 --- /dev/null +++ b/crates/dig-resolver/build.rs @@ -0,0 +1,26 @@ +//! Build script: compile the branded DIG icon into `dig-resolver`. +//! +//! `embed_resource::compile` links the compiled `.res` with +//! `cargo:rustc-link-arg-bins`, which reaches every bin this crate ships — +//! here, just `dig-resolver` — so no `compile_for` scoping is needed. +//! +//! The result is checked rather than discarded: an environment that cannot +//! compile a resource would otherwise silently produce an unbranded binary, +//! which is precisely the failure this build step exists to prevent. +//! +//! No-op on non-Windows. + +fn main() { + #[cfg(windows)] + embed_icon(); +} + +#[cfg(windows)] +fn embed_icon() { + embed_resource::compile("../../assets/dig.rc", embed_resource::NONE) + .manifest_required() + .expect("failed to compile assets/dig.rc — no usable Windows resource compiler?"); + + println!("cargo:rerun-if-changed=../../assets/dig.rc"); + println!("cargo:rerun-if-changed=../../assets/dig.ico"); +} diff --git a/crates/digstore-cli/Cargo.toml b/crates/digstore-cli/Cargo.toml index f43ec3c..ae838c5 100644 --- a/crates/digstore-cli/Cargo.toml +++ b/crates/digstore-cli/Cargo.toml @@ -24,6 +24,9 @@ path = "src/bin/digs.rs" name = "digstore_cli" path = "src/lib.rs" +[target.'cfg(windows)'.build-dependencies] +embed-resource = "3" + [dependencies] digstore-core = { workspace = true } digstore-chain = { workspace = true } diff --git a/crates/digstore-cli/build.rs b/crates/digstore-cli/build.rs new file mode 100644 index 0000000..71903a1 --- /dev/null +++ b/crates/digstore-cli/build.rs @@ -0,0 +1,28 @@ +//! Build script: compile the branded DIG icon into `dig-store` and `digs`. +//! +//! `embed_resource::compile` links the compiled `.res` with +//! `cargo:rustc-link-arg-bins`, so it reaches every `[[bin]]` this crate ships +//! (`dig-store` AND its `digs` alias) without needing `compile_for`. This crate +//! also has a `digstore_cli` lib target, but `-bins` link args never touch a +//! lib, so the library build is unaffected. +//! +//! The result is checked rather than discarded: an environment that cannot +//! compile a resource would otherwise silently produce an unbranded binary, +//! which is precisely the failure this build step exists to prevent. +//! +//! No-op on non-Windows. + +fn main() { + #[cfg(windows)] + embed_icon(); +} + +#[cfg(windows)] +fn embed_icon() { + embed_resource::compile("../../assets/dig.rc", embed_resource::NONE) + .manifest_required() + .expect("failed to compile assets/dig.rc — no usable Windows resource compiler?"); + + println!("cargo:rerun-if-changed=../../assets/dig.rc"); + println!("cargo:rerun-if-changed=../../assets/dig.ico"); +} diff --git a/crates/digstore-host/Cargo.toml b/crates/digstore-host/Cargo.toml index 48729af..f1b04e7 100644 --- a/crates/digstore-host/Cargo.toml +++ b/crates/digstore-host/Cargo.toml @@ -5,6 +5,9 @@ version.workspace = true edition = "2021" description = "wasmtime runtime for serving compiled Digstore WASM modules (paper 6.3, 6.4, 18)." +[target.'cfg(windows)'.build-dependencies] +embed-resource = "3" + [dependencies] digstore-core = { workspace = true } digstore-crypto = { workspace = true } diff --git a/crates/digstore-host/build.rs b/crates/digstore-host/build.rs new file mode 100644 index 0000000..2abb852 --- /dev/null +++ b/crates/digstore-host/build.rs @@ -0,0 +1,28 @@ +//! Build script: compile the branded DIG icon into `dighost`. +//! +//! `embed_resource::compile` links the compiled `.res` with +//! `cargo:rustc-link-arg-bins`, which reaches every bin this crate ships — +//! here, just `dighost` — so no `compile_for` scoping is needed. This crate's +//! `digstore_host` lib target is unaffected: `-bins` link args never touch a +//! lib. +//! +//! The result is checked rather than discarded: an environment that cannot +//! compile a resource would otherwise silently produce an unbranded binary, +//! which is precisely the failure this build step exists to prevent. +//! +//! No-op on non-Windows. + +fn main() { + #[cfg(windows)] + embed_icon(); +} + +#[cfg(windows)] +fn embed_icon() { + embed_resource::compile("../../assets/dig.rc", embed_resource::NONE) + .manifest_required() + .expect("failed to compile assets/dig.rc — no usable Windows resource compiler?"); + + println!("cargo:rerun-if-changed=../../assets/dig.rc"); + println!("cargo:rerun-if-changed=../../assets/dig.ico"); +} diff --git a/scripts/check-icon.sh b/scripts/check-icon.sh new file mode 100644 index 0000000..ae09664 --- /dev/null +++ b/scripts/check-icon.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Assert the vendored DIG application icon is the canonical, byte-identical one. +# +# `assets/dig.ico` is deliberately vendored rather than pulled from a shared +# package: it changes approximately never, and a vendored copy costs no +# release-first cascade. The trade is that drift becomes possible, so this +# check makes drift LOUD — any re-save, re-export or "optimization" changes the +# hash and fails the build here instead of shipping a subtly different mark. +# +# The same literal is pinned in every DIG repo that vendors these bytes, so a +# mismatch means either this repo drifted or the icon was regenerated and every +# sibling repo must be updated in the same unit of work. +# +# Run from the repo root: scripts/check-icon.sh +set -euo pipefail + +readonly ICON="assets/dig.ico" +readonly EXPECTED_SHA256="2f0fb11a1254fc9275248dc340b7aa9c7236484a9531f8aaad2e4bcdf8900096" + +if [[ ! -f "$ICON" ]]; then + echo "FAIL: $ICON is missing." >&2 + exit 1 +fi + +actual="$(sha256sum "$ICON" | cut -d' ' -f1)" + +if [[ "$actual" != "$EXPECTED_SHA256" ]]; then + echo "FAIL: $ICON has drifted from the canonical DIG icon." >&2 + echo " expected sha256: $EXPECTED_SHA256" >&2 + echo " actual sha256: $actual" >&2 + echo "Restore the canonical bytes; do not re-save or re-export the icon." >&2 + exit 1 +fi + +echo "OK: $ICON matches the canonical DIG icon ($EXPECTED_SHA256)."