What actually triggers "Windows protected your PC" on a signed C# app (and how to check before release)
▲ 11 r/csharp

What actually triggers "Windows protected your PC" on a signed C# app (and how to check before release)

I spent a while digging into why signed desktop apps still hit SmartScreen, and a few things surprised me:

  • SmartScreen only checks files that carry Mark-of-the-Web. Anything downloaded through a browser gets a Zone.Identifier stream (ZoneId=3). Run the same installer from a build folder and you'll never see the warning, which is why devs rarely catch it themselves.
  • Reputation is per file hash and per signing certificate. Every new release is a new hash. Renewing the certificate can reset you too.
  • A valid signature isn't the question SmartScreen asks. It asks whether Microsoft has seen this exact file from enough machines without incident. And since 2024 an EV certificate no longer buys that reputation instantly.
  • Signing hygiene still matters: no timestamp means the signature stops validating when the cert expires; SHA-1 signatures are treated close to unsigned.

To see what a first-time user sees, I ended up building a probe that downloads the installer onto a clean Windows 11 VM with Mark-of-the-Web and runs it: https://signalscreen.dev/probe (free while in beta). The homepage has a free signing check too; the grader is open source.

Curious what others have run into: have you seen a release get flagged right after a cert renewal?

u/mnaza_andrey — 13 days ago

Measured flash, RAM and time budgets for post-quantum signature verification in no_std Rust

I could not find published numbers for what post-quantum signature verification costs on small targets, so I measured it. Four crates, LMS/HSS verification plus measurement harnesses.

Three Rust-specific things came out of it.

First one. sha2 0.10 SHA-256 compiles to 3808 bytes on Cortex-M4F. sha2 0.11 compiles to 8776. Same algorithm, same target, same flags. Both versions end up linked in my workspace because different dependencies require different ones.

This means comparing whole verifier binaries mostly compares hash crates, not signature schemes. Ed25519 binary is around 64% unrolled SHA-512. So now every scheme is subtracted from baseline built with same hash it uses.

Second one. Adding second call site to small helper made LLVM stop inlining it at opt-level = "z". Reasonable decision. But forcing #\[inline(always)\] saves 408 bytes on Cortex-M0+ and costs 48 bytes on M4F and RISC-V. Different direction on different targets, so I keep the attribute because 16 KB boot ROM cares and 64 KB one does not.

Third one. Static stack analysis from -Z emit-stack-sizes plus call graph from disassembly does not give upper bound. On one binary 55 call sites cannot be resolved: indirect calls, tail calls compiled as branches, linker thunks. Every missing edge only makes answer smaller. Tool said 720 bytes, hardware said 1152.

Crates are no\_std and allocation-free. Hash is behind trait, so software sha2 and ESP32 SHA peripheral both work. That trait design changed twice, and [DECISIONS.md](http://DECISIONS.md) has why.

Not audited. lms-verify passes RFC 8554 Appendix F vectors, which is not same thing.

[https://github.com/mnaza/pqc-embedded\](https://github.com/mnaza/pqc-embedded)

reddit.com
u/mnaza_andrey — 2 months ago
▲ 2 r/rust

Measured flash, RAM and time budgets for post-quantum signature verification in no_std Rust

I could not find published numbers for what post-quantum signature verification costs on small targets, so I measured it. Four crates, LMS/HSS verification plus measurement harnesses.

Three Rust-specific things came out of it.

First one. sha2 0.10 SHA-256 compiles to 3808 bytes on Cortex-M4F. sha2 0.11 compiles to 8776. Same algorithm, same target, same flags. Both versions end up linked in my workspace because different dependencies require different ones.

This means comparing whole verifier binaries mostly compares hash crates, not signature schemes. Ed25519 binary is around 64% unrolled SHA-512. So now every scheme is subtracted from baseline built with same hash it uses.

Second one. Adding second call site to small helper made LLVM stop inlining it at opt-level = "z". Reasonable decision. But forcing #[inline(always)] saves 408 bytes on Cortex-M0+ and costs 48 bytes on M4F and RISC-V. Different direction on different targets, so I keep the attribute because 16 KB boot ROM cares and 64 KB one does not.

Third one. Static stack analysis from -Z emit-stack-sizes plus call graph from disassembly does not give upper bound. On one binary 55 call sites cannot be resolved: indirect calls, tail calls compiled as branches, linker thunks. Every missing edge only makes answer smaller. Tool said 720 bytes, hardware said 1152.

Crates are no_std and allocation-free. Hash is behind trait, so software sha2 and ESP32 SHA peripheral both work. That trait design changed twice, and DECISIONS.md has why.

Not audited. lms-verify passes RFC 8554 Appendix F vectors, which is not same thing.

https://github.com/mnaza/pqc-embedded

reddit.com
u/mnaza_andrey — 2 months ago
▲ 0 r/embedded+1 crossposts

Built a Wi-Fi remote for my AC on an ESP32 in Rust — all the logic lives in a dependency-free, host-tested core crate

My apartment's AC remote is always missing, so I built a replacement on an M5StickC Plus2 (ESP32-PICO with a screen, battery and IR LED) using esp-idf-svc.

The part I'm happiest with is the split. All the pure logic — IR frame encoding, the weekly-schedule math, Home Assistant MQTT payloads, a LiPo battery model, form parsing — lives in a no_std-friendly, dependency-free ac-core crate with ordinary host unit tests. The firmware crate is a thin hardware layer on top (Wi-Fi, the RMT peripheral for IR, display, storage). So the tricky, easy-to-get-wrong bits are testable with cargo test on my laptop, no hardware in the loop, and the ESP-specific code stays small.

The IR was the interesting problem. An AC remote sends a full state frame on every press — mode, temp, fan, swing, power in a fixed bit layout with a checksum, not deltas — so I re-implemented the ELECTRA_AC / COOLIX / GREE framings from scratch and drove them over the ESP32's RMT peripheral, timing the pulses directly. I validated my frames bit-for-bit against IRremoteESP8266 and everything matched except the power-off frame for my unit's variant (Baxi/AUX), which uses a different byte than the library's default — not a library bug, just a variant its defaults don't cover, which I confirmed by capturing what the physical remote actually sends.

Also in there: Home Assistant MQTT discovery (shows up as a climate entity), a bilingual web UI, OTA over A/B partitions, and some power tuning (modem sleep, CPU scaling) to survive on the small battery.

Embedded Rust was genuinely a good fit — the type system earned its place around the frame encoding and the RMT timing, and host-testing the core caught the protocol mismatch fast. It's MIT: github.com/mnaza/condition-control

Happy to talk about the crate split or the IR reverse-engineering — and I'd welcome pointers from anyone who's wrangled esp-idf-svc or IRremoteESP8266 variants.

u/mnaza_andrey — 3 months ago