Bitcoin Core Patched the SIGHASH_SINGLE Blind Spot in PSBT — the Wallet Stack Is Still Running the Old Logic
Let's be clear about the failure mode before we discuss the fix. There is a 32-byte constant in Bitcoin's signature pipeline — 0x0000000000000000000000000000000000000000000000000000000000000001 — that should never be the thing a private key actually commits to. When a SIGHASH_SINGLE signature is requested for an input index that has no corresponding output, the legacy digest collapses to that single unit value. The key still produces a mathematically valid ECDSA signature. The network still accepts it. No consensus rule complains.
That is the anomaly. Not a crash. Not a chain split. A valid signature over a constant, produced by an honest device, presented to a user who believes they just authorized a payment to a specific address. On September 25, a Bitcoin Core contributor merged a patch into master that stops the client from generating one of those signatures through the PSBT path. As of October 4, that patch has not shipped in a release, and no backport is confirmed. The upstream code is patched. The wallets that sit between a user's finger and a hardware secure element are, for the most part, still running the old logic. That gap — patched upstream, unpatched downstream — is the actual story.
Gas wars are just ego masquerading as utility. This is the opposite kind of event: quiet, cheap, and structurally more interesting than any fee spike.
To understand why a single constant matters, you have to understand what SIGHASH_SINGLE is for and where PSBT inserted it.
Bitcoin transactions are not signed as whole objects. They are signed as a digest, and the digest is parameterized by a flag — the sighash type — that decides which parts of the transaction the signature commits to. SIGHASH_ALL commits to every input and every output. SIGHASH_NONE commits to inputs but no outputs. SIGHASH_SINGLE commits to all inputs but only the output at the same index as the input being signed. The intent is surgical: input N authorizes output N and nothing else. It is the primitive you reach for when you want to bind a specific funding source to a specific destination without dragging the rest of the transaction into the commitment.
That intent has a hole in it. "Output at the same index" is only meaningful if such an output exists. If input N is signed with SIGHASH_SINGLE and the transaction has fewer than N+1 outputs, there is no target to bind to. Satoshi-era code handled this by hashing nothing useful and returning the digest 1 — the constant above. The behavior was preserved for consensus compatibility and never cleaned up. This is not new. It is the same class of bug that surfaced as CVE-2020-14199 in 2020, when a hardware-wallet vendor disclosed that certain devices could be induced to sign a SIGHASH_SINGLE input in a way that produced a reusable or unbound signature. That disclosure triggered firmware updates across the hardware wallet category and a long, uncomfortable conversation about how much of a transaction a device actually verifies before it signs. Five years later, the same edge case is generating a new patch, which tells you something about how durable this class of flaw is.
For years this was a raw-transaction problem. If you built a transaction by hand and signed it, the wallet or library that signed it either refused the malformed request or produced a signature you could inspect. Then BIP-174 arrived and changed the blast radius. PSBT — Partially Signed Bitcoin Transaction — turned signing into a multi-party relay. A coordinator constructs an unsigned transaction, attaches metadata, and hands it to a signer; the signer returns a partial signature; the coordinator combines partials and broadcasts. The format was designed precisely so that software wallets, hardware devices, and offline signers could collaborate without any single party holding the keys. Each role in that relay is an independent piece of software, and each one can be wrong about SIGHASH_SINGLE in its own way.
BIP-174 anticipated the danger. It explicitly instructs signers to reject signature modes they cannot safely evaluate, and it recommends SIGHASH_ALL whenever an alternative is not specified. The standard did its job. The problem is that a recommendation in a BIP is not a check in a code path, and PSBT implementations multiplied faster than any single review process could track. That gap between the standard's intent and the client's implementation is where this whole story lives.
Now the mechanics, because the severity differs by input type and that distinction is the part most summaries flatten. There are two categories of inputs in play: legacy (pre-SegWit) and SegWit v0 (P2WPKH/P2WSH). They fail differently.
For a legacy input signed with SIGHASH_SINGLE where the corresponding output is missing, the digest collapses to the constant 1. The signature is over a fixed value that has nothing to do with the transaction, the amount, or the destination. A signature over a constant is not bound to anything — which means the same key that produced it can be made to produce the identical signature again in a different context. That is the reusable-signature property: an attacker who can induce a signer to produce this signature for one UTXO can potentially replay it against other UTXOs under the same key. The cryptographic protection that is supposed to tie a signature to a specific spend is simply absent.
For a SegWit v0 input, the story is less catastrophic but still wrong. SegWit's BIP-143 digest construction commits to the amount and script code of the input being signed, so the signature is bound to the specific coin being spent — an attacker cannot lift it and reuse it elsewhere. But the target output can still be unbound. The signature proves "this key authorized spending this coin" while proving nothing about where the coin goes. That is a different failure, and it is the more insidious one, because the signature looks completely normal in every downstream check. Code does not lie, but it often forgets to breathe — and here it forgot to breathe on the output side.
I have seen this shape before. In late 2017 I spent forty hours auditing a token-distribution contract that looked fine at the interface level and fell apart one layer down, where a stack underflow let a distributor drain balances under an edge condition nobody had modeled. The lesson was not "contracts are insecure." It was that the security property lives at the level of what is actually committed, not at the level of what the UI claims. The same lesson applies here: the signature commits to a digest, and the digest, under this edge case, commits to less than the user believes.
The fix itself is worth reading closely, because its design choices say more than its diff. The Core patch does not add a new refusal at the wallet boundary or bolt a special case onto the PSBT parser. It moves the guard from the raw transaction signing interface — which already rejected this edge case — into the shared signature-creation logic that every signing path funnels through. That relocation is the whole point. Instead of defending one door, it defends the room. Any code path that reaches signature creation, whether it originates from a hand-built transaction or a PSBT assembled by a coordinator, now hits the same check.
In pseudo-code, the old shape was:
sign_input(tx, i):
if path == RAW and missing_output(tx, i):
reject()
return make_signature(tx, i) # PSBT path never hit the guard
and the new shape is:
sign_input(tx, i):
if missing_output(tx, i):
reject() # enforced in the shared core
return make_signature(tx, i)
The check moved to the choke point, which is where security checks belong. Defense at the perimeter is only as good as the number of perimeters you remembered to build.
And the check is deliberately narrow. It blocks the affected legacy and SegWit v0 inputs from being signed, but it allows other valid inputs in the same PSBT to continue processing. That restraint matters. A blunt "reject the entire PSBT" would have broken legitimate multi-input transactions, which is exactly the kind of overcorrection that gets reverted. The patch refuses the poisoned input and lets the clean ones through, which is what you want from a security fix that has to coexist with a live network.
What the patch does not do is protect anyone yet. Merged into master on September 25 is not the same as released. Bitcoin Core runs a conservative release cadence, and a fix that has not been tagged, has not been backported, and has not been adopted by downstream signers is a fix that exists only for people who build from source. The window between "upstream patched" and "downstream deployed" is not empty. It is the interval in which the theoretical becomes reachable.
Let me put numbers on the exposure surface, because "wallets" is too vague. PSBT is the lingua franca of coordinated signing: desktop wallets that build transactions, hardware devices that sign them, offline machines that hold keys, multi-signature coordinators that assemble partials, and custody platforms that orchestrate all of the above. Each of those is an independent implementation. Each can have its own handling of SIGHASH_SINGLE. Core closing its own path does not close theirs. Based on my audit experience, the failure pattern is almost always the same — the reference implementation is patched first, and the ecosystem copies the old behavior for months because copying is faster than reasoning. I watched this exact dynamic in the 2020 DeFi summer, when I audited a small DEX's liquidity-mining contracts and found a reentrancy hole in the reward distribution that could have minted tokens without limit. The team patched before mainnet, but the pattern repeated across a dozen forks because each fork inherited the original's assumptions.
This is worth stressing because the loudest reactions to patch news tend to be wrong in both directions. It is not a "Bitcoin is broken" event: no consensus rule is changing, no chain is forking, and no private key is exposed. But it is also not a nothing-burger: the whole value of a signature is that it commits to something specific, and a signature that commits to less than the user thinks is a real degradation of that value, however narrow the trigger. The narrowness of the trigger is what keeps the severity at medium rather than high. It requires SIGHASH_SINGLE, a missing output at the matching index, a signer willing to process the request, and — for the legacy case to be dangerous — a second UTXO under the same key that an attacker wants to redirect. Stack all four and the probability drops fast. Stack only three and you still have a signature that means something other than what the user approved.
Bitcoin's version of this dynamic is slower and quieter than DeFi's, but structurally identical. There is no forced upgrade. There is no governance vote that flips a switch. There is a mailing list, a patch, and a hope.
Here is the counter-intuitive part, and it is where I differ from the framing that treats this as a Core bug. Core is not the weak link. Core was already refusing this edge case at the raw interface; the patch merely extended an existing refusal to a newer path. The actual defect is a semantic gap between two layers that were never forced to agree: the display layer, which tells a human what they are about to approve, and the cryptographic layer, which decides what is actually signed. A wallet can show you "send 0.5 BTC to address X" while producing a signature that, under the missing-output case, does not cryptographically bind X at all. Every check downstream sees a valid signature. The human sees a valid approval. The two are not the same statement.
This is the blind-signing problem wearing a new coat. Hardware wallets have spent years trying to prove that what a device signs is what its screen shows, and the entire category keeps discovering that the proof is only as strong as the weakest field. A SIGHASH_SINGLE request with a missing output is exactly the kind of field a device might sign without fully resolving, because resolving it requires modeling the transaction structure, not just echoing bytes. In 2022 I spent six months reverse-engineering oracle-manipulation vectors after the Terra collapse, and the recurring finding was the same: the failure was never in the price feed's arithmetic, it was in the gap between what the system assumed it was reading and what it was actually reading. Price feed latency is DeFi's Achilles' heel, and the display-versus-commitment gap is Bitcoin's wallet-layer equivalent. Both are semantic, not arithmetic.
The second uncomfortable truth is that BIP-174 already told us to prevent this, and it did not matter. Standards are advisory. Implementations are load-bearing. The recommendation to default to SIGHASH_ALL was correct in 2017 and is correct now, and it will keep being ignored by any wallet that finds SIGHASH_SINGLE convenient for a specific flow. You cannot patch a recommendation into a million devices. You can only patch the code you control and hope the rest of the stack reads the changelog.
The third is the timing asymmetry. The moment a fix like this is merged, the vulnerability becomes public knowledge. The attackers read the same commit log the defenders do. In the interval before release and adoption, the population that knows about the flaw grows faster than the population that is protected against it. That is not a flaw in Bitcoin Core's process; it is a structural property of coordinated disclosure in an ecosystem with no forced-upgrade mechanism. There is no central authority to push a firmware update to every signer. Decentralization is a feature until you need someone to enforce a deadline, and then it is just a long tail of unpatched devices.
The forward-looking question is not whether this specific patch ships — it will, in some future minor release, and it will be backported to whatever branches Core still maintains. The question is whether the ecosystem treats the "last mile" as its own responsibility or waits for Core to do the impossible and patch software it does not own. If the next disclosure lands on a flow that a major hardware vendor has quietly relied on for years, the fix will be the same and the delay will be longer. In a bear market, where survival matters more than upside, the protocols that earn trust are the ones that patch themselves before they are asked. The vulnerability was never really in the digest. It was in the assumption that a valid signature means what the screen said it meant. Watch the wallet and hardware firmware changelogs, not the price chart. That is where this actually resolves.