Skip to content

[PM-22038]: Improve Wallet Seed, Key Pair, and Address Code Quality#1217

Merged
m2ux merged 9 commits into
mainfrom
task/PM-22038-improve-wallet-seed-keypair-code-quality
May 1, 2026
Merged

[PM-22038]: Improve Wallet Seed, Key Pair, and Address Code Quality#1217
m2ux merged 9 commits into
mainfrom
task/PM-22038-improve-wallet-seed-keypair-code-quality

Conversation

@m2ux

@m2ux m2ux commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Harden WalletSeed, KeyPair, and address-related types in the Midnight node ledger by implementing redacted Debug, removing Copy (required for ZeroizeOnDrop), adding Zeroize/ZeroizeOnDrop for automatic secret material cleanup, removing unused Clone from Keypair, replacing index-based panics in add_addresses with zip iteration, and enforcing input-size validation in try_from_lazy_hex — addressing Least Authority audit Suggestion 3 (A2).

🎫 Ticket 📐 Engineering


Motivation

The Least Authority Node DIFF audit identified informational findings in ledger/helpers/src/versions/common/types.rs where cryptographic types derive traits that expose secret material. WalletSeed derives Debug, Clone, Copy, Hash, PartialEq, and Eq, enabling accidental logging of seed bytes, invisible copies via Copy, and non-constant-time comparisons. KeyPair derives Clone, encouraging copies of secret key material. Address generation code uses unchecked indexing that can panic, and try_from_lazy_hex allocates memory from untrusted input before validating size.

Key technical insight: ZeroizeOnDrop generates a Drop impl, and Rust prohibits Copy + Drop. Therefore Copy removal is a technical requirement for zeroize support, not merely a style choice. Hash/PartialEq/Eq must remain because WalletSeed serves as a HashMap key in LedgerContext.


Changes

  • WalletSeed type — Removed Copy; added Zeroize + ZeroizeOnDrop via the zeroize crate; implemented redacted fmt::Debug (prints WalletSeed(Medium(***)) instead of raw bytes)
  • Keypair type — Removed unused Clone derivation (zero callers — pure parsing wrapper)
  • Address generation — Rewrote add_addresses to use zip with &[MaintenanceCounter], eliminating unchecked indexing panics
  • Hex decoding — Added pre-validation of hex string length in try_from_lazy_hex before hex::decode allocation
  • Copy→Clone fixups — ~100 call sites across ledger/helpers and util/toolkit updated to use explicit .clone() where WalletSeed previously relied on implicit Copy (see scope note below)
  • Tests — Added tests for redacted Debug output and hex length validation; all existing tests pass

Note

The Least Authority finding has two related intents: (1) zeroize secret material on drop, and (2) minimize the number of plaintext copies of WalletSeed that coexist in memory. This PR fully addresses (1) — every clone is now independently zeroized via ZeroizeOnDrop — but only partially addresses (2). Removing Copy makes each duplication explicit (~100 .clone() insertions across ledger/helpers and util/toolkit), but the resolution preserves the original duplication count rather than reducing it; the implicit bitwise copies are now explicit owned buffers, all of which get zeroized.

A fuller fix would convert BuildInput / BuildOutput / UtxoOutputInfo / wallet_from_seed and similar APIs to borrow &WalletSeed instead of owning it, so the seed lives in one canonical location for the duration of tx construction. That refactor requires threading lifetimes through trait objects (Box<dyn BuildInput<DefaultDB>>, etc.) and is deferred as follow-up scope.


📌 Submission Checklist

  • Changes are backward-compatible (or flagged if breaking)
  • Pull request description explains why the change is needed
  • Self-reviewed the diff
  • I have included a change file, or skipped for this reason: code-quality improvement, no functional behavior change
  • If the changes introduce a new feature, I have bumped the node minor version
  • Update documentation (if relevant)
  • No new todos introduced

🔱 Fork Strategy

  • Node Runtime Update
  • Node Client Update
  • Other
  • N/A

🗹 TODO before merging

  • Ready for review

@m2ux m2ux marked this pull request as ready for review April 1, 2026 22:48
@m2ux m2ux requested a review from a team as a code owner April 1, 2026 22:48
@m2ux m2ux marked this pull request as draft April 2, 2026 07:31
m2ux added a commit that referenced this pull request Apr 7, 2026
@ozgb ozgb mentioned this pull request Apr 8, 2026
14 tasks
m2ux added a commit that referenced this pull request Apr 17, 2026
- Remove extra blank line causing cargo fmt check failure
- Fix test assertions to match actual Debug output (REDACTED vs ***)

Signed-off-by: Mike <mike@midnight.network>
m2ux added a commit that referenced this pull request Apr 17, 2026
- Remove extra blank line causing cargo fmt check failure
- Fix test assertions to match actual Debug output (REDACTED vs ***)

Signed-off-by: Mike <mike@midnight.network>
Signed-off-by: Mike Clay <mike.clay@shielded.io>
@m2ux m2ux force-pushed the task/PM-22038-improve-wallet-seed-keypair-code-quality branch 3 times, most recently from bf1540b to 5e4ef3e Compare April 23, 2026 08:12
m2ux added a commit that referenced this pull request Apr 23, 2026
Made-with: Cursor

Signed-off-by: Mike Clay <mike.clay@shielded.io>
m2ux added a commit that referenced this pull request Apr 23, 2026
- Remove extra blank line causing cargo fmt check failure
- Fix test assertions to match actual Debug output (REDACTED vs ***)


Signed-off-by: Mike Clay <mike.clay@shielded.io>
@m2ux m2ux force-pushed the task/PM-22038-improve-wallet-seed-keypair-code-quality branch from 5e4ef3e to 403c5d6 Compare April 23, 2026 08:34
m2ux added a commit that referenced this pull request Apr 23, 2026
Made-with: Cursor

Signed-off-by: Mike Clay <mike.clay@shielded.io>
m2ux added a commit that referenced this pull request Apr 23, 2026
- Remove extra blank line causing cargo fmt check failure
- Fix test assertions to match actual Debug output (REDACTED vs ***)


Signed-off-by: Mike Clay <mike.clay@shielded.io>
@m2ux m2ux self-assigned this Apr 24, 2026
@m2ux m2ux marked this pull request as ready for review April 24, 2026 16:01
@m2ux m2ux requested a review from ozgb April 24, 2026 16:01
@m2ux m2ux changed the title PM-22038: Improve Wallet Seed, Key Pair, and Address Code Quality [PM-22038]: Improve Wallet Seed, Key Pair, and Address Code Quality Apr 29, 2026
m2ux added a commit that referenced this pull request Apr 29, 2026
Made-with: Cursor

Signed-off-by: Mike Clay <mike.clay@shielded.io>
@m2ux m2ux force-pushed the task/PM-22038-improve-wallet-seed-keypair-code-quality branch from d5705a1 to beb49f7 Compare April 29, 2026 15:34
m2ux added 6 commits April 29, 2026 17:36
Address Least Authority audit Suggestion 3 (A2):

- WalletSeed: remove Copy (required for ZeroizeOnDrop), add
  Zeroize+ZeroizeOnDrop, implement redacted Debug that prints
  WalletSeed::<variant>(***) instead of raw bytes. Keep Clone
  (needed for HashMap dual-ownership pattern) and Hash/PartialEq/Eq
  (required for HashMap<WalletSeed, Wallet<D>> key).

- Keypair: remove Clone (zero callers — pure parsing wrapper).

- MaintenanceUpdateBuilder::add_addresses: accept &[MaintenanceCounter]
  and use zip iterator instead of unchecked index loop, eliminating
  potential index out-of-bounds panic.

- WalletSeed::try_from_lazy_hex: pre-validate hex string length
  (max 128 hex chars) before calling hex::decode, preventing memory
  allocation from oversized untrusted input.

- Fix all Copy-removal compilation errors across ledger/helpers and
  util/toolkit by adding explicit .clone() at ownership transfer points.

- Add unit tests for redacted Debug output, hex length validation,
  zip truncation safety, and HashMap key compatibility.

Made-with: Cursor

Signed-off-by: Mike Clay <mike.clay@shielded.io>
WalletSeed no longer derives Copy (removed for security hardening).
Two test helpers and one genesis utility need explicit .clone() to
construct owned values from borrows.

Made-with: Cursor

Signed-off-by: Mike Clay <mike.clay@shielded.io>
Add explicit .clone() on WalletSeed in e2e tests and format
merge-resolved files to satisfy CI cargo fmt check.

Made-with: Cursor

Signed-off-by: Mike Clay <mike.clay@shielded.io>
Add .clone() for WalletSeed in transaction, utxo_spend, dust_balance,
generate_intent, show_wallet, and contract_maintenance. Apply cargo fmt.

Made-with: Cursor

Signed-off-by: Mike Clay <mike.clay@shielded.io>
Made-with: Cursor

Signed-off-by: Mike Clay <mike.clay@shielded.io>
Signed-off-by: Mike Clay <mike.clay@shielded.io>
@m2ux m2ux force-pushed the task/PM-22038-improve-wallet-seed-keypair-code-quality branch from 6677bec to be21f44 Compare April 29, 2026 16:53
Made-with: Cursor

Signed-off-by: Mike Clay <mike.clay@shielded.io>
@m2ux m2ux force-pushed the task/PM-22038-improve-wallet-seed-keypair-code-quality branch from be21f44 to 92e78d1 Compare April 29, 2026 17:10
@m2ux m2ux marked this pull request as draft April 30, 2026 09:19
m2ux added 2 commits April 30, 2026 11:14
…eploy/maintain

PM-22038 inadvertently reverted the PM-22034 zeroization fix when this file
was re-touched. The Vec<u8> from serialize_untagged(...signing_key()) and
the hex String it's encoded into are not covered by WalletSeed's
ZeroizeOnDrop, so they need explicit zeroization on both success and error
paths.

Refs: PM-22034, #53
Signed-off-by: Mike Clay <mike.clay@shielded.io>
Signed-off-by: Mike Clay <mike.clay@shielded.io>
@m2ux m2ux marked this pull request as ready for review April 30, 2026 10:56
@m2ux m2ux added this pull request to the merge queue Apr 30, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Apr 30, 2026
@m2ux m2ux added this pull request to the merge queue May 1, 2026
Merged via the queue into main with commit 9490430 May 1, 2026
52 of 53 checks passed
@m2ux m2ux deleted the task/PM-22038-improve-wallet-seed-keypair-code-quality branch May 1, 2026 08:08
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