Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mnemonic support for descriptor wallets #6570

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from

Conversation

knst
Copy link
Collaborator

@knst knst commented Feb 11, 2025

Issue being fixed or feature implemented

Descriptor wallets doesn't support yet mnemonics (BIP39), which are supported only by legacy (non-descriptor) wallet.

What was done?

This PR adds basic support BIP39 to descriptor wallets:

  • newly created descriptor wallets will have mnemonic
  • RPC upgradetohd now support descriptor wallets and let to set specifict mnemonic with passphrase to newly created wallet
  • mnemonic is shown when calling RPC listdescriptors if exists

How Has This Been Tested?

Functional test wallet_mnemonicbits.py support --descriptors now
Functional test wallet_upgradetohd.py is updated also

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)

@knst knst added this to the 23 milestone Feb 11, 2025
Copy link

This pull request has conflicts, please rebase.

@knst knst force-pushed the mnemonic-descriptors branch from e2f9de2 to 36f3516 Compare February 18, 2025 14:04
@knst knst force-pushed the mnemonic-descriptors branch 2 times, most recently from 6fd861d to 3bc5159 Compare February 21, 2025 16:46
@knst knst force-pushed the mnemonic-descriptors branch from 3bc5159 to f80cd34 Compare February 21, 2025 17:50
@knst knst marked this pull request as ready for review February 21, 2025 18:14
Copy link

coderabbitai bot commented Feb 21, 2025

Walkthrough

The pull request introduces modifications across multiple components of the wallet system. Template signatures in the serialization module have been revised to support more flexible string types. In the RPC layer, additional fields for mnemonic and mnemonic passphrase are conditionally included in descriptor listings, and the wallet encryption message now directs users to record the new mnemonic seed. The descriptor script public key management components have been extensively updated to incorporate mnemonic handling, including new parameters for mnemonic storage, encryption, and decryption. The wallet setup and upgrade processes now pass mnemonic data, and corresponding database methods have been modified to store this information. Test files have been updated to reflect these changes by verifying mnemonic consistency, adjusting parameters related to HD wallets, and enabling previously commented-out test scripts for mnemonic functionality.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 2

🧹 Nitpick comments (13)
test/functional/wallet_mnemonicbits.py (1)

35-50: Refine membership test and validate descriptor coverage.

  • The expression assert not "mnemonic" in ... at line 35 should be replaced with assert "mnemonic" not in ... to adhere to Python best practices (E713).
  • The checks comprehensively test descriptor presence and ensure that mnemonic usage remains consistent across descriptors.

Example diff to fix the membership test style:

-assert not "mnemonic" in self.nodes[0].listdescriptors()['descriptors'][0]
+assert "mnemonic" not in self.nodes[0].listdescriptors()['descriptors'][0]
🧰 Tools
🪛 Ruff (0.8.2)

36-36: Test for membership should be not in

Convert to not in

(E713)

src/wallet/walletdb.cpp (1)

264-264: Add inline comment for potential future mnemonic usage.

The commented-out mnemonic parameters hint at possible expansions for WriteDescriptor. Retaining this comment clarifies future extension paths without complicating the current implementation.

src/wallet/scriptpubkeyman.cpp (7)

1848-1848: Add unit tests for mnemonic handling.
The // TODO: test for mnemonics comment indicates missing coverage. Consider creating tests that validate both encrypted and unencrypted mnemonic usage and confirm end-to-end correctness.

Would you like help drafting these tests?


1878-1878: Use more secure buffers for mnemonic data.
Storing sensitive keys and mnemonics in std::vector<unsigned char> might leave remnants in memory after usage. Consider consistently using SecureVector or explicit overwrites to reduce the risk of memory disclosures.

Also applies to: 1882-1896


1902-1903: Clear mnemonic details more securely.
Calling m_mnemonics.clear() alone might not guarantee secure wiping of memory. For additional safety, consider using a secure wrapper or explicit zeroization to prevent data remnants in memory.


2090-2098: Eliminate duplication noted in the // TODO: comment.
Code for generating a seed from mnemonic and passphrase repeats logic from elsewhere. Centralizing the seed derivation steps in a shared utility would simplify future maintenance.


2120-2120: Improve error feedback.
When AddDescriptorKeyWithDB fails, the exception message is minimal. Consider clarifying the reason in the error string to aid debugging.


2423-2423: Unify crypted key addition flow.
AddCryptedKey mirrors some encryption steps found in other routines. Extract a common helper to handle crypted key insertion consistently and reduce code sprawl.


2483-2540: Ensure a robust single-mnemonic policy.
GetMnemonicString currently supports only one mnemonic per descriptor and logs an error if multiple versions are found. Long-term, you may need to handle multi-mnemonic or multi-wallet scenarios. Also consider adding explicit zeroization or secure memory usage here for in-memory mnemonic data.

src/wallet/wallet.cpp (3)

42-42: Consider moving bip39 dependency to appropriate module.

The inline comment and the updated include line suggest that <wallet/bip39.h> should be refactored into scriptpubkeyman.cpp. If you decide to proceed, ensure the bip39 functionality is neatly encapsulated there to avoid bloating wallet-wide dependencies.


4307-4311: Refactor and ensure robust mnemonic handling.

  1. Lines 4310-4311 mention a “TODO: remove duplicated code with CHDChain::SetMnemonic”. Factor out the shared logic into a helper to maintain DRY principles.
  2. Properly handle potential edge cases around custom mnemonic bit sizes.
  3. Ensure thorough testing of invalid/partial mnemonics and error messages (line 4315).
  4. Line 4335 calls SetupDescriptorGeneration; confirm consistent encryption states for the new spk_man if the wallet is locked or re-locked.

Also applies to: 4313-4318, 4335-4335


4416-4418: Implement descriptor-level mnemonic.

The TODO comment at line 4416 suggests a missing implementation for mnemonic-based descriptor creation within AddDescriptor(). If desired, the code from SetupDescriptorScriptPubKeyMans can be adapted or refactored here, ensuring consistent encryption and seed handling.

src/wallet/wallettool.cpp (1)

52-52: Add documentation for the mnemonic parameters.

The empty strings represent default values for mnemonic and mnemonic passphrase. Consider adding a comment to clarify this, especially since these parameters are part of the new BIP39 support.

-    wallet_instance->SetupDescriptorScriptPubKeyMans("", "");
+    // Initialize descriptor wallet with empty mnemonic and passphrase
+    wallet_instance->SetupDescriptorScriptPubKeyMans("", ""); // mnemonic, mnemonic_passphrase
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e500191 and f80cd34.

📒 Files selected for processing (16)
  • src/serialize.h (3 hunks)
  • src/wallet/rpcdump.cpp (2 hunks)
  • src/wallet/rpcwallet.cpp (1 hunks)
  • src/wallet/scriptpubkeyman.cpp (9 hunks)
  • src/wallet/scriptpubkeyman.h (4 hunks)
  • src/wallet/wallet.cpp (8 hunks)
  • src/wallet/wallet.h (1 hunks)
  • src/wallet/walletdb.cpp (5 hunks)
  • src/wallet/walletdb.h (1 hunks)
  • src/wallet/wallettool.cpp (1 hunks)
  • test/functional/test_runner.py (1 hunks)
  • test/functional/wallet_descriptor.py (1 hunks)
  • test/functional/wallet_dump.py (1 hunks)
  • test/functional/wallet_importmulti.py (0 hunks)
  • test/functional/wallet_keypool_hd.py (0 hunks)
  • test/functional/wallet_mnemonicbits.py (2 hunks)
💤 Files with no reviewable changes (2)
  • test/functional/wallet_importmulti.py
  • test/functional/wallet_keypool_hd.py
✅ Files skipped from review due to trivial changes (2)
  • test/functional/wallet_descriptor.py
  • src/wallet/rpcwallet.cpp
🧰 Additional context used
🪛 Ruff (0.8.2)
test/functional/wallet_mnemonicbits.py

36-36: Test for membership should be not in

Convert to not in

(E713)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: linux64_multiprocess-build / Build source
  • GitHub Check: linux64_tsan-build / Build source
  • GitHub Check: win64-build / Build source
  • GitHub Check: linux64_ubsan-build / Build source
🔇 Additional comments (34)
test/functional/wallet_mnemonicbits.py (6)

27-32: Ensure mnemonic invariance post-encryption.

These lines verify the mnemonic before and after wallet encryption. The approach looks solid for detecting any unintentional mnemonic changes.


51-51: Confirm default mnemonic length logic.

Verifying that the default mnemonic remains 12 words is a key baseline check. The assertion ensures user configurations haven't inadvertently changed default behavior.


54-62: Test coverage for different mnemonic bit sizes.

Restarting the node with different -mnemonicbits values and creating separate wallets ensures thorough coverage and helps catch issues related to varied entropy lengths.


65-74: Validate descriptor-based HD wallet creation.

Creating a blank descriptor wallet (wallet_256) and upgrading it to HD ensures the descriptors are properly set up, and the mnemonic word count checks confirm consistency for 12, 15, 18, 21, and 24-word sets.


75-82: Confirm legacy HD wallet behavior.

This block parallels the descriptor-based path but exercises the legacy codepath. The consistent word count checks help validate that both approaches produce the expected mnemonic lengths.


83-84: Re-check mnemonic consistency after multiple wallet operations.

Comparing the final mnemonic with the pre-encryption mnemonic ensures no unintended side-effects from repeated creation, encryption, and node restarts.

src/wallet/scriptpubkeyman.h (5)

513-517: Organize mnemonic-related type definitions.

Introducing Mnemonic, MnemonicMap, CryptedMnemonic, and CryptedMnemonicMap is a clean approach for grouping mnemonic data. These aliases improve clarity by explicitly referencing pairs of mnemonic/passphrase and their encrypted forms.


525-526: Leverage fine-grained storage for mnemonic data.

Storing m_mnemonics and m_crypted_mnemonics in separate maps avoids conflating plaintext and encrypted data. This design choice improves maintainability and clarifies the encryption lifecycle.


531-531: Add mnemonic parameters to database method.

AddDescriptorKeyWithDB now accepts mnemonic/passphrase parameters, allowing the descriptor key addition workflow to simultaneously persist mnemonic data. Ensure corresponding calls handle these parameters consistently.


599-600: Enable explicit addition of keys and mnemonics.

By extending AddKey and AddCryptedKey with mnemonic support, the code merges standard or encrypted keys with associated mnemonic data in a single interface, consolidating key creation steps.


612-612: Expose mnemonic retrieval.

GetMnemonicString provides a straightforward way to retrieve the mnemonic and passphrase, and can be a critical piece for external usage or UI integration. Ensure any call-sites handle passphrase sensitivity securely.

src/wallet/walletdb.cpp (7)

244-245: Extend descriptor key writing to include mnemonic data.

These lines demonstrate storing both the private key hash and mnemonic fields. This integration ensures that descriptors carry the relevant seed phrase data for future recovery or reference.

Also applies to: 252-252


255-258: Support encrypted descriptor keys alongside mnemonics.

WriteCryptedDescriptorKey accommodates encrypted private keys as well as optional mnemonic data. This allows a more secure approach, ensuring confidential mnemonic storage.


339-340: Persist additional mnemonic fields in scan state.

Maintaining both plaintext and encrypted mnemonic data in CWalletScanState ensures uniform handling during wallet loading. Verify all transitions from scan to final wallet state are robust.


709-723: Safely parse plaintext mnemonic from database.

By wrapping the read logic in a try/catch block, the code gracefully handles older wallets or missing mnemonic data. The fallback to an empty string is appropriate for legacy scenarios.


741-755: Handle encrypted mnemonic data gracefully.

Similar to plaintext mnemonics, this block ensures safe reading of encrypted data. Catching std::ios_base::failure avoids crashes when older or incomplete wallet records lack mnemonic info.


900-906: Finalize plain-text mnemonic insertion into descriptor keys.

This segment merges any existing mnemonic parameters from the scan state into the in-memory descriptor structure. Empty strings default to no mnemonic, ensuring backward compatibility.


910-915: Integrate encrypted mnemonic data on load.

Similarly, crypted mnemonics from the scan state are applied to descriptor keys if present. This preserves encryption boundaries and streamlines loading new or migrated wallets.

src/wallet/scriptpubkeyman.cpp (6)

16-16: Ensure BIP39 library usage is properly tested and secure.
The inclusion of <wallet/bip39.h> introduces mnemonic handling. Verify that every new mnemonic-related branch is thoroughly covered by unit or functional tests, and consider any potential security implications of introducing the BIP39 library.


1875-1876: Avoid production assert for external inputs.
Using assert(pubkey.GetID() == key_in.first) could cause the wallet to terminate if a mismatch ever occurs in production. Consider gracefully handling key mismatches by returning an appropriate error code instead of asserting.


1898-1899: Storing encrypted mnemonics looks correct.
Implementation for populating m_crypted_mnemonics and writing to the batch is straightforward and aligned with the rest of the encryption flow.


2033-2033: New function signature to handle descriptor key insertion.
This method now accepts mnemonic parameters, aligning with in-wallet handling of BIP39 data. The signature looks consistent with your design.


2080-2080: Verify derivation path assumptions for descriptor generation.
SetupDescriptorGeneration introduces new descriptor logic (BIP44 style path). Confirm that it matches your chain’s derivation policies, and consider exposing a way for users to specify custom paths if needed.


2100-2100: Storing creation time.
Using GetTime() to mark descriptor creation keeps consistency with other wallet metadata. This appears correct for tracking.

src/wallet/wallet.cpp (2)

339-339: Verify empty mnemonic arguments.

Calling SetupDescriptorScriptPubKeyMans("", "") creates descriptor managers with empty mnemonic/passphrase. Verify that this is intentional and doesn't break downstream operations that assume valid mnemonic data.


3702-3704: Double-check descriptor upgrade logic.

When upgrading to HD (UpgradeToHD), the code branches to descriptor logic under WALLET_FLAG_DESCRIPTORS. Confirm if skipping the legacy code path is correct, and consider clarifying behavior for non-legacy descriptor wallets upgrading to HD.

test/functional/wallet_dump.py (1)

90-90: LGTM! Simplified test configuration.

Removing -usehd=1 is appropriate since descriptor wallets now handle HD and mnemonic functionality by default.

src/wallet/walletdb.h (1)

215-216: LGTM! Secure implementation of mnemonic storage.

The implementation properly handles both unencrypted and encrypted mnemonic storage:

  • Uses SecureString for sensitive data in memory
  • Uses vector<unsigned char> for encrypted storage
test/functional/test_runner.py (1)

297-297: LGTM! Activated descriptor wallet mnemonic tests.

Enabling wallet_mnemonicbits.py --descriptors ensures proper testing of the new BIP39 mnemonic support in descriptor wallets.

src/serialize.h (2)

820-821: LGTM! Template signature change improves flexibility.

The template signature change from a single parameter to three parameters allows for more flexible string serialization by supporting different allocator and character types.


823-829:

❓ Verification inconclusive

Verify if SecureString serialization is still needed.

The SecureString serialization code has been commented out. Since this PR adds mnemonic support and mnemonics need to be stored securely, we should verify if an alternative secure serialization mechanism is in place.

Run the following script to check for alternative SecureString serialization implementations:


🏁 Script executed:

#!/bin/bash
# Description: Check for SecureString serialization implementations

# Search for SecureString serialization in the codebase
rg -A 5 "SecureString.*[Ss]erializ"

# Search for mnemonic-related serialization
rg -A 5 "mnemonic.*[Ss]erializ"

Length of output: 71


SecureString Serialization Verification Needed for Mnemonic Storage

The recent searches did not reveal any alternative implementations for SecureString serialization or mnemonic-related serialization in the codebase. Please verify manually that no new secure serialization mechanism has been introduced elsewhere. If secure serialization for mnemonics is required, consider re-enabling or replacing the commented-out SecureString serialization code.

  • Location under review: src/serialize.h (lines 823-829)
  • Next step: Manually confirm if a secure serialization mechanism exists or needs to be implemented for mnemonic secure storage.
src/wallet/wallet.h (1)

1070-1070:

✅ Verification successful

LGTM! Method signature change adds secure mnemonic support.

The addition of mnemonic and mnemonic_passphrase parameters of type SecureString ensures secure handling of sensitive mnemonic data in descriptor wallets. This change aligns with the PR objective of adding BIP39 support.

Run the following script to verify consistent mnemonic handling across the codebase:


🏁 Script executed:

#!/bin/bash
# Description: Verify consistent mnemonic handling

# Check for mnemonic-related function signatures
ast-grep --pattern $'void $_(const SecureString& mnemonic, const SecureString $_)'

# Check for potential security issues in mnemonic handling
rg -A 5 "mnemonic.*=|=.*mnemonic"

Length of output: 24145


LGTM! Secure Mnemonic Support Verified

The updated method signature for SetupDescriptorScriptPubKeyMans—now accepting both mnemonic and mnemonic_passphrase of type SecureString—has been verified across the codebase. Searches confirmed that mnemonic and mnemonic passphrase handling is consistent in wallet initialization, descriptor key management, and associated tests. This change aligns perfectly with the PR objective of adding BIP39 support and securely managing sensitive data.

src/wallet/rpcdump.cpp (2)

1967-1968: LGTM! Documentation update for mnemonic fields.

The RPCResult documentation correctly specifies the new mnemonic fields and their conditions for inclusion in the response.


2015-2022:

❓ Verification inconclusive

Verify secure handling of mnemonic data.

The code retrieves and exposes sensitive mnemonic data. While the implementation correctly:

  1. Uses SecureString for mnemonic storage
  2. Only includes mnemonic data when private=true
  3. Checks for empty mnemonic before including

Consider adding additional security measures:

Also verify that the RPC connection is using TLS to prevent mnemonic exposure during transit:


🏁 Script executed:

#!/bin/bash
# Check if the mnemonic data is properly cleared from memory
ast-grep --pattern $'SecureString $var;
  $$$
  $var.clear();'

Length of output: 229


🌐 Web query:

What are the best practices for securing sensitive data in Bitcoin RPC responses?

💡 Result:

Securing sensitive data in Bitcoin RPC responses requires a multi-layered approach combining encryption, access control, and operational safeguards. Here are the critical best practices:


1. Network Security

  • Restrict RPC exposure: Bind RPC interfaces to localhost by default and only allow remote access through VPNs or SSH tunnels[1][7]. Avoid public internet exposure due to unencrypted credentials[1][3].
  • Use encrypted connections: Implement HTTPS with TLS 1.2+/SSL for all RPC communications[2][3][8]. QuickNode's Endpoint Armor provides secure URL templates for this purpose[10].
  • Configure firewalls: Limit inbound/outbound traffic to trusted IP ranges using tools like ufw or iptables[7].

2. Authentication & Authorization

  • Use .cookie authentication: Prefer Bitcoin Core's auto-generated credentials stored in .cookie files over static rpcuser/rpcpassword[1][9].
  • Implement RBAC/ACLs: Restrict methods like sendtoaddress to specific user roles[2][7]. For multi-wallet setups:
    # Access wallet-specific endpoints
    curl --user alice localhost:8332/wallet/walletname/getbalance[2]
  • Rotate credentials: Regularly update API keys/tokens if using services like Alchemy or Infura[2][3].

3. Data Handling

  • Sanitize outputs: Display serialized data (e.g., raw transactions) exclusively in hex format to prevent script injection[1][9].
  • Validate inputs: Check parameters like wallet_name in createwallet for path traversal attempts[1].
  • Escape special characters: Use libraries like jq when processing JSON-RPC responses:
    bitcoin-cli getrawtransaction TXID | jq -r .hex[2]

4. Operational Security

  • Update regularly: Patch Bitcoin Core and dependencies to address vulnerabilities like CVE-2023-12345[4][6][7].
  • Monitor logs: Audit /debug.log for suspicious activity patterns[1][3].
  • Limit privileges: Run bitcoind under a non-root user account with restricted filesystem permissions[1][7].

5. Architectural Controls

| Control                | Implementation Example          | Source |
|------------------------|----------------------------------|--------|
| Rate limiting          | 100 req/minute per IP           | [5][7] |
| Request validation      | Whitelist allowed RPC methods    | [8]    |
| Encrypted storage       | LUKS for wallet.dat files        | [8]    |
| Fail2ban integration    | Block brute-force attempts       | [8]    |

By combining these measures, you create defense-in-depth against common attack vectors while maintaining compatibility with standard Bitcoin JSON-RPC workflows[1][8][9].

Citations:


Action Required: Further Manual Security Verification Needed

  • The automated AST search did not conclusively verify that SecureString instances (e.g., mnemonic data) are explicitly cleared from memory. The absence of output (aside from unrelated UTF-8 errors) suggests that this check requires manual confirmation—please ensure that either the destructor of SecureString or explicit .clear() calls are properly clearing sensitive data once used.
  • Based on the latest best practices for Bitcoin RPC security, confirm that the RPC connection is secured via TLS (or an equivalent encryption method) to guard sensitive data during transit.

Please review and verify these points manually before finalizing this implementation.

Comment on lines +2050 to +2076
std::vector<unsigned char> crypted_mnemonic;
std::vector<unsigned char> crypted_mnemonic_passphrase;
CKeyingMaterial secret(key.begin(), key.end());
CKeyingMaterial mnemonic_secret(mnemonic.begin(), mnemonic.end());
CKeyingMaterial mnemonic_passphrase_secret(mnemonic_passphrase.begin(), mnemonic_passphrase.end());
if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) {
return EncryptSecret(encryption_key, secret, pubkey.GetHash(), crypted_secret);
if (!EncryptSecret(encryption_key, secret, pubkey.GetHash(), crypted_secret)) return false;
if (!mnemonic.empty()) {
if (!EncryptSecret(encryption_key, mnemonic_secret, pubkey.GetHash(), crypted_mnemonic)) {
return false;
}
if (!EncryptSecret(encryption_key, mnemonic_passphrase_secret, pubkey.GetHash(), crypted_mnemonic_passphrase)) {
return false;
}
}
return true;
})) {
return false;
}

m_map_crypted_keys[pubkey.GetID()] = make_pair(pubkey, crypted_secret);
return batch.WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret);
m_crypted_mnemonics[pubkey.GetID()] = make_pair(crypted_mnemonic, crypted_mnemonic_passphrase);
return batch.WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret, crypted_mnemonic, crypted_mnemonic_passphrase);
} else {
m_map_keys[pubkey.GetID()] = key;
return batch.WriteDescriptorKey(GetID(), pubkey, key.GetPrivKey());
m_mnemonics[pubkey.GetID()] = make_pair(mnemonic, mnemonic_passphrase);
return batch.WriteDescriptorKey(GetID(), pubkey, key.GetPrivKey(), mnemonic, mnemonic_passphrase);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor to reduce duplication in encryption logic.
The added logic for encrypting private keys, mnemonic, and passphrase largely duplicates earlier code blocks. Extracting a helper function can improve maintainability and reduce potential inconsistencies.

@@ -2354,21 +2401,34 @@
}
}

bool DescriptorScriptPubKeyMan::AddKey(const CKeyID& key_id, const CKey& key)
bool DescriptorScriptPubKeyMan::AddKey(const CKeyID& key_id, const CKey& key, const SecureString& mnemonic, const SecureString& mnemonic_passphrase)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid duplication with AddDescriptorKeyWithDB.
This new AddKey method partially overlaps with the logic in AddDescriptorKeyWithDB. Consider consolidating them to reduce the overhead of multiple mnemonic-handling paths.

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: 0

🧹 Nitpick comments (1)
test/functional/wallet_mnemonicbits.py (1)

50-52: Use Python's 'not in' operator for membership testing.

The membership test should use Python's not in operator for better readability and adherence to PEP 8 style guidelines.

-    assert not "mnemonic" in self.nodes[0].listdescriptors()['descriptors'][0]
+    assert "mnemonic" not in self.nodes[0].listdescriptors()['descriptors'][0]
🧰 Tools
🪛 Ruff (0.8.2)

51-51: Test for membership should be not in

Convert to not in

(E713)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f80cd34 and 31316ed.

📒 Files selected for processing (1)
  • test/functional/wallet_mnemonicbits.py (2 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
test/functional/wallet_mnemonicbits.py

51-51: Test for membership should be not in

Convert to not in

(E713)

⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: x86_64-w64-mingw32 / Build depends
  • GitHub Check: x86_64-apple-darwin / Build depends
  • GitHub Check: x86_64-pc-linux-gnu_multiprocess / Build depends
  • GitHub Check: arm-linux-gnueabihf / Build depends
  • GitHub Check: x86_64-pc-linux-gnu_nowallet / Build depends
  • GitHub Check: x86_64-pc-linux-gnu / Build depends
🔇 Additional comments (7)
test/functional/wallet_mnemonicbits.py (7)

27-29: LGTM: Effective mnemonic retrieval handling for both wallet types.

The code correctly handles mnemonic retrieval for both descriptor and legacy wallets. For descriptor wallets, it uses the listdescriptors(True) RPC and accesses the mnemonic from the second descriptor (index 1), while for legacy wallets, it uses the dumphdinfo() RPC.


30-49: LGTM: Well-structured encryption test with appropriate assertions.

The code properly tests wallet encryption behavior for both wallet types:

  • For descriptor wallets, it correctly verifies that encryption creates new private keys, making old descriptors inactive and new ones active
  • For legacy wallets, it confirms that the mnemonic remains unchanged after encryption
  • Good comment explaining that descriptor wallet encryption creates new private keys

The assertion checking for exactly 2 inactive descriptors with the old mnemonic is appropriate.


53-57: LGTM: Good validation of mnemonic consistency across descriptors.

These assertions correctly verify that:

  1. Both descriptors have mnemonics with the standard 12-word length
  2. The same mnemonic is used across descriptors

This ensures the mnemonic implementation for descriptor wallets is working as expected.


58-65: LGTM: Thorough descriptor counting with clear explanation.

The code properly counts the total number of descriptors and those containing mnemonics:

  • Good comment explaining why one descriptor doesn't have a mnemonic (imported coinbase private key)
  • Clear assertion that there should be 4 descriptors with mnemonics (2 active, 2 inactive)
  • Verification of the total descriptor count (5)

80-89: LGTM: Comprehensive testing of different mnemonic bit lengths for descriptor wallets.

The code thoroughly tests descriptor wallets with different mnemonic bit lengths:

  • Creates a blank descriptor wallet and uses upgradetohd() to test that functionality
  • Correctly checks mnemonic word counts for each bit length (128→12, 160→15, 192→18, 224→21, 256→24)
  • Good comment explaining why index 1 is used for the default wallet (index 0 is for coinbase private key)
  • Uses the correct RPC methods for descriptor wallets

90-96: LGTM: Proper handling for legacy wallet mnemonic verification.

The code correctly handles the legacy wallet case:

  • Creates a blank HD legacy wallet with appropriate parameters
  • Uses upgradetohd() to initialize the HD wallet
  • Properly verifies mnemonic word counts across different bit lengths using dumphdinfo()

98-99: LGTM: Effective final verification of mnemonic persistence.

The code correctly retrieves and verifies that the mnemonic in the default wallet matches the one saved before encryption, ensuring mnemonic persistence works as expected across both descriptor and legacy wallet types.

@knst knst force-pushed the mnemonic-descriptors branch from 31316ed to 7a6bf5f Compare March 3, 2025 08:15
@knst
Copy link
Collaborator Author

knst commented Mar 3, 2025

@knst knst force-pushed the mnemonic-descriptors branch from 31316ed to 7a6bf5f

Last 2 pushes to fix intermittent failure in functional test and remove duplicated code, commits are squashed.

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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31316ed and 7a6bf5f.

📒 Files selected for processing (1)
  • test/functional/wallet_mnemonicbits.py (2 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
test/functional/wallet_mnemonicbits.py

45-45: SyntaxError: Expected a statement


45-46: SyntaxError: Expected a statement

⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: x86_64-w64-mingw32 / Build depends
  • GitHub Check: arm-linux-gnueabihf / Build depends
  • GitHub Check: x86_64-pc-linux-gnu / Build depends
  • GitHub Check: x86_64-pc-linux-gnu_multiprocess / Build depends
  • GitHub Check: x86_64-pc-linux-gnu_nowallet / Build depends
🔇 Additional comments (6)
test/functional/wallet_mnemonicbits.py (6)

33-38: LGTM: Effectively verifies mnemonic presence in descriptor wallets.

The code correctly checks that the initial descriptor doesn't contain a mnemonic by default, but does contain it when the True parameter is passed to listdescriptors. The additional check for mnemonic equality between descriptors ensures consistency.


39-58: Comprehensive mnemonic validation logic for descriptor wallets.

This section thoroughly verifies the mnemonic structure and behavior in descriptor wallets by:

  1. Counting mnemonics across descriptors
  2. Verifying active/inactive states
  3. Checking the expected total number of descriptors

The comments explaining the expected descriptor counts are particularly helpful for future maintenance.

🧰 Tools
🪛 Ruff (0.8.2)

45-45: SyntaxError: Expected a statement


45-46: SyntaxError: Expected a statement


59-62: Effective handling of legacy wallet case.

The code appropriately falls back to the legacy wallet verification pattern when descriptors are not enabled, maintaining backward compatibility while adding new functionality.


76-84: Well-structured testing of different mnemonic bit lengths for descriptor wallets.

The implementation correctly tests each mnemonic bit length configuration and verifies the appropriate number of words for each. The descriptive comments alongside each assertion make the test's intent clear.


85-92: Comprehensive parallel testing of legacy wallets.

The code maintains parallel tests for legacy wallets, ensuring that both wallet types are thoroughly tested with various mnemonic bit lengths.


94-95: Proper final verification of mnemonic persistence.

This final check ensures that the original wallet's mnemonic remains consistent throughout the test, which is a crucial validation for wallet security and reliability.

if 'mnemonic' not in desc:
# skip imported coinbase private key
continue
assert_equal(desc['mnemonic'].split()), 12)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix syntax error in assertion statement.

There's a syntax error in this line. The parentheses are mismatched and the len() function is missing.

-                assert_equal(desc['mnemonic'].split()), 12)
+                assert_equal(len(desc['mnemonic'].split()), 12)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert_equal(desc['mnemonic'].split()), 12)
assert_equal(len(desc['mnemonic'].split()), 12)
🧰 Tools
🪛 Ruff (0.8.2)

45-45: SyntaxError: Expected a statement


45-46: SyntaxError: Expected a statement

@knst knst force-pushed the mnemonic-descriptors branch from 7a6bf5f to f08e935 Compare March 3, 2025 08:19
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: 0

♻️ Duplicate comments (1)
test/functional/wallet_mnemonicbits.py (1)

45-45: ⚠️ Potential issue

Fix syntax error in assertion statement.

There's a syntax error in this line. The parentheses are mismatched and the len() function is missing.

-                assert_equal(desc['mnemonic'].split()), 12)
+                assert_equal(len(desc['mnemonic'].split()), 12)
🧹 Nitpick comments (2)
test/functional/wallet_mnemonicbits.py (2)

77-77: Consider clarifying createwallet parameters.

The createwallet call has six parameters, but their meaning isn't immediately clear from context. Consider adding a brief inline comment or using named arguments for better readability.

-            self.nodes[0].createwallet("wallet_256", False, True, "", False, True)  # blank Descriptors
+            # Parameters: name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
+            self.nodes[0].createwallet("wallet_256", False, True, "", False, True)  # blank Descriptors

80-84: Consider using constants for expected word counts.

The code uses magic numbers (12, 15, 18, 21, 24) for the expected word counts based on mnemonic bits. Consider defining constants at the class level for better readability and maintainability.

+ # At the class level
+ MNEMONIC_WORDS = {
+     128: 12,  # Default
+     160: 15,
+     192: 18, 
+     224: 21,
+     256: 24
+ }

# Then in the test
- assert_equal(len(self.nodes[0].get_wallet_rpc(self.default_wallet_name).listdescriptors(True)["descriptors"][1]["mnemonic"].split()), 12)  # 12 words by default
+ assert_equal(len(self.nodes[0].get_wallet_rpc(self.default_wallet_name).listdescriptors(True)["descriptors"][1]["mnemonic"].split()), self.MNEMONIC_WORDS[128])  # Default
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a6bf5f and f08e935.

📒 Files selected for processing (1)
  • test/functional/wallet_mnemonicbits.py (2 hunks)
🔇 Additional comments (8)
test/functional/wallet_mnemonicbits.py (8)

28-29: LGTM! Robust mnemonic retrieval based on wallet type.

The code now properly handles mnemonic retrieval for both descriptor and legacy wallets, ensuring the test can work with either wallet type.


30-32: LGTM! Wallet encryption preparation.

Properly encrypts the wallet and unlocks it with the passphrase, a necessary step to test mnemonic persistence.


32-58: LGTM! Comprehensive descriptor wallet mnemonic validation.

This code block effectively tests several key aspects of descriptor wallet mnemonic functionality:

  1. Verifies that public descriptors don't expose mnemonics while private ones do
  2. Confirms mnemonic consistency across descriptors
  3. Validates the standard 12-word length for BIP39 mnemonics
  4. Checks active/inactive status relationship with mnemonics
  5. Verifies the expected number of descriptors with mnemonics

The validation logic is thorough and covers important edge cases.


59-62: LGTM! Legacy wallet mnemonic validation.

Properly validates the expected behavior for legacy wallets, confirming the default 12-word mnemonic length and persistence after encryption.


72-72: LGTM! Wallet unlocking for tests.

Correctly unlocks the default wallet before proceeding with additional tests.


76-84: LGTM! Comprehensive descriptor wallet mnemonic length testing.

This section thoroughly tests the descriptor wallet functionality with different mnemonic bit lengths, verifying that each wallet has the expected number of words based on the configured bit length (128→12, 160→15, 192→18, 224→21, 256→24).

The commented explanation on line 79 is helpful to understand why index 1 is used for the default wallet.


85-92: LGTM! Comprehensive legacy wallet mnemonic length testing.

This section properly tests the legacy wallet functionality with different mnemonic bit lengths, ensuring each wallet has the expected number of words.


94-95: LGTM! Final mnemonic consistency verification.

This correctly verifies that the mnemonic of the default wallet still matches what was captured before encryption, confirming mnemonic persistence.

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