declares, and a scoped fill leaves the rest behind. A 32 KiB fill costs ~0.1 µs
— roughly 0.4% of one signature — so this is never worth optimising away.
+**Internal scratch holds key material too, and in shapes a scan will miss.**
+The gotcha above is about the *input* buffers of exported functions. The harder
+case is module-level scratch on a secret path: `ge_scalarmult_base` expands its
+scalar into 64 signed radix-16 digits in `ge_scalarmult_base_e`, and
+`ge_cmov8_base` copies the selected base-table entry into `minust`. Neither was
+cleared, so `derive` left the clamped secret scalar behind and `sign` left the
+nonce `r` — enough, with the signature, to recover the key and forge. Both are
+scrubbed now; the rule is that **any** static a secret passes through gets
+cleared before the function returns, not just the ones the ABI exposes.
+
+The reason it survived so long is worth more than the fix: the residue scan
+searched for the 32-byte little-endian encodings and truthfully reported "0
+copies" the whole time. A scan finds a *representation*, not a secret. When you
+put a secret into a new buffer, enumerate the form it actually takes there —
+digit expansion, limb array, precomp entry, negated copy — and add that form to
+the scan in the same commit.
+
**Establish fail-closed defaults before the first early exit.** `verify_blocks`
-runs `OUTPUT_VERIFY.fill(255)` *before* validating the public key, so a batch
+runs `OUTPUT_VERIFY.fill(0)` *before* validating the public key, so a batch
that bails out reports every block as `false` rather than leaving stale bytes.
Setting the default in the failure branch instead is the bug this shape avoids:
a result buffer is only fail-closed if the default is written before anything