Test: Cover malformed and truncated tx encodings #36456

pull benthecarman wants to merge 1 commits into bitcoin:master from benthecarman:test-tx-deser-truncation changing 2 files +35 −0
  1. benthecarman commented at 7:21 PM on October 7, 2026: contributor

    Check that every strict prefix of a witness transaction fails to deserialize, and that a witness flag of 0x02, which skips the witness section, is rejected as unknown optional data. Also check that CompactSize prefixes missing some of the bytes they announce fail to read.

    These were discovered by finding gaps in an alternate implementation (bitcoin-s)

  2. DrahtBot added the label Tests on Oct 7, 2026
  3. DrahtBot commented at 7:21 PM on October 7, 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 darosior

    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-->

  4. darosior commented at 7:31 PM on October 7, 2026: member

    Concept ACK

  5. fanquake commented at 6:19 AM on October 16, 2026: member
  6. in src/test/transaction_tests.cpp:1397 in 3ddf182d04 outdated
    1393 | @@ -1394,4 +1394,37 @@ BOOST_AUTO_TEST_CASE(spends_witness_prog)
    1394 |      }
    1395 |  }
    1396 |  
    1397 | +BOOST_AUTO_TEST_CASE(tx_malformed_serialization)
    


    instagibbs commented at 1:50 PM on October 19, 2026:

    suggestion that reads more clear to me personally, and:

    1. Replace truncation checks with a prefix loop
    2. Removes DecodeHexTx checks. DecodeHexTx in general doesn't promise to reject all mutations or truncations
      diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp
      --- a/src/test/transaction_tests.cpp
      +++ b/src/test/transaction_tests.cpp
      @@ -1398,31 +1398,24 @@ BOOST_AUTO_TEST_CASE(tx_malformed_serialization)
       {
      -    // A transaction with one input, one output and a one-element witness,
      split into its fields.
      -    const std::string version{"01000000"};
      -    const std::string marker_flag{"0001"};
      -    const std::string inputs{"01" + std::string(64, '0') + "00000000" "00"
      "ffffffff"};
      -    const std::string outputs{"01" "0100000000000000" "0151"};
      -    const std::string witness{"01" "01aa"};
      -    const std::string locktime{"00000000"};
      -
      +    // A transaction with one input, one output and a one-element witness.
           CMutableTransaction mtx;
      -    BOOST_REQUIRE(DecodeHexTx(mtx, version + marker_flag + inputs + outputs +
      witness + locktime));
      -    BOOST_CHECK_EQUAL(mtx.vin.at(0).scriptWitness.stack.size(), 1U);
      +    mtx.vin.resize(1);
      +    mtx.vin[0].scriptWitness.stack = {{0xaa}};
      +    mtx.vout.emplace_back(1, CScript{} << OP_TRUE);
      +    DataStream ser;
      +    ser << TX_WITH_WITNESS(mtx);
    
      -    // Each malformed serialization must fail to deserialize, and DecodeHexTx
      must
      -    // not fall back to reading the same bytes as some other transaction
      without
      -    // witness data.
      -    const auto check_malformed{[](const std::string& hex, const std::string&
      reason) {
      -        CMutableTransaction tx;
      -        BOOST_CHECK_EXCEPTION(DataStream{ParseHex(hex)} >>
      TX_WITH_WITNESS(tx), std::ios_base::failure, HasReason{reason});
      -        BOOST_CHECK(!DecodeHexTx(tx, hex, /*try_no_witness=*/true,
      /*try_witness=*/true));
      -    }};
      +    CMutableTransaction tx;
      +    DataStream{std::span{ser}} >> TX_WITH_WITNESS(tx);
      +    BOOST_CHECK(CTransaction{tx}.HasWitness());
      +
      +    // Every strict prefix ends inside some field, such as a CompactSize, an 
      output
      +    // script, a witness element or the locktime, and fails to deserialize.
      +    for (size_t len{0}; len < ser.size(); ++len) {
      +        BOOST_CHECK_EXCEPTION(DataStream{std::span{ser}.first(len)} >> 
      TX_WITH_WITNESS(tx), std::ios_base::failure, HasReason{"end of data"});
      +    }
    
           // A flag other than 1 is unknown, including one without the witness bit.
      -    check_malformed(version + "0002" + inputs + outputs + witness + locktime,
      "Unknown transaction optional data");
      -    // A witness element that is longer than the remaining data.
      -    check_malformed(version + marker_flag + inputs + outputs + "01" "06aa" + 
      locktime, "end of data");
      -    // A truncated locktime.
      -    check_malformed(version + marker_flag + inputs + outputs + witness + 
      "000000", "end of data");
      -    // An output script that is truncated after its length.
      -    check_malformed(version + marker_flag + inputs + "01" "0100000000000000" 
      "01", "end of data");
      +    DataStream bad_flag;
      +    bad_flag << mtx.version << uint8_t{0x00} << uint8_t{0x02} << mtx.vin << 
      mtx.vout << mtx.vin[0].scriptWitness.stack << mtx.nLockTime;
      +    BOOST_CHECK_EXCEPTION(bad_flag >> TX_WITH_WITNESS(tx), 
      std::ios_base::failure, HasReason{"Unknown transaction optional data"});
       }
    

    benthecarman commented at 5:18 PM on October 21, 2026:

    done

  7. Test: Cover malformed and truncated tx encodings
    Check that every strict prefix of a witness transaction fails to
    deserialize, and that a witness flag of 0x02, which skips the witness
    section, is rejected as unknown optional data. Also check that
    CompactSize prefixes missing some of the bytes they announce fail to
    read.
    10f7cf8487
  8. benthecarman force-pushed on Oct 21, 2026
  9. in src/test/serialize_tests.cpp:251 in 10f7cf8487
     246 | +{
     247 | +    // A CompactSize prefix followed by fewer bytes than it announces fails to read.
     248 | +    for (const char* hex : {"fd", "fd01", "fe010000", "ff01000000000000"}) {
     249 | +        DataStream ss{ParseHex(hex)};
     250 | +        BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, HasReason{"end of data"});
     251 | +    }
    


    darosior commented at 9:10 PM on October 21, 2026:

    Missed opportunity not to also do too large: "ReadCompactSize(): size too large" doesn't have any coverage.

  10. darosior commented at 9:31 PM on October 21, 2026: member

    ACK 10f7cf848792b040bb3d1393598250c71c5700e0

  11. fanquake merged this on Oct 22, 2026
  12. fanquake closed this on Oct 22, 2026

  13. benthecarman deleted the branch on Oct 22, 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 06:51 UTC

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