Skip to content

btcutil/hdkeychain: private-key material transits big.Int in several paths (not zeroable) #2574

Description

@Lrifton92

Split out from #2573 per the discussion with @allocz. #2545 added scalar zeroing on the wif.go decode path; this issue tracks the broader concern @allocz raised: private-key material that transits math/big.Int cannot be reliably scrubbed from memory.

Why big.Int is a problem for secrets

big.Int stores its magnitude in an internal nat ([]Word) slice that can be grown/reallocated by arithmetic, and there is no exported way to zero the backing store. So even when a caller zeroes its own key byte slice, a copy of the secret can linger in the heap inside intermediate big.Int values until GC (and possibly beyond).

Concrete paths still using big.Int on secret material

btcutil/hdkeychain/extendedkey.go:

  • DeriveNonStandard (~L419–430): ilNum := new(big.Int).SetBytes(il), then ilNum.Add(keyNum) / ilNum.Mod(N) / ilNum.Bytes() — both the intermediate IL and the parent private key pass through big.Int.
  • NewMaster (~L673): secretKeyNum := new(big.Int).SetBytes(secretKey) on the HMAC-derived master secret.
  • NewKeyFromString (~L720): keyNum := new(big.Int).SetBytes(keyData) on the deserialized private key.

Precedent already in-tree

The standard Child() derivation was already migrated away from big.Int to btcec.ModNScalar (fixed-size, zeroable) — see the // as the old big.Int usage in this area of the codebase comment around L324. So the remediation direction is established and accepted; these three paths just weren't converted.

Proposed scope

  1. Convert the secret paths above from big.Int to btcec.ModNScalar (or fixed 32-byte arrays with explicit zero()), mirroring Child().
  2. Grep-audit remaining secret-bearing big.Int uses beyond hdkeychain (ECDSA nonce handling, WIF, any key (de)serialization) and list them here before touching code.
  3. Keep the wif.go zeroing from btcutil/wif.go: private key material not zeroed in several code paths #2573 / btcutil: reject out-of-range private keys in DecodeWIF #2545 as the tracked baseline.

runtime/secret (as @allocz noted) could help but is still experimental — not proposing to depend on it here.

Before opening PRs I'd like a maintainer to confirm appetite and whether DeriveNonStandard in particular is in scope (it's the legacy issue-172 path). Refs #2573, #2545.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions