Skip to content

BIP174's invalid-witnessScript vector pushes a key that is not a point #408

Description

@fametrano

BIP174's "PSBT with invalid output witness script typed key" pushes a key
that is not a point of secp256k1. The vector is vendored in
tests/psbt/_data/bip174_test_vectors.json, invalid psbts, and the copy
is faithful: bip-0174.mediawiki has the same octets. btclib refuses the
vector for the defect it declares, so the script is never parsed.

The bytes

Its last record, key length included, is

21 010025512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d 06 d57f8a8751ae

Read as a stream, the witnessScript is

OP_1 <03b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d06d57f8a87> OP_1 OP_CHECKMULTISIG

and x**3 + 7 is not a square mod p for that x.

import base64
import json

from btclib.to_pub_key import point_from_pub_key

with open("tests/psbt/_data/bip174_test_vectors.json") as file_:
    vectors = json.load(file_)
raw = base64.b64decode(vectors["invalid psbts"][17]["encoded psbt"])
point_from_pub_key(raw[-36:-3])  # BTClibValueError: not a public key

The sibling "PSBT with invalid output redeem script typed key" carries the
same script with 2b in place of 06, and that key is a point. It is the
only well-formed key in the file — 33 octets, prefix 02 or 03 — that is
not one; the two the file declares invalid, in the input and output BIP32
derivations, are 32 octets, invalid by length.

The risk

The case tests one condition: a PSBT_OUT_WITNESS_SCRIPT key longer than
the one octet the type is. An implementation that checks the pushed key
but not the key length refuses the vector for the wrong reason and the
missing check goes undetected.

Provenance

bitcoin/bips 6107b014 added the case with 2b. 65f0b3dd62ec, "BIP-174:
test data: fix value length", replaced it with 06: with 2b the value
length is 43 where 7 octets remain, so a reader that takes the whole
key-value pair before judging the key hits a short read. That octet is the
29th of the pushed key.

bitcoin/bitcoin's test/functional/data/rpc_psbt.json carries the
pre-65f0b3dd octets, byte for byte.

The fix

The key is not free. sha256 of the script with 2b is
876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65, the
P2WSH the output's redeemScript pushes, and hash160 of that redeemScript
is b921b1ba6f722e4bfa83b6557a3139986a42ec83, the P2SH script_pub_key of
the unsigned transaction's second output. The script as it reads today
hashes to bcea4f011e49b37595c2c24fd4b39077670a5d3a32dd47ed928d4e0dfbb21dc8,
which nothing in the vector commits to. Another point in its place would
mean recomputing both hashes and the unsigned transaction: the octet to
keep is 2b, and what moves is the value length sitting on it.

Upstream, mangling the record as the redeemScript case is mangled: the
extra octet in the key, the whole script in the value.

02 0100 25 512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae

The first 222 of the 264 octets are unchanged; the last record reads

output 1: witness script
  now       key    33 octets: type 0x01 plus 32 of key data
            value   6 octets: the tail of a script the framing cut
  proposed  key     2 octets: type 0x01 plus 1 of key data
            value  37 octets: OP_1 <03b7ce...2bd57f8a87> OP_1 OP_CHECKMULTISIG

Same failure. btclib refuses it with invalid witness script key length: 2 against the current : 33, the vendored error message is the prefix
both share, and tests/psbt passes with the vector replaced.

bitcoin/bips#2238 carries it: its text is in the comment below, its diff
in the one after.

Activity

  1. fametrano commented on Aug 5, 2026

    @fametrano
    MemberAuthor

    The pull request, opened as bitcoin/bips#2238.

    Title: BIP-174: test data: a public key in the witnessScript case

    Body:


    The "PSBT with invalid output witnessScript typed key" case tests one
    condition: a PSBT_OUT_WITNESS_SCRIPT key longer than the one octet the
    type is. Its last record, key length included, is

    21 010025512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d 06 d57f8a8751ae
    

    Read as a stream, the witnessScript is

    OP_1 <03b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d06d57f8a87> OP_1 OP_CHECKMULTISIG
    

    and x**3 + 7 is not a square mod p for that x: the pushed key is not a
    point of secp256k1.

    An implementation that checks the pushed key but not the key length
    refuses the case for the wrong reason and the missing check goes
    undetected.

    65f0b3dd62ecc55e43436173c5f84e893809d0fa replaced 2b with 06 to make
    the value length consistent: with 2b it is 43 where 7 octets remain, and
    a reader that takes the whole key-value pair before judging the key hits a
    short read. That octet is the 29th of the pushed key.

    The case commits to the key with 2b. sha256 of that script is
    876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a65, the
    P2WSH the output's redeemScript pushes, and hash160 of that redeemScript
    is b921b1ba6f722e4bfa83b6557a3139986a42ec83, the P2SH scriptPubKey of the
    unsigned transaction's second output. The script as it reads today hashes
    to bcea4f011e49b37595c2c24fd4b39077670a5d3a32dd47ed928d4e0dfbb21dc8, which
    nothing in the case commits to. Another point in its place would mean
    recomputing both hashes and the unsigned transaction; keeping it means
    moving the value length off it.

    The record below does that, carrying the extra octet in the key and the
    whole script in the value, as the "invalid output redeemScript typed key"
    case does.

    02 0100 25 512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae
    

    Same length as now, same failure, and the first 222 of the 264 octets
    unchanged.

    bitcoin/bitcoin's test/functional/data/rpc_psbt.json carries the
    pre-65f0b3dd octets, byte for byte: that copy has the key and the short
    read. The encoding above serves both.


  2. fametrano commented on Aug 5, 2026

    @fametrano
    MemberAuthor

    The diff.

    diff --git a/bip-0174.mediawiki b/bip-0174.mediawiki
    index 454a1db..640a207 100644
    --- a/bip-0174.mediawiki
    +++ b/bip-0174.mediawiki
    @@ -679,8 +679,8 @@ The following are invalid PSBTs:
     ** Base64 String: <pre>cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAgAAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A</pre>
     
     * Case: PSBT with invalid output witnessScript typed key
    -** Bytes in Hex: <pre>70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c00010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a6521010025512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d06d57f8a8751ae00</pre>
    -** Base64 String: <pre>cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnQbVf4qHUa4A</pre>
    +** Bytes in Hex: <pre>70736274ff0100730200000001301ae986e516a1ec8ac5b4bc6573d32f83b465e23ad76167d68b38e730b4dbdb0000000000ffffffff02747b01000000000017a91403aa17ae882b5d0d54b25d63104e4ffece7b9ea2876043993b0000000017a914b921b1ba6f722e4bfa83b6557a3139986a42ec8387000000000001011f00ca9a3b00000000160014d2d94b64ae08587eefc8eeb187c601e939f9037c00010016001462e9e982fff34dd8239610316b090cd2a3b747cb000100220020876bad832f1d168015ed41232a9ea65a1815d9ef13c0ef8759f64b5b2b278a6502010025512103b7ce23a01c5b4bf00a642537cdfabb315b668332867478ef51309d2bd57f8a8751ae00</pre>
    +** Base64 String: <pre>cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plAgEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A</pre>
     
     * Case: PSBT with unsigned tx serialized with witness serialization format
     ** Bytes in Hex: <pre>70736274ff01007802000000000101268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc78700b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000</pre>
  3. fametrano commented on Aug 6, 2026

    @fametrano
    MemberAuthor

    btclib already tests this vector, unmodified, as part of
    test_invalid_psbt_bip174 (case 18, "PSBT with invalid output witness
    script typed key"). It raises BTClibValueError: invalid witness script key length: 33, matching the vendored error message: deserialize_bytes
    checks the record's key length before the value is ever read, and nothing
    in btclib's psbt code validates a pubkey pushed inside a script. The
    vector cannot fail on "invalid public key" today, regardless of what
    bitcoin/bips#2238 does upstream.

    tests/psbt/_data/bip174_test_vectors.json is pinned to bitcoin/bips
    commit 8c369ac8e6, documented in tests/_data/README.md;
    .github/workflows/vendored-vectors.yml's monthly
    check_vendored_vectors.py reports when that pin falls behind the tip of
    bip-0174.mediawiki. Once bitcoin/bips#2238, or any other fix, merges and
    moves the tip, that check opens "Vendored vectors behind upstream" and
    the vector is refreshed then.

    Nothing to change in btclib until upstream moves.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions