Skip to content

Conversation

@UdjinM6
Copy link

@UdjinM6 UdjinM6 commented Nov 5, 2025

Issue being fixed or feature implemented

Add defensive checks to prevent double-encryption of HD chains and improve wallet encryption robustness:

  • Check IsCrypted() status to prevent re-encryption attempts
  • Add TOCTOU protection by re-checking IsCrypted() under lock
  • Validate decryption keys before TopUp operations
  • Only decrypt HD chains when actually encrypted
  • Remove irrelevant HasEncryptionKeys() checks (keys passed as parameters)
  • Log warning when db rewrite fails after encryption

What was done?

How Has This Been Tested?

run tests

Breaking Changes

n/a

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

Add defensive checks to prevent double-encryption of HD chains and improve wallet encryption robustness:

- Check IsCrypted() status to prevent re-encryption attempts
- Add TOCTOU protection by re-checking IsCrypted() under lock
- Validate decryption keys before TopUp operations
- Only decrypt HD chains when actually encrypted
- Remove irrelevant HasEncryptionKeys() checks (keys passed as parameters)
- Log warning when db rewrite fails after encryption
@UdjinM6 UdjinM6 added this to the 23.1 milestone Nov 5, 2025
@github-actions
Copy link

github-actions bot commented Nov 5, 2025

✅ No Merge Conflicts Detected

This PR currently has no conflicts with other open PRs.

@coderabbitai
Copy link

coderabbitai bot commented Nov 5, 2025

Walkthrough

The changes introduce defensive pre-checks for HD chain cryptographic state across wallet key management functions. In scriptpubkeyman.cpp, guards prevent decryption/encryption operations when the HD chain is uninitialized or already in the target encryption state. In wallet.cpp, the EncryptWallet function gains validation to detect pre-existing encryption, verify decryption key usability before key refresh, and ensure rewrite operations succeed before proceeding with post-encryption steps. All changes remain internal; no public function signatures are altered.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant EncryptWallet
    participant ScriptPubKeyMan
    participant HDChain

    Caller->>EncryptWallet: Initiate wallet encryption
    EncryptWallet->>EncryptWallet: Check if already encrypted (NEW GUARD)
    alt Already encrypted
        EncryptWallet-->>Caller: Return false
    else Not encrypted
        EncryptWallet->>ScriptPubKeyMan: CheckDecryptionKey(vMasterKey) (NEW CHECK)
        alt Invalid key
            ScriptPubKeyMan-->>EncryptWallet: Return false
            EncryptWallet-->>Caller: Return false
        else Valid key
            EncryptWallet->>ScriptPubKeyMan: TopUp
            EncryptWallet->>EncryptWallet: Rewrite (NEW SUCCESS CHECK)
            alt Rewrite fails
                EncryptWallet->>EncryptWallet: Log warning
                EncryptWallet-->>Caller: Return false
            else Rewrite succeeds
                EncryptWallet->>ScriptPubKeyMan: EncryptKeys
                EncryptWallet-->>Caller: Return true
            end
        end
    end
Loading
sequenceDiagram
    participant Caller
    participant ScriptPubKeyMan
    participant HDChain as HD Chain State

    Caller->>ScriptPubKeyMan: Encrypt/Decrypt/Derive operation
    
    alt GetDecryptedHDChain
        ScriptPubKeyMan->>HDChain: Get current chain
        alt Chain is non-null AND crypted (NEW GUARD)
            ScriptPubKeyMan->>HDChain: Decrypt
            HDChain-->>ScriptPubKeyMan: Decrypted chain
        else Chain is null OR not crypted
            ScriptPubKeyMan->>ScriptPubKeyMan: Skip decryption (NEW BRANCH)
            ScriptPubKeyMan-->>ScriptPubKeyMan: Return as-is
        end
    end

    alt EncryptHDChain
        ScriptPubKeyMan->>HDChain: Check encryption state
        alt Already crypted (NEW GUARD)
            ScriptPubKeyMan-->>Caller: Return false
        else Not crypted
            ScriptPubKeyMan->>HDChain: Perform encryption
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • scriptpubkeyman.cpp: Multiple functions modified with conditional guard patterns; consistent but requires tracing state assumptions across 5+ functions (GetKey, DeriveNewChildKey, EncryptHDChain, DecryptHDChain, GetDecryptedHDChain, UpgradeKeyMetadata)
  • wallet.cpp: Critical path changes in EncryptWallet; requires careful verification that new guard order and rewrite success check don't introduce state inconsistencies
  • Focus areas: Verify HD chain state machine is correctly modeled by the new guards; ensure early returns and conditional skips don't leave wallet in partial encryption state; confirm Rewrite failure handling doesn't corrupt wallet metadata

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: improve wallet encryption robustness' clearly summarizes the main change of adding defensive checks to enhance wallet encryption safety, which aligns with the changeset's primary objective.
Description check ✅ Passed The description is directly related to the changeset, detailing the specific defensive checks and robustness improvements made to wallet encryption across the modified files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/wallet/wallet.cpp (1)

792-795: Rewrite failure only logs; consider surfacing to caller or setting a persistent warning flag

If Rewrite() fails, we log but proceed. Consider returning false or setting a wallet flag that surfaces in RPC/UI so operators can act, and covering this path in tests.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98600eb and 1a0e401.

📒 Files selected for processing (2)
  • src/wallet/scriptpubkeyman.cpp (6 hunks)
  • src/wallet/wallet.cpp (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/wallet/wallet.cpp
  • src/wallet/scriptpubkeyman.cpp
🧠 Learnings (4)
📓 Common learnings
Learnt from: kwvg
Repo: dashpay/dash PR: 6543
File: src/wallet/receive.cpp:240-251
Timestamp: 2025-02-06T14:34:30.466Z
Learning: Pull request #6543 is focused on move-only changes and refactoring, specifically backporting from Bitcoin. Behavior changes should be proposed in separate PRs.
Learnt from: kwvg
Repo: dashpay/dash PR: 6761
File: src/chainlock/signing.cpp:247-250
Timestamp: 2025-07-29T14:32:48.369Z
Learning: In PR #6761, kwvg acknowledged a null pointer check issue in ChainLockSigner::Cleanup() method but deferred it to follow-up, consistent with the pattern of avoiding scope creep in refactoring PRs.
Learnt from: knst
Repo: dashpay/dash PR: 6805
File: src/wallet/rpc/wallet.cpp:357-357
Timestamp: 2025-08-08T07:01:47.332Z
Learning: In src/wallet/rpc/wallet.cpp, the upgradetohd RPC now returns a UniValue string message (RPCResult::Type::STR) instead of a boolean, including guidance about mnemonic backup and null-character passphrase handling; functional tests have been updated to assert returned strings in several cases.
Learnt from: kwvg
Repo: dashpay/dash PR: 6529
File: src/wallet/rpcwallet.cpp:3002-3003
Timestamp: 2025-02-14T15:19:17.218Z
Learning: The `GetWallet()` function calls in `src/wallet/rpcwallet.cpp` are properly validated with null checks that throw appropriate RPC errors, making additional validation unnecessary.
📚 Learning: 2025-08-08T07:01:47.332Z
Learnt from: knst
Repo: dashpay/dash PR: 6805
File: src/wallet/rpc/wallet.cpp:357-357
Timestamp: 2025-08-08T07:01:47.332Z
Learning: In src/wallet/rpc/wallet.cpp, the upgradetohd RPC now returns a UniValue string message (RPCResult::Type::STR) instead of a boolean, including guidance about mnemonic backup and null-character passphrase handling; functional tests have been updated to assert returned strings in several cases.

Applied to files:

  • src/wallet/wallet.cpp
  • src/wallet/scriptpubkeyman.cpp
📚 Learning: 2025-02-14T15:19:17.218Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6529
File: src/wallet/rpcwallet.cpp:3002-3003
Timestamp: 2025-02-14T15:19:17.218Z
Learning: The `GetWallet()` function calls in `src/wallet/rpcwallet.cpp` are properly validated with null checks that throw appropriate RPC errors, making additional validation unnecessary.

Applied to files:

  • src/wallet/wallet.cpp
  • src/wallet/scriptpubkeyman.cpp
📚 Learning: 2025-07-29T14:32:48.369Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6761
File: src/chainlock/signing.cpp:247-250
Timestamp: 2025-07-29T14:32:48.369Z
Learning: In PR #6761, kwvg acknowledged a null pointer check issue in ChainLockSigner::Cleanup() method but deferred it to follow-up, consistent with the pattern of avoiding scope creep in refactoring PRs.

Applied to files:

  • src/wallet/scriptpubkeyman.cpp
🧬 Code graph analysis (1)
src/wallet/wallet.cpp (3)
src/wallet/hdchain.cpp (2)
  • IsCrypted (37-41)
  • IsCrypted (37-37)
src/wallet/wallet.h (1)
  • IsCrypted (463-502)
src/wallet/interfaces.cpp (1)
  • spk_man (214-221)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: mac-build / Build source
  • GitHub Check: win64-build / Build source
  • GitHub Check: linux64_nowallet-build / Build source
  • GitHub Check: arm-linux-build / Build source
  • GitHub Check: linux64_tsan-build / Build source
  • GitHub Check: linux64_ubsan-build / Build source
  • GitHub Check: linux64_sqlite-build / Build source
  • GitHub Check: linux64_fuzz-build / Build source
  • GitHub Check: linux64-build / Build source
  • GitHub Check: Lint / Run linters
🔇 Additional comments (7)
src/wallet/wallet.cpp (1)

723-728: Good TOCTOU guard on encryption state

Re-checking IsCrypted() under cs_wallet closes the race window. LGTM.

src/wallet/scriptpubkeyman.cpp (6)

250-254: Prevent double-encrypting HD chain

Early-return when hdChainCurrent is already crypted is correct and matches the PR goal.


348-354: Only decrypt when crypted

The additional guard avoids unnecessary DecryptHDChain calls.


485-491: GetDecryptedHDChain: skip decryption unless needed

Conditional decrypt keeps behavior tight and avoids redundant work.


506-508: EncryptHDChain: idempotence guard

Returning false if already crypted prevents accidental re-encryption attempts.


1177-1183: GetKey: decrypt HD chain only when crypted

The guard reduces unnecessary decryption attempts and aligns with robustness goals.


1294-1300: DeriveNewChildKey: conditional decryption

Correctly avoids decrypting unencrypted chains.

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