Load PSBT error: Unable to decode PSBT #30070

issue foolbear openend this issue on May 9, 2024
  1. foolbear commented at 5:34 am on May 9, 2024: none

    Is there an existing issue for this?

    • I have searched the existing issues

    Current behaviour

    “Load PSBT from …” from menu, error shows: Unable to decode PSBT Unsigned tx does not have empty scriptSigs and scriptWitnesses.: unspecified iostream_category error

    decodepsbt and analyzepsbt show the same error too: TX decode failed Unsigned tx does not have empty scriptSigs and scriptWitnesses.: unspecified iostream_category error (code -22)

    Expected behaviour

    I expect Load PSBT success.

    And same procedure works fine in another wallet of test net. Both wallets are not watch-only

    Steps to reproduce

    I test PSBT in my wallet today, create a transaction in Bitcoin Core, click “send” and “create unsigned”, then “Load PSBT from …” from menu

    I’m using prune mode for block chain. This address I made tx was obtained by importprivkey, and then covert to Descriptor Wallets by migratewallet.

    I found the source code:

                 case PSBT_GLOBAL_UNSIGNED_TX:
                {
                    if (!key_lookup.emplace(key).second) {
                        throw std::ios_base::failure("Duplicate Key, unsigned tx already provided");
                    } else if (key.size() != 1) {
                        throw std::ios_base::failure("Global unsigned tx key is more than one byte type");
                    }
                    CMutableTransaction mtx;
                    // Set the stream to serialize with non-witness since this should always be non-witness
                    UnserializeFromVector(s, TX_NO_WITNESS(mtx));
                    tx = std::move(mtx);
                    // Make sure that all scriptSigs and scriptWitnesses are empty
                    for (const CTxIn& txin : tx->vin) {
                        if (!txin.scriptSig.empty() || !txin.scriptWitness.IsNull()) {
     ---->                  throw std::ios_base::failure("Unsigned tx does not have empty scriptSigs and scriptWitnesses.");
                        }
                    }
                    break;
                }
    

    Relevant log output

    no relevant log.

    How did you obtain Bitcoin Core

    Pre-built binaries

    What version of Bitcoin Core are you using?

    Bitcoin Core version v27.0.0

    Operating system and version

    MacOS 14.4.1

    Machine specifications

    No response

  2. willcl-ark added the label PSBT on May 9, 2024
  3. maflcko commented at 1:06 pm on May 13, 2024: member
    Are you able to reproduce in regtest, with exact steps to reproduce?
  4. foolbear commented at 1:25 pm on May 13, 2024: none

    keywords: not watch-only wallet, prune mode, importprivkey, migratewallet.

    “And same procedure works fine in another wallet of test net”, but I cannot make an address with UTXOs from years ago(maybe have something to do with prune mode?).

  5. maflcko added the label Wallet on May 13, 2024
  6. foolbear commented at 9:37 am on May 28, 2024: none
    In the above issue, PSBT was created from GUI - send - create unsigned. If I create it using ‘walletcreatefundedpsbt,’ there is no problem. in main net.
  7. willcl-ark commented at 1:38 pm on June 26, 2024: member

    @foolbear are you able to provide some concrete steps to reproduce this? I can’t get it to happen in a few quick tests on my side.

    Without reproduction steps it’s likely going to be difficult to fix, and perhaps we should close the issue for now until we get verified repro steps.

  8. foolbear commented at 4:33 pm on June 26, 2024: none
    1. run bitcoin core in prune mode
    2. create a new blank wallet
    3. importprivkey from an old private key
    4. migratewallet
    5. re-sync blockchain
    6. show the balance
    7. create a transaction using GUI, click “send” and “create unsigned”.
    8. then “Load PSBT from clipboard” from menu, error fired.
  9. vasild commented at 2:20 pm on November 14, 2025: contributor

    I can reproduce this. A cursory debug shows that txin.scriptWitness IsNull but txin.scriptSig is not empty:

    https://github.com/bitcoin/bitcoin/blob/e221b2524659d22cc5a2b0d0023254115a7a5622/src/psbt.h#L1253-L1254

    A transaction created via the RPC does not exhibit this problem. Only when created from the GUI.

  10. 151henry151 commented at 10:21 pm on November 14, 2025: contributor

    It looks to me like GUI’s “Create unsigned” path still signs the transaction, so legacy inputs end up with non-empty scriptSig and the PSBT parser rejects them. Perhaps we could call CreateTransaction with signing disabled or explicitly clear each scriptSig/scriptWitness before building the PSBT, or maybe it would be better to reuse the RPC PSBT builder so the GUI and RPC share the same logic.

    I haven’t attempted to reproduce yet, just read through the issue and thinking about possible solutions.

  11. vasild commented at 11:16 am on November 18, 2025: contributor

    Indeed the GUI signs the transaction and the RPC does not. Here is the GUI code path:

    0SendCoinsDialog::sendButtonClicked()
    1  SendCoinsDialog::PrepareSendText()
    2    WalletModel::prepareTransaction() // result saved in SendCoinsDialog::m_current_transaction
    3      WalletImpl::createTransaction(/*sign=*/!wallet().privateKeysDisabled()) // sign=true
    4        CreateTransaction(sign=true)
    

    walletcreatefundedpsbt RPC code path:

    0FundTransaction()
    1  CreateTransaction(sign=false)
    

    Why is this a problem only when spending from P2SH addresses? PartiallySignedTransaction::Serialize() serializes the underlying transaction without a witness: SerializeToVector(s, TX_NO_WITNESS(*tx));, so it drops the scriptWitness from the inputs, but still includes scriptSig.

    Here is a q&d patch that fixes the problem by removing scriptSigs from the transaction when “Create Unsigned” is clicked in the GUI:

     0diff --git i/src/qt/sendcoinsdialog.cpp w/src/qt/sendcoinsdialog.cpp
     1index 3962dd0887..2ce657df7c 100644
     2--- i/src/qt/sendcoinsdialog.cpp
     3+++ w/src/qt/sendcoinsdialog.cpp
     4@@ -501,12 +501,15 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
     5     }
     6 
     7     bool send_failure = false;
     8     if (retval == QMessageBox::Save) {
     9         // "Create Unsigned" clicked
    10         CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
    11+        for (CTxIn& txin : mtx.vin) {
    12+            txin.scriptSig.clear();
    13+        }
    14         PartiallySignedTransaction psbtx(mtx);
    15         bool complete = false;
    16         // Fill without signing
    17         const auto err{model->wallet().fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete)};
    18         assert(!complete);
    19         assert(!err);
    

    Probably a better solution would be to change PartiallySignedTransaction::Serialize() to omit scriptSigs just like it omits scriptWitnesses? @achow101, @ishaanam?

  12. achow101 commented at 8:01 pm on November 18, 2025: member
    I think it would be better to have the prepareTransaction have a sign parameter that is set if “Create Unsigned” is selected. Having PSBT silently drop scriptSigs (and scriptWitnesses too) feels like there is the possibility for misuse/unexpected behavior. PartiallySignedTransaction probably needs some changes to more strongly enforce its invariants.

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: 2025-11-19 06:13 UTC

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