#36442 descriptor: Represent multipath descriptors in a single Descriptor object
| Reviewability | Reviews | Agreement | Size | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| #36442 descriptor: Represent multipath descriptors in a single Descriptor object | Waiting on author | 0 | Blocked | 642 + 163 tests | ||||||
|
|
|
| |||||||
Reviewability: ReadyStalePaused Agreement: StrongPositivePositive w/ caveatsNeutralMildDisputedBlockedCrickets Reviews: current code-review ACKs, then (+stale ACKs) and -NACKs; greener = more ACKs. Size: lines added or modified outside tests, then in tests; greener = smaller. | ||||||||||
Goal
Refactors output descriptor parsing and BIP32 keypath handling so multipath descriptors are represented as a single `Descriptor` object rather than immediately expanding into a vector of separate descriptors. Replaces raw integer derivation vectors with a dedicated `KeyPath` class composed of `KeyPathElement` objects that preserve multipath branches and the original hardening character. Updates `Parse` to return a single `std::unique_ptr<Descriptor>`, providing `GetMultipathExpansion()` and `IsMultipath()` for subsystems that require individual paths.
Problem: Multipath descriptor strings were treated purely as syntactic sugar and discarded during parsing, splitting into multiple distinct descriptors. As a result, the original multipath string could not be preserved, round-tripped, or returned by RPCs like `getdescriptorinfo`.
Evidence of importance:
- PR description: 'Currently, multipath descriptor strings treat the multipath specifiers as syntactic sugar. Once we finish parsing such a string, the multipath elements are lost as the result is the multiple distinct descriptors that the multipath is a shorthand for. This means that we are unable to return the multipath string for such descriptors.'
- test/functional/rpc_getdescriptorinfo.py: asserts that `getdescriptorinfo(desc)["descriptor"]` now returns the unexpanded multipath descriptor rather than `descsum_create(expanded_descs[0])`.
Scope: A wide-reaching refactor that replaces `std::vector<uint32_t>` derivation paths across wallet, descriptor, signing, and BIP32 utility code with custom `KeyPath` and `KeyPathElement` abstractions, affecting call sites across the codebase.
Risk of a mistake: high (wallet, rpc_api). Mistakes in derivation path parsing or child key derivation could cause wallets to generate wrong addresses, misplace keys, or fail to find expected transaction outputs.
Changed since the previous assessment: Rebased or updated since the previous version with no major architectural changes.
Reviewability
Paused · Waiting on author
Open objections from fametrano with no author reply for 9 days; review would repeat what has been asked.
Waiting on (from the thread read):
- author: address the migratewallet hardening bug reported by fametrano 2026-10-23
Agreement
Blocked
objection that needs a change, nobody has replied (fametrano)
Thread notes: fametrano reported a regression in SingleKeyPathElement::Unserialize causing migratewallet to mark all derivation steps hardened, offered an inline fix, and suggested a release note for descriptor marker display changes.
Objections, suggestions, and questions:
| Reviewer | Type | Harm, request, or question | Status | Must be | Author replied | Quote |
|---|---|---|---|---|---|---|
| fametrano (contributor) | objection, correctness | migratewallet turns every step of an imported key's origin path into a hardened step because SingleKeyPathElement::Unserialize assigns a bool to std::optional<char> m_hardened | open | changed | no | 2026-10-23: 'On this head, `migratewallet` turns every step of an imported key's origin path into a hardened step.' |
| fametrano (contributor) | suggestion, maintenance | Consider adding a release note since descriptors mixing h and ' now print, with their checksum, differently from v31.1 | open | answered | no | 2026-10-23: 'A descriptor mixing `h` and `'` thus prints, with its checksum, differently from v31.1, which may deserve a release note.' |
| fametrano (contributor) | suggestion, correctness | Only engage m_hardened for a hardened step so that unhardened steps read from disk stay unhardened | open | changed | no | 2026-10-23: 'Engaged only for a hardened step, so an unhardened step read from disk stays unhardened:' |
Support:
- w0xlt (contributor) (Concept ACK, 2026-10-08): (no reason given) [not substantive]
- Sjors (member) (Concept ACK, 2026-10-21): (no reason given) [not substantive]
Participants:
- w0xlt (contributor, support, 1 statements 2026-10-08 to 2026-10-08): Concept ACK
- Sjors (member, support, 1 statements 2026-10-21 to 2026-10-21): Concept ACK
- fametrano (contributor, objection, 2 statements 2026-10-23 to 2026-10-23): reported migratewallet hardening regression and suggested a release note
Dependencies
Enables:
Files
322 lines under test/bench/ci.
- src/script/descriptor.cpp +263/-410
- src/util/bip32.cpp +150/-28
- src/test/bip32_tests.cpp +77/-79
- src/util/bip32.h +111/-10
- src/test/descriptor_tests.cpp +45/-37
- src/rpc/output_script.cpp +14/-19
- src/wallet/wallet.cpp +18/-14
- src/wallet/imports.cpp +13/-12
- src/wallet/scriptpubkeyman.cpp +13/-11
- src/wallet/walletdb.cpp +12/-10
- src/rpc/util.cpp +8/-8
- src/test/fuzz/parse_hd_keypath.cpp +8/-6
- src/test/psbt_tests.cpp +5/-5
- src/wallet/test/util.cpp +4/-6
- src/wallet/rpc/spend.cpp +4/-4
- src/wallet/test/fuzz/scriptpubkeyman.cpp +4/-4
- src/wallet/walletutil.h +4/-4
- src/key.cpp +4/-3
- src/qt/test/wallettests.cpp +3/-4
- src/rpc/mining.cpp +3/-4
- src/script/descriptor.h +6/-1
- src/wallet/export.cpp +4/-3
- src/wallet/test/walletload_tests.cpp +4/-3
- src/script/sign.cpp +3/-3
- src/wallet/test/psbt_wallet_tests.cpp +2/-3
- src/bench/descriptors.cpp +2/-2
- src/bench/wallet_encrypt.cpp +2/-2
- src/bench/wallet_ismine.cpp +2/-2
- src/test/fuzz/descriptor_parse.cpp +2/-2
- src/wallet/rpc/wallet.cpp +2/-2
- src/wallet/walletutil.cpp +2/-2
- src/key.h +2/-1
- src/rpc/util.h +2/-1
- src/script/keyorigin.h +2/-1
- src/wallet/test/wallet_tests.cpp +1/-2
- src/psbt.h +1/-1
- src/test/fuzz/util/wallet.h +1/-1
- src/wallet/rpc/addresses.cpp +1/-1
- test/functional/rpc_getdescriptorinfo.py +1/-1
Card
The short text the ranking pass compares across PRs.
Represents multipath descriptors natively in a single Descriptor object rather than immediately expanding them during parsing. Previously, multipath syntax was discarded upon parsing as syntactic sugar, which prevented RPCs like getdescriptorinfo from returning or round-tripping the original multipath descriptor string. Derivation path parsing is refactored into KeyPath and KeyPathElement classes to retain multipath steps and hardening formats, while callers requiring individual paths use GetMultipathExpansion. This change enables downstream work in #36443.
Data
thread read through 2026-10-23 (4 statements), head 74915efa6e
labels: Needs rebase, Descriptors, CI failed
Categories
Utilities (logging, arguments, libraries)
P3 · cleanup (ranking pass: Moved up: it unblocks #36443 and restores round-tripping of multipath descriptor strings.)
Replaces raw vector<uint32_t> derivation paths with structured KeyPath abstractions in shared BIP32 utilities. This improves type safety and derivation path parsing across consumers, though the primary motivation is descriptor-layer functionality.
Membership: Substantially refactors src/util/bip32.cpp and src/util/bip32.h (+261/-38) to introduce structured KeyPath and KeyPathElement primitives.
Factors: security/stability 0, bug 0, performance 0, user value 2, leverage 2
Wallet
P2 · new feature (ranking pass: Moved down slightly to head the multipath chain #36442 -> #36443 -> #36444 as a contiguous block.)
Preserves multipath descriptor strings instead of prematurely expanding and discarding them during parsing. As stated in the description, 'Once we finish parsing such a string, the multipath elements are lost... This means that we are unable to return the multipath string for such descriptors'; representing them in a single object resolves this limitation and unblocks follow-on work like #36443.
Membership: Has the Descriptors label, heavily alters src/script/descriptor.cpp, and updates wallet storage and scriptpubkeyman handling for multipath descriptors.
Factors: security/stability 0, bug 0, performance 0, user value 2, leverage 2