Skip to content

feat(zcash_keys): erase UnifiedSpendingKey secret material on drop - #633

Draft
ValarDragon wants to merge 2 commits into
mainfrom
claude/intelligent-davinci-7yby8f
Draft

ValarDragon wants to merge 2 commits into
mainfrom
claude/intelligent-davinci-7yby8f

Conversation

@ValarDragon

Copy link
Copy Markdown
Contributor

Summary

Adds a default-on zeroize feature to zakura-keys. With it, UnifiedSpendingKey overwrites its Sapling extended spending key and its Orchard spending key with zeros when dropped.

UnifiedSpendingKey::to_bytes (behind unstable) now returns secrecy::SecretVec<u8>. It reserves the full encoded length up front, so the buffer never reallocates and leaves freed copies behind. It also erases the intermediate Sapling and transparent encodings it builds.

To make that possible, the key types underneath gain zeroize::Zeroize impls behind new default-off features:

Crate Feature Adds
zakura-orchard zeroize Zeroize for keys::SpendingKey
zakura-sapling-crypto zeroize Zeroize for SpendAuthorizingKey, ExpandedSpendingKey, OutgoingViewingKey, zip32::DiversifierKey, zip32::ExtendedSpendingKey
zakura-redjubjub zeroize Zeroize for SigningKey
zakura-reddsa zeroize-signing-key Zeroize for SigningKey; erases the secret scalar and keeps the verification key
zakura-jubjub zeroize DefaultIsZeroes for Fr
zakura-pasta-curves zeroize DefaultIsZeroes for Fp and Fq

reddsa's existing zeroize feature is an inert compatibility feature that std turns on. The working impl therefore takes a separate name: reusing zeroize would switch it on in every default build, and removing zeroize from std is a semver break (feature_not_enabled_by_default).

Orchard SpendingKey's Debug output is now redacted (SpendingKey { .. }). It previously printed the key bytes.

Compatibility

  • No breaking changes. Everything is additive. The key types keep Copy, and only UnifiedSpendingKey, which is not Copy, erases on drop. Callers who copy a key out of it erase their own copy. check_semver.py --default-features reports "no semver update required" for all seven crates.
  • Zakura is unaffected. It doesn't depend on zakura-keys and enables none of the new features. I resolved Zakura against this branch and against main through [patch.crates-io]. Both give the same 348 packages, the same enabled features (normal, build and dev edges), and the same zeroize reverse dependencies.

Not covered

  • The Sapling ZIP 32 chain code. zip32 0.2 has no way to overwrite a ChainCode. zip32 0.3 adds one, but moving to it is a breaking bump shared with the wallet libraries, so it's left for a follow-up.
  • The transparent spending key, and secret intermediates produced while deriving keys.

Tests

  • New unit tests check each Zeroize impl: Fr, Fp and Fq become zero; SigningKey scalars become zero while the verification key is unchanged; Orchard SpendingKey bytes become zero; Sapling ExtendedSpendingKey bytes after the chain code become zero, while the chain code survives.
  • A test checks that Orchard SpendingKey's Debug output is redacted.
  • prop_usk_roundtrip now reads to_bytes through expose_secret, and its Sapling-only parts are gated on sapling.
  • Passing: zakura-keys tests with and without zeroize; the jubjub, reddsa, redjubjub and sapling-crypto suites with the features on; the orchard keys and pasta_curves fields tests; cargo check --workspace --all-targets; the CI librustzcash all-supported-features check; cargo fmt --check. Clippy produces no diagnostics beyond those already on main.

Generated by Claude Code

Add a default-on `zeroize` feature to zakura-keys. With it,
`UnifiedSpendingKey` overwrites its Sapling extended spending key and
its Orchard spending key with zeros when dropped. `to_bytes` (behind
`unstable`) now returns `secrecy::SecretVec<u8>`, reserves the full
length up front so growth never leaves freed copies, and erases the
intermediate Sapling and transparent encodings it builds.

The key types underneath gain `zeroize::Zeroize` impls behind new
default-off features:

- zakura-orchard `zeroize`: `keys::SpendingKey`.
- zakura-sapling-crypto `zeroize`: `SpendAuthorizingKey`,
  `ExpandedSpendingKey`, `OutgoingViewingKey`, `zip32::DiversifierKey`,
  and `zip32::ExtendedSpendingKey`.
- zakura-redjubjub `zeroize` and zakura-reddsa `zeroize-signing-key`:
  `SigningKey`, erasing the secret scalar and keeping the public key.
  reddsa's existing `zeroize` feature is inert and enabled by `std`, so
  the working impl takes a separate name to stay out of default builds.
- zakura-jubjub and zakura-pasta-curves `zeroize`:
  `DefaultIsZeroes` for `Fr`, `Fp`, and `Fq`.

Every change is additive. The key types keep `Copy`, and only
`UnifiedSpendingKey`, which is not `Copy`, erases on drop. Callers who
copy a key out erase their own copy. The orchard `SpendingKey` `Debug`
output is now redacted.

Not covered: the Sapling ZIP 32 chain code, since zip32 0.2 cannot
overwrite a `ChainCode`, and the transparent spending key.

Zakura enables none of these features. Resolving Zakura against this
branch through `[patch.crates-io]` gives the same packages, enabled
features, and `zeroize` reverse dependencies as against main.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PvjDwWmc4La5L9yLQKBmUE

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants