Skip to content

wallet: Add deriveHDKey interface - #36070

Open
PraneethGunas wants to merge 3 commits into
bitcoin:masterfrom
PraneethGunas:derivehdkey-interface
Open

PraneethGunas wants to merge 3 commits into
bitcoin:masterfrom
PraneethGunas:derivehdkey-interface

Conversation

@PraneethGunas

@PraneethGunas PraneethGunas commented Aug 24, 2026

Copy link
Copy Markdown

This PR adds a wallet interface for derivehdkey.

The motivation is the same as #35436 and #34861. The derivehdkey RPC exists
(#32784), but the GUI does not go through RPC, so the logic is currently out of
its reach. A dedicated wallet interface makes it available for multisig setup.
Alongside the addhdkey interface, this lets the GUI add an HD key and derive a
shareable xpub from it. Tracked as the deriveHDKey item in #35645.

Key changes:

  • Add CWallet::DeriveHDKey(), which selects the HD key and derives it at the
    requested path.
  • Update the derivehdkey RPC to call it, rather than repeating the hardened path check, the key selection and the derivation. The watch-only and unlock checks stay in the RPC, along with the xpub argument parsing.
  • Add interfaces::Wallet::deriveHDKey(), which returns the derived xpub and its
    key origin. Private key material does not cross the interface.
  • Add unit test coverage for interfaces::Wallet::deriveHDKey().

The RPC maps every WalletError to -4, moving the unhardened path and failed derivation cases from -8 and the key selection cases from -5. The watch-only -4 and locked wallet -13 are unchanged.

@DrahtBot DrahtBot changed the title wallet: Add deriveHDKey interface wallet: Add deriveHDKey interface Aug 24, 2026
@DrahtBot

DrahtBot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/36070.

Reviews

See the guideline and AI policy for information on the review process.

Type Reviewers
Concept ACK polespinasa, jeanpablojp, pseudoramdom

If your review is incorrectly listed, please copy-paste <!--meta-tag:bot-skip--> into the comment that the bot should ignore.

Conflicts

No conflicts as of last run.

@polespinasa

polespinasa commented Aug 25, 2026

Copy link
Copy Markdown
Member

concept ACK

will review soon :)

@jeanpablojp jeanpablojp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Concept ACK

Left some comments.

Comment thread src/wallet/test/wallet_interfaces_tests.cpp Outdated
Comment thread src/wallet/rpc/wallet.cpp Outdated

@polespinasa polespinasa left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

did a first swift review

Comment thread src/wallet/rpc/wallet.cpp Outdated
Comment thread src/wallet/wallet.h Outdated
Comment thread src/wallet/wallet.cpp Outdated
return *xprv;
}

util::Expected<std::pair<CExtKey, KeyOriginInfo>, WalletError> CWallet::DeriveHDKey(const std::vector<uint32_t>& path, const std::optional<CExtPubKey>& hdkey) const

@polespinasa polespinasa Aug 28, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

in a1250d2 wallet: Add deriveHDKey interface

All this is duplicated from RPC code. Why not just make both the interface and the RPC rely on the same code? Because of both using different code functions, they both return different error codes for same errors.

Also probably this commit could be split in different commits. First a preparatory commit that creates the DeriveHDKey function + testing, then a commit rebasing the RPC to use it, then a commit creating the interface. You can check #34861 for a commit structure idea :)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done. Split into "Add CWallet::DeriveHDKey" and "Add deriveHDKey interface".

On sharing code with the RPC: it already calls SelectHDKey(). What's left is the watch-only, hardened-path and unlock guards, which pin the error codes wallet_derivehdkey.py asserts (-4, -8, -13).

DeriveHDKey() returns GenericError for every failure, so routing the rest through it would turn "Unable to derive HD key at the requested path" from -8 into -5. Keeping -8 would need a new WalletErrorCode just so one caller can pick a different number, which src/wallet/types.h:48-50 warns against.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Now both the interface and the RPC rely on the same CWallet::DeriveHDKey

@jeanpablojp jeanpablojp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed again. Left one more comment on the new test.

Comment thread src/wallet/test/wallet_tests.cpp Outdated
@DrahtBot

DrahtBot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🚧 At least one of the CI tasks failed.
Task iwyu: https://github.com/bitcoin/bitcoin/actions/runs/33223334574/job/99831020528
LLM reason (✨ experimental): CI failed because the IWYU (include-what-you-use) check reported include issues and intentionally exited with a failure.

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

@hebasto

hebasto commented Sep 2, 2026

Copy link
Copy Markdown
Member

From https://github.com/bitcoin/bitcoin/actions/runs/33532997440/job/100190403622?pr=36070:

--- a/src/interfaces/wallet.h
+++ b/src/interfaces/wallet.h
@@ -34,19 +34,16 @@
 #include <vector>
 
 class ArgsManager;
-class CKeyID;
-class CPubKey;
 class CScript;
 class PartiallySignedTransaction;
 class uint256;
 enum class FeeReason;
 enum class OutputType;
 struct bilingual_str;
+
 namespace wallet {
-struct CreatedTransactionResult;
 class CCoinControl;
 class CWallet;
-enum class AddressPurpose;
 struct CRecipient;
 struct WalletContext;
 } // namespace wallet

@achow101

achow101 commented Sep 9, 2026

Copy link
Copy Markdown
Member

Why is SelectHDKey being split into a separate function? This PR should only create one function: DeriveHDKey, and that should be called by RPC. There should not need to be a RPC that is essentially duplicating code of CWallet::DeriveHDKey. This also allows the unit tests to be dropped as the functional tests (can) cover those cases.

@PraneethGunas

Copy link
Copy Markdown
Author

Why is SelectHDKey being split into a separate function? This PR should only create one function: DeriveHDKey, and that should be called by RPC. There should not need to be a RPC that is essentially duplicating code of CWallet::DeriveHDKey. This also allows the unit tests to be dropped as the functional tests (can) cover those cases.

It was done so that the the RPC error codes and messages are preserved. Also, looking at the discussions in #35436 (comment) it makes sense to collapse the error codes down based on how the errors are logged/displayed #35436 (comment)

@PraneethGunas

Copy link
Copy Markdown
Author

Rebased on master and rebuilt the history

@pseudoramdom pseudoramdom left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Concept ACK. Left some comments

Comment thread src/wallet/wallet.cpp
Comment thread src/wallet/test/wallet_interfaces_tests.cpp
master_xpub_2 = wallet.addhdkey()['xpub']
assert_raises_rpc_error(
-5,
-4,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In wallet: Add CWallet::DeriveHDKey

I'm not sure if we're okay with changing the error contract since derivehdkey will ship in v32.
If not, we likely have to duplicate the checks to retain the error codes.
@achow101

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Restored the watch-only and unlock checks in the RPC, so -4 and -13 are unchanged from v32.

-8 and -5 are now collapsed to -4, or we can duplicate the checks in the RPC to preserve the v32 behaviour.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Haven't reviewed yet, but re changing error codes:
#35690 (comment)

The derivehdkey RPC selects the HD key and derives it inline, which the GUI
cannot reach. Move both into the wallet.
The RPC now consumes CWallet::DeriveHDKey instead of repeating the hardened
path check, the key selection and the derivation. Those errors now come from
the wallet and are reported as -4.
Only the xpub is returned. interfaces::Wallet is not authenticated, so the
private key is not passed over it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants