#35041 descriptor: speed-up `Parse` (xpub/xpriv) in ~30%
| Reviewability | Reviews | Agreement | Size | ||||
|---|---|---|---|---|---|---|---|
| #35041 descriptor: speed-up `Parse` (xpub/xpriv) in ~30% | Waiting on author | 1 | Strong | 71 | |||
|
|
|
|
| |||
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
Improves descriptor parsing performance by combining extended key decoding (`xpub`/`xprv`) into a single Base58Check decode pass. It also replaces the O(N) linear character scan in descriptor checksum computation with a precomputed 256-byte lookup table, and updates Base58 and key decoding APIs to accept `std::string_view` to avoid string allocations.
Problem: When parsing extended keys, the descriptor parser unconditionally called both `DecodeExtKey()` and `DecodeExtPubKey()`, executing two separate Base58Check passes (including double-SHA256 hashing) even though only one prefix could match. In addition, descriptor checksumming performed linear scans over the character set for every character, and key decoding functions required `const std::string&`, causing redundant heap allocations and slowing down wallet fuzzing and descriptor parsing.
Evidence of importance:
- Author provides microbenchmark results parsing a large multisig Taproot descriptor showing parse time reduced from 127,812 ns/op to 90,253 ns/op (~29.4% speedup) (PR description).
Risk of a mistake: medium (wallet, other). A flaw in Base58 decoding or extended key parsing could cause valid descriptors or keys to be rejected or parsed incorrectly by the wallet.
Changed since the previous assessment: The Base58 decoding helper now caps the initial buffer allocation size using `max_ret_len - zeroes`.
Reviewability
Paused · Waiting on author
Open objection from hodlinator with no author reply for 11 days; review would repeat what has been asked.
Waiting on (from the thread read):
- author: respond to hodlinator's suggestion regarding max_ret_len precondition 2026-10-21
Agreement
Strong
substantive support, no open objection (hodlinator)
Thread notes: Added hodlinator's suggestion to add a precondition or make max_ret_len unsigned, and recorded their latest re-ACK.
Objections, suggestions, and questions:
| Reviewer | Type | Harm, request, or question | Status | Must be | Author replied | Quote |
|---|---|---|---|---|---|---|
| l0rinc (member) | question, approach | would it help if earlier base58 optimization attempts were revived? | answered · author explained prior PRs focused on algorithm rewrite whereas this PR optimizes usage | answered | yes | 2026-04-09: 'would it help if we revived them?' Status from: 2026-04-10: brunoerg: 'I think their focus is to improve/rewrites the algorithm itself, here is more about the way it's been used.' |
| sedited (member) | suggestion, maintenance | make the lookup table constexpr | fixed · fix pushed · author made it constexpr and pushed | answered | yes | 2026-06-07: 'Shouldn't really matter, but I thinks this could be constexpr too.' Status from: 2026-06-10: brunoerg: 'Done.' |
| hodlinator (member) | suggestion, maintenance | change INPUT_CHARSET_POS element type to int8_t to better match range and decrease size | accepted · fix pushed · author implemented int8_t change; hodlinator acknowledged feedback was applied | answered | yes | 2026-08-25: 'Change element type of `INPUT_CHARSET_POS` to `int8_t` to better match range of possible values and decrease size.' Status from: 2026-09-02: hodlinator: 'Thanks for applying most of my feedback!' |
| maflcko (member) | suggestion, interface | pass a string_view to clean up callers | fixed · fix pushed · author updated function to accept string_view | answered | yes | 2026-09-01: 'Could also pass a string view in here to clean up the callers and then derive `psz` and `end` only in the first line of this function?' Status from: 2026-09-02: brunoerg: 'Force-pushed addressing https://github.com/bitcoin/bitcoin/pull/35041#discussion_r3906003696 (string view)' |
| sedited (member) | suggestion, safety | clamp to max size to prevent 32-bit overflow | fixed · fix pushed · author force-pushed fix clamping to max size | answered | yes | 2026-10-19: 'Could take this opportunity and make this operation 32bit overflow-safe by clamping to max size' Status from: 2026-10-19: brunoerg: 'Force-pushed addressing https://github.com/bitcoin/bitcoin/pull/35041/changes/59daebcf05db41d83f9d1974b5c79be3eaf62770#r' |
| hodlinator (member) | suggestion, maintenance | add a precondition assert for max_ret_len >= 0 or make it unsigned instead of std::max(..., 0) | open | answered | no | 2026-10-21: 'Could instead add a precondition for the argument (or make it unsigned)' |
Support:
- sedited (member) (Concept ACK, 2026-06-07): Concept ACK [not substantive]
- hodlinator (member) (ACK, 2026-08-25) [both]: Cleaner higher-level optimization than previously rejected PRs, concrete benefit for fuzz targets, and benchmarked 36.7% speedup in ParseDescriptor
- hodlinator (member) (ACK, 2026-09-02): re-ACK ddcacd25e735dad4868bab7f618961a363f23239 [not substantive]
- hodlinator (member) (ACK, 2026-10-21): re-ACK 61cb3d966a855a551307889daea9c45c2c1db203 [not substantive]
Participants:
- l0rinc (member, question, 1 statements 2026-04-09 to 2026-04-09): asked whether earlier base58 optimization attempts should be revived
- sedited (member, support, 3 statements 2026-06-07 to 2026-10-19): Concept ACK; suggested constexpr and clamping to prevent 32-bit overflow
- hodlinator (member, support, 10 statements 2026-08-25 to 2026-10-21): ACK with benchmark showing 36.7% speedup; suggested int8_t table and max_ret_len precondition; re-ACKed
- maflcko (member, neutral, 1 statements 2026-09-01 to 2026-09-01): suggested passing string_view to simplify callers
Files
0 lines under test/bench/ci.
- src/base58.cpp +19/-13
- src/key_io.cpp +22/-3
- src/script/descriptor.cpp +17/-7
- src/key_io.h +10/-3
- src/base58.h +3/-2
Card
The short text the ranking pass compares across PRs.
This pull request speeds up descriptor parsing for descriptors containing extended public and private keys. The parser previously called both DecodeExtKey and DecodeExtPubKey on each extended key, redundantly running Base58Check and double-SHA256 passes twice per key, while descriptor checksums performed linear charset scans. This PR combines the key decodes into a single Base58Check pass, replaces checksum charset lookups with a 256-byte table, and switches Base58 string APIs to std::string_view. The author reports a 29.4% parsing speedup on a large multisig descriptor in microbenchmarks.
Data
thread read through 2026-10-21 (25 statements), head 61cb3d966a
labels: Descriptors
Categories
Wallet
P3 · speedup (ranking pass: Moved down: sub-millisecond per-descriptor gain mainly benefits fuzzing, not normal wallet use.)
Optimizing descriptor parsing by ~30% via unified Base58Check decoding and character lookup tables is a solid optimization with real value for fuzzing and descriptor loading, but the sub-millisecond savings per descriptor are reasonably deferrable. Reviewers and author confirm a 29–37% speedup on `ParseDescriptor` microbenchmarks (from 127 µs to 90 µs on large multisig descriptors) with concrete benefit for descriptor fuzz targets.
Membership: Touches descriptor parsing and extended key decoding in `src/script/descriptor.cpp` and `src/key_io.cpp`, which are core to wallet key and descriptor management.
Factors: security/stability 0, bug 0, performance 1, user value 1, leverage 1