bip352: verify redeem script and pubkey hash in GetPubKeyFromInput #36417

pull Yudis-bit wants to merge 1 commits into bitcoin:master from Yudis-bit:bip352-verify-redeem-script changing 2 files +106 −3
  1. Yudis-bit commented at 1:56 PM on October 2, 2026: none

    In GetPubKeyFromInput(), evaluating TxoutType::SCRIPTHASH (P2SH-P2WPKH) inputs runs EvalScript under SCRIPT_VERIFY_NONE and passes the resulting redeem script to Solver(redeem, solutions). Because Solver clears the passed solutions vector, the 20-byte script hash originally extracted from the UTXO's scriptPubKey is erased without checking that Hash160(redeem) matches it. Consequently, spending an arbitrary P2SH UTXO (such as a multisig or timelock) with a scriptSig that pushes a valid P2WPKH redeem script causes an unintended public key to be extracted and summed into A_sum, silently corrupting the Silent Payments input hash and derived shared secret.

    Additionally, GetPubKeyFromInput() previously accepted malleated or non-standard witness stacks and scriptSigs by grabbing stack.back() without enforcing the BIP-16 push-only requirement (!txin.scriptSig.IsPushOnly()), the BIP-141 single-element redeem script constraint (stack.size() == 1), the standard two-element P2WPKH witness stack depth (txin.scriptWitness.stack.size() == 2), or verifying that the extracted compressed public key actually hashes to the target witness program (Hash160(key) == solutions[0]).

    This change preserves the expected 20-byte script hash prior to solver invocation, enforces BIP-16 push-only and single-element constraints, verifies both redeem script and witness pubkey preimage hashes, and validates exact witness stack depths for both native P2WPKH and P2SH-P2WPKH inputs. Targeted unit test coverage in src/test/bip352_tests.cpp verifies rejection of redeem script mismatches, non-push opcodes, multi-push scriptSigs, pubkey hash mismatches, uncompressed keys, and invalid stack sizes.

  2. Yudis-bit requested review from Copilot on Oct 2, 2026
  3. Copilot commented at 1:56 PM on October 2, 2026: none

    Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

  4. DrahtBot commented at 1:56 PM on October 2, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

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

    Type Reviewers
    ACK fametrano
    Concept NACK rustaceanrob
    Concept ACK w0xlt

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

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #36521 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36521.svg"></sub> (Keep witness stacks in serialized form in CScriptWitness by sipa)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  5. Yudis-bit force-pushed on Oct 6, 2026
  6. DrahtBot added the label Needs rebase on Oct 7, 2026
  7. Yudis-bit force-pushed on Oct 7, 2026
  8. DrahtBot removed the label Needs rebase on Oct 7, 2026
  9. Yudis-bit force-pushed on Oct 8, 2026
  10. w0xlt commented at 5:53 PM on October 8, 2026: contributor

    Concept ACK

  11. fametrano commented at 10:20 AM on October 9, 2026: contributor

    Tested ACK d48b8af

    bip352_tests passes, and five of the new assertions fail with master's bip352.cpp.

    Every input this PR newly rejects already fails consensus: a P2SH scriptSig that is not push-only, a P2SH-P2WPKH scriptSig that is not a single push, a P2WPKH witness without exactly two items, or a redeem script or pubkey that does not match its hash. The multisig spend in the description is invalid, and the BIP-352 test vectors still pass. So this changes nothing for valid transactions: it is a robustness fix, not a fix for an exploitable bug.

    The new stack.size() != 2 checks are not tested on their own. The one- and three-item witnesses in the tests are rejected on master too, because their last item is not a valid pubkey. A three-item witness ending with the matching pubkey would show the difference.

  12. bip352: verify redeem script, push-only scriptSig, and pubkey hash in GetPubKeyFromInput
    In GetPubKeyFromInput, evaluating TxoutType::SCRIPTHASH (P2SH-P2WPKH) inputs
    runs EvalScript under SCRIPT_VERIFY_NONE and passes the resulting redeem script
    to Solver(redeem, solutions). However, Solver immediately clears the solutions
    vector, discarding the original 20-byte script hash from the UTXO's scriptPubKey
    without checking that Hash160(redeem) matches it.
    
    As a result, any non-P2WPKH P2SH UTXO (such as multisig or timelock) spent by an input
    whose scriptSig pushes a valid P2WPKH redeem script is mistakenly treated as an
    eligible BIP-352 input, causing an unintended pubkey to be extracted and summed into
    A_sum, which silently corrupts the derived input_hash and shared secret.
    
    Additionally, GetPubKeyFromInput did not enforce the BIP-16 push-only constraint
    (!txin.scriptSig.IsPushOnly()), the BIP-141 single-element redeem script constraint
    (stack.size() == 1), standard two-element P2WPKH witness stack depths
    (txin.scriptWitness.stack.size() == 2), or that the extracted compressed public key
    hashes to the witness program (Hash160(key) == solutions[0]).
    
    Preserve the expected script hash before Solver runs, enforce BIP-16 push-only and
    single-element constraints, verify both redeem script and witness pubkey preimage hashes,
    and validate exact witness stack depths for both native P2WPKH and P2SH-P2WPKH inputs.
    Targeted unit tests in src/test/bip352_tests.cpp cover redeem script mismatches,
    non-push opcodes, multi-push scriptSigs, pubkey hash mismatches, uncompressed keys,
    and invalid stack sizes.
    c751d92d39
  13. Yudis-bit force-pushed on Oct 9, 2026
  14. Yudis-bit commented at 12:16 PM on October 9, 2026: none

    Good catch. Updated both the P2SH-P2WPKH and native P2WPKH test suites so the 3-item and 1-item witness stacks end with the valid matching compressed pubkey. On master, both cases now fail as expected since master extracts stack.back() whenever it is a valid pubkey regardless of witness depth.

  15. DrahtBot added the label CI failed on Oct 9, 2026
  16. DrahtBot removed the label CI failed on Oct 9, 2026
  17. fametrano commented at 6:49 AM on October 10, 2026: contributor

    re-ACK c751d92d39c02f48f363c91ac7c27b4b7937ea4c

    I checked that the new witness tests cover the stack.size() != 2 checks, which my last review said were untested.

    The range-diff from d48b8af changes only bip352_tests.cpp. The 3-item and 1-item witnesses now end with the matching pubkey.

    On this head, bip352_tests, script_tests and transaction_tests pass. With only the two size() != 2 checks put back to empty(), exactly those four new assertions fail. With master's bip352.cpp, nine assertions fail.

  18. rustaceanrob commented at 12:47 AM on October 18, 2026: member

    Concept NACK adding this. GetPubKeyFromInput assumes inputs have been validated,. This adds pointless hashing to an operation that will be used extensively in scanning.

  19. Yudis-bit commented at 1:40 AM on October 18, 2026: none

    Fair point on confirmed blocks—for transactions already connected to the chain, VerifyScript has already verified the script and witness program hashes.

    The motivation was whether GetPubKeyFromInput in src/common/ should be a self-contained parser safe for unconfirmed transactions (mempool/0-conf) or light-client contexts where VerifyScript hasn't run. In those cases, omitting the hash check allows an invalid P2SH input to inject an arbitrary public key into A_sum, corrupting the shared secret and causing the wallet to miss incoming payments.

    On performance, #36475 showed GetPubKeyFromInput on P2WPKH takes ~2,757 ns (dominated by secp256k1_ec_pubkey_parse), where Hash160 is ~50 ns (<0.1% of the overall scanning loop with ecmult). But if the intended architecture is for GetPubKeyFromInput to strictly assume pre-validated inputs to keep the rescan path minimal, that's completely reasonable.

    Happy to close this if that's the preferred contract, or scope the check strictly to P2SH (where SCRIPT_VERIFY_NONE is used) if unconfirmed robustness is desired.

  20. rustaceanrob commented at 2:24 AM on October 18, 2026: member

    light-client contexts

    Not relevant here

    unconfirmed transactions (mempool/0-conf)

    Scripts are validated before TransactionAddedToMempool

    I don't think this is in scope for the module

  21. Yudis-bit commented at 2:43 AM on October 18, 2026: none

    Makes sense. Closing.

  22. Yudis-bit closed this on Oct 18, 2026


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-10-29 07:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me