Skip to content

fix(core): zeroize the derived X25519 secret (#65)#66

Open
beardthelion wants to merge 1 commit into
fix/zeroize-seed-bytesfrom
fix/zeroize-x25519-secret
Open

fix(core): zeroize the derived X25519 secret (#65)#66
beardthelion wants to merge 1 commit into
fix/zeroize-seed-bytesfrom
fix/zeroize-x25519-secret

Conversation

@beardthelion

Copy link
Copy Markdown
Collaborator

Stacked on #64 (#41). Based on fix/zeroize-seed-bytes; the diff here is only this change. Retarget/rebase onto main once #64 merges. Please merge #64 first.

Summary

x25519_secret_from_seed derived the X25519 private scalar from the Ed25519 seed and returned it as a bare [u8; 32]. Because [u8; 32] is Copy with no Drop (and sha2's digest output likewise has no zeroize), the scalar, the SHA-512 digest, and the returned temporary were released without being scrubbed. This routes the derived secret through Zeroizing and explicitly wipes the digest.

Motivation & context

Closes #65

Direct sibling of #41/#64: same Copy-no-Drop memory-hygiene class, on the derived X25519 secret rather than the raw seed. #41 was deliberately scoped to the seed accessor and flagged this downstream leak for its own follow-up. The derived scalar is the actual private key fed into crypto_box::SecretKey in open_blob, which runs on every withheld-blob read.

Kind of change

  • Bug fix
  • Feature
  • Security fix
  • Docs
  • Tests / CI
  • Refactor (no behavior change)
  • Breaking or protocol change (issue required first)

What changed

Crate touched: gitlawb-core.

  • x25519_secret_from_seed now returns Zeroizing<[u8; 32]> and builds the scalar directly into a zeroizing buffer, so no bare [u8; 32] secret local persists.
  • The intermediate SHA-512 digest is explicitly wiped (h.as_mut_slice().zeroize(), a volatile non-elidable write) before it drops.
  • The two callers (open_blob and a crypto test) deref the result into crypto_box::SecretKey, which already scrubs its own copy.

No behavior change: the derived scalar and all decryption output are byte-identical, covered by the existing ed25519_to_x25519_keypair_agrees and seal_open_round_trip_for_recipients tests.

How a reviewer can verify

cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings

Before you request review

  • Scope is one logical change; no unrelated churn
  • cargo test --workspace passes locally
  • New behavior is covered by tests (behavior is byte-identical; existing crypto round-trip tests guard it)
  • cargo fmt --all and cargo clippy --workspace --all-targets -- -D warnings are clean
  • Commit titles use Conventional Commits (feat(...), fix(...), docs(...))
  • Docs / .env.example updated if behavior or config changed (or N/A)
  • Checked existing PRs so this isn't a duplicate

Protocol & signing impact

  • Touches DID / did:key, Ed25519 / RFC 9421 signatures, UCAN, ref certs, or P2P wire formats
  • Discussed in an issue before implementation
  • Backward-compatible with existing nodes and previously signed history

In-memory hygiene only. No change to the encryption scheme, key derivation math, or wire format; output is byte-identical.

x25519_secret_from_seed derived the X25519 private scalar from the Ed25519
seed and returned it as a bare [u8; 32]. Because [u8; 32] is Copy with no
Drop (and sha2's digest output likewise has no zeroize), the scalar, the
SHA-512 digest, and the returned temporary were released without being
scrubbed, leaving secret-derived material in freed memory. This is the same
Copy-no-Drop class fixed for the raw seed in #41, on the derived secret.

Return Zeroizing<[u8; 32]>, build the scalar directly into a zeroizing
buffer so no bare secret local persists, and explicitly wipe the SHA-512
digest before it drops. Callers deref the result into crypto_box::SecretKey,
which already scrubs its own copy. No behavior change: the derived scalar and
all decryption output are byte-identical, covered by the existing
ed25519_to_x25519_keypair_agrees and seal_open_round_trip_for_recipients tests.

Stacked on #41 (#64).
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0792a9ba-f99b-4367-a12d-181f5d4f2bf1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/zeroize-x25519-secret

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

1 participant