psbt: validate pubkeys in MuSig2 pubnonce/partial sig deserialization #34219

pull tboy1337 wants to merge 1 commits into bitcoin:master from tboy1337:fix-musig2-validation-incomplete changing 2 files +23 −0
  1. tboy1337 commented at 12:41 pm on January 7, 2026: none

    The previous fix for invalid MuSig2 pubkeys (bitcoin/bitcoin#34010) only addressed the PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS field. However, the PSBT_IN_MUSIG2_PUB_NONCE and PSBT_IN_MUSIG2_PARTIAL_SIG fields also deserialize pubkeys without validation, which could lead to crashes when invalid pubkeys are processed.

    This commit adds validation to the DeserializeMuSig2ParticipantDataIdentifier function to ensure all pubkeys in MuSig2 pubnonce and partial signature fields are fully valid elliptic curve points.

    The fix:

    • Validates both aggregate and participant pubkeys in MuSig2 pubnonce and partial signature deserialization
    • Throws std::ios_base::failure with descriptive error messages for invalid pubkeys
    • Prevents potential crashes from invalid elliptic curve points
    • Maintains backward compatibility for valid PSBTs

    This completes the fix for issues #33999 and #34201.

  2. DrahtBot added the label PSBT on Jan 7, 2026
  3. DrahtBot commented at 12:41 pm on January 7, 2026: contributor

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

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/34219.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK rkrux

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

  4. maflcko commented at 3:34 pm on January 7, 2026: member

    Please squash your commits according to https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md#squashing-commits

    Also, you’ll have to add tests

  5. tboy1337 force-pushed on Jan 7, 2026
  6. tboy1337 force-pushed on Jan 7, 2026
  7. in test/functional/rpc_psbt.py:307 in 8fde87be2c outdated
    299+        participant1_keydata_invalid_agg = invalid_pubkey_compressed + invalid_agg_pubkey_compressed + fake_leaf_hash
    300+        psbt_invalid_agg = PSBT()
    301+        psbt_invalid_agg.g = PSBTMap({PSBT_GLOBAL_UNSIGNED_TX: tx.serialize()})
    302+        psbt_invalid_agg.i = [PSBTMap({
    303+            bytes([PSBT_IN_MUSIG2_PUB_NONCE]) + participant1_keydata_invalid_agg: randbytes(66),  # fake pubnonce
    304+        })]
    


    w0xlt commented at 9:28 pm on January 7, 2026:

    The PSBT serialization requires the number of output maps to match the number of transaction outputs (1 in this case). This needs to be done to all 5 PSBT creations in test_musig2_invalid_pubkey_validation().

    0        })]
    1        psbt_invalid_part_sig.o = [PSBTMap()]
    
  8. in src/psbt.h:245 in 8fde87be2c
    241@@ -242,8 +242,17 @@ void DeserializeMuSig2ParticipantDataIdentifier(Stream& skey, CPubKey& agg_pub,
    242     std::array<unsigned char, CPubKey::COMPRESSED_SIZE> agg_pubkey_bytes;
    243 
    244     skey >> std::as_writable_bytes(std::span{part_pubkey_bytes}) >> std::as_writable_bytes(std::span{agg_pubkey_bytes});
    245-    agg_pub.Set(agg_pubkey_bytes.begin(), agg_pubkey_bytes.end());
    246-    part_pub.Set(part_pubkey_bytes.begin(), part_pubkey_bytes.end());
    247+    CPubKey agg_pub_candidate(agg_pubkey_bytes);
    


    w0xlt commented at 9:35 pm on January 7, 2026:

    It’s not necessary to create new objects (agg_pub_candidate and part_pub_candidate).

    0    if (!agg_pub.IsFullyValid()) {
    1        throw std::ios_base::failure("musig2 aggregate pubkey is invalid");
    2    }
    3
    4    if (!part_pub.IsFullyValid()) {
    5        throw std::ios_base::failure("musig2 participant pubkey is invalid");
    6    }
    
  9. in src/test/psbt_tests.cpp:1 in 8fde87be2c outdated
    0@@ -0,0 +1,127 @@
    1+// Copyright (c) 2019-present The Bitcoin Core developers
    


    w0xlt commented at 9:43 pm on January 7, 2026:
    There are already PSBT tests in src/wallet/test/psbt_wallet_tests.cpp, so it’d be preferable to add the new ones there instead of introducing a new file.
  10. tboy1337 force-pushed on Jan 7, 2026
  11. tboy1337 requested review from w0xlt on Jan 7, 2026
  12. maflcko commented at 12:23 pm on January 8, 2026: member

    Your added function test passes even before your changes, so this is not a regression test for the code change. Likely, it doesn’t add any test coverage. All of this looks LLM generated.

    If you want to add tests, that is fine, but you’ll have to explain and show what coverage they are adding.

    If you want to fix something, this is fine, but you’ll have to add a regression test that fails before the changes, and passes after them: https://en.wikipedia.org/wiki/Regression_testing

  13. tboy1337 force-pushed on Jan 8, 2026
  14. in test/functional/rpc_psbt.py:404 in 6a1e3620af
    399+        psbt_invalid_agg_output = PSBT()
    400+        psbt_invalid_agg_output.g = PSBTMap({PSBT_GLOBAL_UNSIGNED_TX: tx.serialize()})
    401+        psbt_invalid_agg_output.i = [PSBTMap()]
    402+        psbt_invalid_agg_output.o = [PSBTMap({
    403+            bytes([PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS]) + invalid_agg_pubkey_compressed: [valid_pubkey1, valid_pubkey2],
    404+        })]
    


    rkrux commented at 12:09 pm on January 9, 2026:
    These 4 tests are not related to this fix and such errors were tested in PR #34010.
  15. in test/functional/rpc_psbt.py:362 in 6a1e3620af
    357+
    358+        # This should not raise an error
    359+        result = self.nodes[0].decodepsbt(psbt_valid.to_base64())
    360+        assert_equal(len(result["inputs"]), 1)
    361+        assert "musig2_pubnonces" in result["inputs"][0]
    362+        assert "musig2_partial_sigs" in result["inputs"][0]
    


    rkrux commented at 12:10 pm on January 9, 2026:
    Adding this test is not needed because the several existing cases for the valid PSBTs should suffice.
  16. in test/functional/rpc_psbt.py:345 in 6a1e3620af
    340+        psbt_invalid_part_sig.o = [PSBTMap()]
    341+        psbt_invalid_part_sig.i = [PSBTMap({
    342+            bytes([PSBT_IN_MUSIG2_PARTIAL_SIG]) + participant1_keydata_invalid_part_sig: randbytes(32),  # fake partial sig
    343+        })]
    344+
    345+        assert_raises_rpc_error(-22, "musig2 participant pubkey is invalid", self.nodes[0].decodepsbt, psbt_invalid_part_sig.to_base64())
    


    rkrux commented at 12:14 pm on January 9, 2026:

    While these 4 tests do indeed check the newly added fix, but I think these cases are too customised and also increases the diff a bit. I created 4 invalid PSBTs from a valid PSBT that contain the invalid pubkeys:

    1. invalid aggregate pubkey in PSBT pubnonce key
    2. invalid participant pubkey in PSBT partial signature key
    3. invalid aggregate pubkey in PSBT pubnonce key
    4. invalid participant pubkey in PSBT partial signature key

    Consider adding this diff that passes with this fix:

     0diff --git a/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json
     1index 752fce558f..abc09d523a 100644
     2--- a/test/functional/data/rpc_psbt.json
     3+++ b/test/functional/data/rpc_psbt.json
     4@@ -106,6 +106,22 @@
     5         [
     6             "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",
     7             "Size of value was not the stated size"
     8+        ],
     9+        [
    10+            "cHNidP8BAFICAAAAASWJ53Z5WLoVT5AYzM8N7ephR7tgzRoZS241kKmWVpDWAQAAAAD9////ARjd9QUAAAAAFgAUyRI+BujX8JZsXRzQ+TMALU63V80AAAAAAAEBKwDh9QUAAAAAIlEg0LImxlmfJzh034/mhKtsMCgIG+6KLL7TGhNvWGX2z6QhFjRrmVkzVxB8nTRZ6d66jT6vROZjbIXH+FPrkLpS6M0ABQBYCwiHIRZPr9ZfgWkYb8K/2yIzx35jDRC+KAokxxZcCaJ2EXdcLAUAwySagiEWjdlquFiyWcUYIYwBSkbrTmrImeUcZ173dPu2ioeZzi8NACaA3W4BAAAAAgAAACEW+TCKAZJYwxBJNE+F+J1SKbUxyEWDb5mwhgHxE7zgNvkFAH3WVZIBFyCN2Wq4WLJZxRghjAFKRutOasiZ5RxnXvd0+7aKh5nOLyIaAwtY4zeqTThSqMKTh8QkCNjPvjphOl45fgqfAaX7cQfUYwI0a5lZM1cQfJ00Weneuo0+r0TmY2yFx/hT65C6UujNAAJPr9ZfgWkYb8K/2yIzx35jDRC+KAokxxZcCaJ2EXdcLAL5MIoBkljDEEk0T4X4nVIptTHIRYNvmbCGAfETvOA2+UMbAk+v1l+BaRhvwr/bIjPHfmMNEL4oCiTHFlwJonYRd1wsAtCyJsZZnyc4dN+P5oSrbDAoCBvuiiy+0xoTb1hl9s+kQgOjJKP0Ihv6srb6B4anBI8zRc40TxRY4VG6GHtZqrSYywKjY4JZukzMRv552NeaTZ5wTsD3cBteZk1NhzODivSRlkMbAvkwigGSWMMQSTRPhfidUim1MchFg2+ZsIYB8RO84Db5AtCyJsZZnyc4dN+P5oSrbDAoCBvuiiy+0xoTb1hl9s+kQgLBLrTvh2AyHAcqUdj7ZcNO6LRSoUgYVX9h3wYShaWAkQOgjkGyYpQsblky/R+12ZI2iXmJ9ukS3CFHbSdxh0EUk0Mb0kSzWjtpjQM5dsRU2Eg/PTsX6bcFgeuZWOqYfPkzyBX2AtCyJsZZnyc4dN+P5oSrbDAoCBvuiiy+0xoTb1hl9s+kQgJO78n90SvnR0ZIXGeLgmiUnMkjbp/OgiQTlVI68EJivwOMJ26DKq1L+56QSFFi9XTIsmGd9b0Z24/6LrAFlJO/GwAA",
    11+            "musig2 participant pubkey is invalid"
    12+        ],
    13+        [
    14+            "cHNidP8BAFICAAAAASWJ53Z5WLoVT5AYzM8N7ephR7tgzRoZS241kKmWVpDWAQAAAAD9////ARjd9QUAAAAAFgAUyRI+BujX8JZsXRzQ+TMALU63V80AAAAAAAEBKwDh9QUAAAAAIlEg0LImxlmfJzh034/mhKtsMCgIG+6KLL7TGhNvWGX2z6QBE0CeOYl6wv/idSXcRg+FhP3dEf6al84uUMFIm4waTpL8wH5I22OhpMy50pdTfQwDiDg3i78njeeqGhKJldFiXMXNIRY0a5lZM1cQfJ00Weneuo0+r0TmY2yFx/hT65C6UujNAAUAWAsIhyEWT6/WX4FpGG/Cv9siM8d+Yw0QvigKJMcWXAmidhF3XCwFAMMkmoIhFo3ZarhYslnFGCGMAUpG605qyJnlHGde93T7toqHmc4vDQAmgN1uAQAAAAIAAAAhFvkwigGSWMMQSTRPhfidUim1MchFg2+ZsIYB8RO84Db5BQB91lWSARcgjdlquFiyWcUYIYwBSkbrTmrImeUcZ173dPu2ioeZzi8iGgMLWOM3qk04UqjCk4fEJAjYz746YTpeOX4KnwGl+3EH1GMCNGuZWTNXEHydNFnp3rqNPq9E5mNshcf4U+uQulLozQACT6/WX4FpGG/Cv9siM8d+Yw0QvigKJMcWXAmidhF3XCwC+TCKAZJYwxBJNE+F+J1SKbUxyEWDb5mwhgHxE7zgNvlDGwI0a5lZM1cQfJ00Weneuo0+r0TmY2yFx/hT65C6UujNAALQsibGWZ8nOHTfj+aEq2wwKAgb7oosvtMaE29YZfbPpEICTu/J/dEr50dGSFxni4JolJzJI26fzoIkE5VSOvBCYr8DjCdugyqtS/uekEhRYvV0yLJhnfW9GduP+i6wBZSTvxtDGwJPr9ZfgWkYb8K/2yIzx35jDRC+KAokxxZcCaJ2EXdcLALQsibGWZ8nOHTfj+aEq2wwKAgb7oosvtMaE29YZfbPpEIDoySj9CIb+rK2+geGpwSPM0XONE8UWOFRuhh7Waq0mMsCo2OCWbpMzEb+edjXmk2ecE7A93AbXmZNTYczg4r0kZZDGwL5MIoBkljDEEk0T4X4nVIptTHIRYNvmbCGAfETvOA2+QLQsibGWZ8nOHTfj+aEq2wwKAgb7oosvtMaE29YZfbPpEICwS6074dgMhwHKlHY+2XDTui0UqFIGFV/Yd8GEoWlgJEDoI5BsmKULG5ZMv0ftdmSNol5ifbpEtwhR20ncYdBFJNDHAJPr9ZfgWkYb8K/2yIzx35jDRC+KAokxxZcCaJ2EXdcLALQsibGWZ8nOHTfj+aEq2wwKAgb7oosvtMaE29YZfbPpCDnhVL7TOmy0A4e0OLLmOCHGZEnR36PGhxoGgC5xM5wmEMcAvkwigGSWMMQSTRPhfidUim1MchFg2+ZsIYB8RO84Db5AtCyJsZZnyc4dN+P5oSrbDAoCBvuiiy+0xoTb1hl9s+kIMvMlXhqHGdNSNisUuMi3Xxuq6w7/Evnm3UN4o34Li9zQxy9GBUQ2ZeszqyEQK2COnd79TGhILEsdA0ocnS0vqs9z/IC0LImxlmfJzh034/mhKtsMCgIG+6KLL7TGhNvWGX2z6QgZX2yhr4U6A7ODdhLTRfE3EFMPla8jO8IJ7BhtN0sj0MAAA==",
    15+            "musig2 participant pubkey is invalid"
    16+        ],
    17+        [
    18+            "cHNidP8BAFICAAAAASWJ53Z5WLoVT5AYzM8N7ephR7tgzRoZS241kKmWVpDWAQAAAAD9////ARjd9QUAAAAAFgAUyRI+BujX8JZsXRzQ+TMALU63V80AAAAAAAEBKwDh9QUAAAAAIlEg0LImxlmfJzh034/mhKtsMCgIG+6KLL7TGhNvWGX2z6QhFjRrmVkzVxB8nTRZ6d66jT6vROZjbIXH+FPrkLpS6M0ABQBYCwiHIRZPr9ZfgWkYb8K/2yIzx35jDRC+KAokxxZcCaJ2EXdcLAUAwySagiEWjdlquFiyWcUYIYwBSkbrTmrImeUcZ173dPu2ioeZzi8NACaA3W4BAAAAAgAAACEW+TCKAZJYwxBJNE+F+J1SKbUxyEWDb5mwhgHxE7zgNvkFAH3WVZIBFyCN2Wq4WLJZxRghjAFKRutOasiZ5RxnXvd0+7aKh5nOLyIaAwtY4zeqTThSqMKTh8QkCNjPvjphOl45fgqfAaX7cQfUYwI0a5lZM1cQfJ00Weneuo0+r0TmY2yFx/hT65C6UujNAAJPr9ZfgWkYb8K/2yIzx35jDRC+KAokxxZcCaJ2EXdcLAL5MIoBkljDEEk0T4X4nVIptTHIRYNvmbCGAfETvOA2+UMbAk+v1l+BaRhvwr/bIjPHfmMNEL4oCiTHFlwJonYRd1wsAtCyJsZZnyc4dN+P5oSrbDAoCBvuiiy+0xoTb1hl9s+kQgOjJKP0Ihv6srb6B4anBI8zRc40TxRY4VG6GHtZqrSYywKjY4JZukzMRv552NeaTZ5wTsD3cBteZk1NhzODivSRlkMbAvkwigGSWMMQSTRPhfidUim1MchFg2+ZsIYB8RO84Db5AtCyJsZZnyc4dN+P5oSrbDAoCBvuiiy+0xoTb1hl9s+kQgLBLrTvh2AyHAcqUdj7ZcNO6LRSoUgYVX9h3wYShaWAkQOgjkGyYpQsblky/R+12ZI2iXmJ9ukS3CFHbSdxh0EUk0MbAjRrmVkzVxB8nTRZ6d66jT6vROZjbIXH+FPrkLpS6M0A24oIbe9JQsYZIdUKU0vvOQHc/23mGL2R5js8lN/JmLWHQgJO78n90SvnR0ZIXGeLgmiUnMkjbp/OgiQTlVI68EJivwOMJ26DKq1L+56QSFFi9XTIsmGd9b0Z24/6LrAFlJO/GwAA",
    19+            "musig2 aggregate pubkey is invalid"
    20+        ],
    21+        [
    22+            "cHNidP8BAFICAAAAASWJ53Z5WLoVT5AYzM8N7ephR7tgzRoZS241kKmWVpDWAQAAAAD9////ARjd9QUAAAAAFgAUyRI+BujX8JZsXRzQ+TMALU63V80AAAAAAAEBKwDh9QUAAAAAIlEg0LImxlmfJzh034/mhKtsMCgIG+6KLL7TGhNvWGX2z6QBE0CeOYl6wv/idSXcRg+FhP3dEf6al84uUMFIm4waTpL8wH5I22OhpMy50pdTfQwDiDg3i78njeeqGhKJldFiXMXNIRY0a5lZM1cQfJ00Weneuo0+r0TmY2yFx/hT65C6UujNAAUAWAsIhyEWT6/WX4FpGG/Cv9siM8d+Yw0QvigKJMcWXAmidhF3XCwFAMMkmoIhFo3ZarhYslnFGCGMAUpG605qyJnlHGde93T7toqHmc4vDQAmgN1uAQAAAAIAAAAhFvkwigGSWMMQSTRPhfidUim1MchFg2+ZsIYB8RO84Db5BQB91lWSARcgjdlquFiyWcUYIYwBSkbrTmrImeUcZ173dPu2ioeZzi8iGgMLWOM3qk04UqjCk4fEJAjYz746YTpeOX4KnwGl+3EH1GMCNGuZWTNXEHydNFnp3rqNPq9E5mNshcf4U+uQulLozQACT6/WX4FpGG/Cv9siM8d+Yw0QvigKJMcWXAmidhF3XCwC+TCKAZJYwxBJNE+F+J1SKbUxyEWDb5mwhgHxE7zgNvlDGwI0a5lZM1cQfJ00Weneuo0+r0TmY2yFx/hT65C6UujNAALQsibGWZ8nOHTfj+aEq2wwKAgb7oosvtMaE29YZfbPpEICTu/J/dEr50dGSFxni4JolJzJI26fzoIkE5VSOvBCYr8DjCdugyqtS/uekEhRYvV0yLJhnfW9GduP+i6wBZSTvxtDGwJPr9ZfgWkYb8K/2yIzx35jDRC+KAokxxZcCaJ2EXdcLALQsibGWZ8nOHTfj+aEq2wwKAgb7oosvtMaE29YZfbPpEIDoySj9CIb+rK2+geGpwSPM0XONE8UWOFRuhh7Waq0mMsCo2OCWbpMzEb+edjXmk2ecE7A93AbXmZNTYczg4r0kZZDGwL5MIoBkljDEEk0T4X4nVIptTHIRYNvmbCGAfETvOA2+QLQsibGWZ8nOHTfj+aEq2wwKAgb7oosvtMaE29YZfbPpEICwS6074dgMhwHKlHY+2XDTui0UqFIGFV/Yd8GEoWlgJEDoI5BsmKULG5ZMv0ftdmSNol5ifbpEtwhR20ncYdBFJNDHAJPr9ZfgWkYb8K/2yIzx35jDRC+KAokxxZcCaJ2EXdcLALQsibGWZ8nOHTfj+aEq2wwKAgb7oosvtMaE29YZfbPpCDnhVL7TOmy0A4e0OLLmOCHGZEnR36PGhxoGgC5xM5wmEMcAvkwigGSWMMQSTRPhfidUim1MchFg2+ZsIYB8RO84Db5AtCyJsZZnyc4dN+P5oSrbDAoCBvuiiy+0xoTb1hl9s+kIMvMlXhqHGdNSNisUuMi3Xxuq6w7/Evnm3UN4o34Li9zQxwCNGuZWTNXEHydNFnp3rqNPq9E5mNshcf4U+uQulLozQBq5voHDYXyLgYYDZRwd2Cemo5Lf+nNM2aIGiiZxmFnVU8gZX2yhr4U6A7ODdhLTRfE3EFMPla8jO8IJ7BhtN0sj0MAAA==",
    23+            "musig2 aggregate pubkey is invalid"
    24         ]
    25     ],
    26     "valid" : [
    
  17. in test/functional/rpc_psbt.py:280 in 6a1e3620af
    273@@ -274,6 +274,137 @@ def test_decodepsbt_musig2_input_output_types(self):
    274         assert "participant_pubkeys" in out_participant_pks
    275         assert_equal(out_participant_pks["participant_pubkeys"], [out_pubkey1.hex(), out_pubkey2.hex()])
    276 
    277+    def test_musig2_invalid_pubkey_validation(self):
    278+        self.log.info("Test MuSig2 pubkey validation prevents acceptance of invalid pubkeys in PSBT deserialization")
    279+        self.log.info("This is a regression test ensuring that invalid pubkeys in MuSig2 fields are properly rejected")
    280+        self.log.info("rather than being accepted and potentially causing crashes or invalid behavior later")
    


    rkrux commented at 12:15 pm on January 9, 2026:
    Such comments are not needed. In light of the previously mentioned comments in this file, I suggest to remove the tests in this file in favour of adding in the rpc_psbt.json like suggested in another comment.
  18. in src/wallet/test/psbt_wallet_tests.cpp:1 in 6a1e3620af


    rkrux commented at 12:16 pm on January 9, 2026:
    I don’t think these unit tests are needed when we can add more functional tests easily.
  19. rkrux commented at 12:29 pm on January 9, 2026: contributor

    Concept ACK 6a1e3620aff758eee82840790e0f748474287558

    Thanks for raising this PR, these checks for the aggregate and participant pubkeys in the MuSig2 pubnonce and partial signatures PSBT keys seem fine to me. BIP Reference: https://github.com/bitcoin/bips/blob/master/bip-0373.mediawiki

    In the absence of this fix, such invalid PSBTs get parsed successfully with empty participant and aggregate pubkeys that might later cause some flow (eg: signing) to crash like the earlier fuzz crashes found out.

    I used the following code to generate cases that I have suggested in the inline comments.

     0diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
     1index cc443f6a7c..9d30d571ff 100755
     2--- a/test/functional/rpc_psbt.py
     3+++ b/test/functional/rpc_psbt.py
     4@@ -407,6 +407,31 @@ class PSBTTest(BitcoinTestFramework):
     5         self.log.info("PSBT parameter handling test completed successfully")
     6 
     7     def run_test(self):
     8+        encoded_psbts = [
     9+            "",
    10+        ]
    11+        old_key = None
    12+        value = None
    13+        new_key = None
    14+        for encoded in encoded_psbts:
    15+            decoded = PSBT.from_base64(encoded)
    16+            for k, v in decoded.i[0].map.items():
    17+                if type(k) == bytes and k.hex().startswith('1b'):
    18+                    old_key = k
    19+                    value = v
    20+
    21+                if old_key:
    22+                    hex = old_key.hex()
    23+                    new_hex = hex[0:2] + randbytes(33).hex() + hex[68:]
    24+                    new_key = bytes.fromhex(new_hex)
    25+                    break
    26+
    27+            del decoded.i[0].map[old_key]
    28+            decoded.i[0].map[new_key] = value
    29+            invalid_psbt = decoded.to_base64()
    30+            print(invalid_psbt)
    31+        return
    32+
    33         # Create and fund a raw tx for sending 10 BTC
    34         psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.nodes[2].getnewaddress():10})['psbt']
    
  20. DrahtBot added the label CI failed on Jan 11, 2026
  21. psbt: validate pubkeys in MuSig2 pubnonce/partial sig deserialization
    Add validation for pubkeys in MuSig2 pubnonce and partial signature deserialization to prevent crashes with invalid curve points.
    
    - Validate aggregate and participant pubkeys in PSBT MuSig2 fields
    
    - Add comprehensive test coverage for invalid pubkey rejection
    
    - Ensure proper error handling during PSBT deserialization
    f51665bee7
  22. tboy1337 force-pushed on Jan 12, 2026
  23. tboy1337 requested review from rkrux on Jan 12, 2026
  24. DrahtBot removed the label CI failed on Jan 13, 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-01-17 21:13 UTC

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