#35662 script: prevent partial reinitialization of sighash data
| Reviewability | Reviews | Agreement | Size | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| #35662 script: prevent partial reinitialization of sighash data | Needs rebase | 1 | Strong | 35 + 55 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
Removes default construction and the public `Init()` method from `PrecomputedTransactionData`, requiring transaction context and optional spent outputs at construction time. Validation and test call sites that defer precomputation now use `std::optional<PrecomputedTransactionData>`, emplacing the object only after a script-cache miss. The PR also skips txdata allocation during block connection when assumevalid bypasses script checks, and eliminates an unintended copy of caller-supplied txdata in the kernel C API.
Problem: Default-constructing `PrecomputedTransactionData` and initializing it later through `Init()` allowed instances to exist uninitialized or be reused across transactions. The old `Init()` safety guard checked only `m_spent_outputs_ready`, which stayed false after forced initialization without spent outputs even though BIP143 sighash hashes were cached. Additionally, the experimental kernel C verification API copied caller txdata on every call due to ternary expression typing, and `ConnectBlock()` allocated unused txdata memory during assumevalid verification.
Evidence of importance:
- PR description: "The old Init() guard only checked m_spent_outputs_ready, which remained false after forced initialization without spent outputs even though force=true cached the BIP143 prevout, sequence, and output hashes for witness-v0 sighash."
- Commit d07c727ff8: "Reproducer: https://godbolt.org/z/PzcKGW9da (GCC 15.2: one copy before, none after)."
- PR description references closed PR #35663 ("validation: avoid preallocating unused txdata during assumevalid"), whose memory optimization is incorporated directly into ConnectBlock().
Scope: Touches 17 files, but most edits are mechanical adaptations across call sites (PSBT, wallet feebumper, fuzz targets, test fixtures) replacing two-step `PrecomputedTransactionData` initialization with direct construction or `std::optional` wrapping.
Risk of a mistake: medium (consensus, validation). A mistake in initializing or binding PrecomputedTransactionData could corrupt sighash computations during script execution, causing invalid transactions to be accepted or valid blocks to be rejected.
Reviewability
Ready · Needs rebase
Conflicts with master; review the logic now, the rebase is mechanical.
Waiting on (from the thread read):
- reviewer: review of the updated stack and kernel API fix from theuni or other reviewers 2026-09-22
Agreement
Strong
substantive support, no open objection (fametrano)
Thread notes: Added sedited's suggestion to remove Init() and question on staleness, answered by fametrano who also provided a substantive Tested ACK. / Added sedited's suggestion to remove Init entirely and question regarding staleness. Added fametrano's tested ACK and answer to sedited's question.
Objections, suggestions, and questions:
| Reviewer | Type | Harm, request, or question | Status | Must be | Author replied | Quote |
|---|---|---|---|---|---|---|
| sedited (member) | objection, approach | relaxing the requirement against re-initialization and leaving a reuse path open instead of enforcing initialization at construction | fixed · fix pushed · Init was removed and initialization moved to construction, which sedited then ACKed | changed | yes | 2026-07-08: 'We should never be hitting a code path where a re-initialization is possible, and this seems to relax that requirement. I think rather than messing with `Init`, we should seriously reconsider whether this data can be initialized at construction.' Status from: 2026-07-09: l0rinc: 'Pushed: `PrecomputedTransactionData` is no longer default-constructible, public `Init()` is gone, and each object is built for one transaction.' |
| sedited (member) | question, correctness | which staleness is specifically being prevented by this change | answered · fametrano detailed how sequential Init calls across transactions retained cached BIP143 hashes | answered | by others | 2026-09-22: 'Which staleness are we exactly preventing with this change?' Status from: 2026-10-25: fametrano: 'On which staleness this prevents: on the base (7988380da9), `Init(A, {})` for a transaction with a witness, then `Init(B, spent_outputs)` for one without, passes the old `assert(!m_spent_outputs_ready)`' |
| sedited (member) | suggestion, maintenance | remove Init() entirely rather than keeping it as a helper | fixed · fix pushed · author removed Init() and moved its body directly into the constructor | answered | yes | 2026-09-22: 'Why not remove it entirely? The diff from doing so is small.' Status from: 2026-09-22: l0rinc: 'I removed `Init()` entirely and moved its body into the constructor - beautiful!' |
Support:
- sedited (member) (ACK, 2026-09-22): ACK on the changes [not substantive]
- fametrano (contributor) (Tested ACK, 2026-10-25) [both: kernel API, libbitcoinkernel, src/script/interpreter.cpp]: differential sighash testing showed equivalence across 5961 BIP143 and BIP341 key paths, unit and kernel tests passed, and kernel API benchmark showed orders-of-magnitude speedup by eliminating txdata copying
Participants:
- sedited (member, support, 3 statements 2026-07-08 to 2026-09-22): ACKed the changes; earlier objection and suggestion to eliminate Init() were adopted
- fametrano (contributor, support, 1 statements 2026-10-25 to 2026-10-25): Tested ACK with sighash differential testing and kernel API benchmarks
Files
81 lines under test/bench/ci.
- src/test/sighash_tests.cpp +37/-3
- src/validation.cpp +14/-14
- src/script/interpreter.cpp +4/-14
- src/psbt.cpp +5/-9
- src/test/txvalidationcache_tests.cpp +8/-6
- src/kernel/bitcoinkernel.cpp +4/-8
- src/script/interpreter.h +4/-8
- src/script/sign.cpp +2/-5
- src/test/fuzz/script_assets_test_minimizer.cpp +2/-4
- src/test/script_assets_tests.cpp +2/-4
- src/bench/verify_script.cpp +1/-2
- src/signet.cpp +1/-2
- src/test/descriptor_tests.cpp +1/-2
- src/test/fuzz/script_flags.cpp +1/-2
- src/test/fuzz/script_sigcache.cpp +2/-1
- src/test/script_tests.cpp +1/-2
- src/wallet/feebumper.cpp +1/-2
Card
The short text the ranking pass compares across PRs.
Removes default construction and the public Init() method from PrecomputedTransactionData, ensuring instances are immutably bound to a specific transaction and spent outputs upon construction. Validation and tests now hold an std::optional to defer construction until a script-cache miss, while ConnectBlock skips allocating txdata vectors entirely when assumevalid bypasses script verification. It also fixes an unintended copy of caller-supplied txdata on every call in the experimental bitcoinkernel C API, corroborated by a compiler reproducer.
Data
thread read through 2026-10-25 (9 statements), head 35a8964f88
labels: Consensus, Needs rebase
Categories
Kernel (libbitcoinkernel)
P3 · speedup
Eliminating txdata copying in the experimental kernel C verification API resolves a significant performance issue, yielding orders-of-magnitude speedups in kernel API benchmarks.
Membership: src/kernel/bitcoinkernel.cpp is modified to eliminate an unintended copy of caller-supplied txdata in the kernel C verification API.
Factors: security/stability 1, bug 1, performance 1, user value 0, leverage 0
Validation
P3 · cleanup (ranking pass: Moved down: hardening of an error-prone pattern, no active bug, sits with the other defensive validation refactors)
The PR removes an error-prone stateful reinitialization pattern in PrecomputedTransactionData that allowed instances to be partially reinitialized or reused across transactions, and avoids allocating unused txdata during assumevalid block connection.
Membership: Labeled Consensus and modifies src/script/interpreter.{h,cpp} and src/validation.cpp.
Factors: security/stability 1, bug 1, performance 1, user value 0, leverage 0