handle an absent final scriptsig or witness when restoring the original payjoin inputs
What changed, and why it matters
This commit fixes a crash in Sparrow Wallet's Payjoin feature. When receiving a Payjoin proposal, the wallet tries to restore original payment input details. Previously, if the original PSBT input lacked a final script signature or final witness, the code would throw a NullPointerException and crash. The patch now safely handles those missing fields by setting the restored values to null instead of crashing.
Review whether other PSBT input fields accessed in this method need similar null guards. Consider adding validation for Payjoin proposals to reject malformed inputs gracefully rather than relying on exception handling. Users should upgrade to a version containing this fix if they use Payjoin.
Security signals we found
NullPointerException crash in Payjoin proposal handling
Missing input validation on PSBT final script fields
Payjoin protocol implementation robustness fix
Potential denial-of-service via malformed Payjoin proposal
Evidence from the diff
In Payjoin.java’s checkProposal(), the code restores redeemScript and witnessScript from the original PSBT input. The original code unconditionally called getFinalScriptSig().getFirstNestedScript() and getFinalScriptWitness().getWitnessScript(), which would throw NullPointerException when those PSBT fields were absent. The patch adds null checks: if final scriptsig/witness are missing, it passes null to setRedeemScript()/setWitnessScript(). This prevents a crash during Payjoin proposal processing for inputs that are not yet finalized or don’t use those fields.
Changed components
src/main/java/com/sparrowwallet/sparrow/payjoin/Payjoin.javaPayjoin proposal verification and input restorationInspect captured patch +2 / −2
### src/main/java/com/sparrowwallet/sparrow/payjoin/Payjoin.java
@@ -183,8 +183,8 @@ void checkProposal(PSBT original, PSBT proposal, int changeOutputIndex, long max
proposedPSBTInput.getTapDerivedPublicKeys().putAll(originalPSBTInput.getTapDerivedPublicKeys());
proposedPSBTInput.setTapInternalKey(originalPSBTInput.getTapInternalKey());
proposedPSBTInput.getProprietary().putAll(originalPSBTInput.getProprietary());
- proposedPSBTInput.setRedeemScript(originalPSBTInput.getFinalScriptSig().getFirstNestedScript());
- proposedPSBTInput.setWitnessScript(originalPSBTInput.getFinalScriptWitness().getWitnessScript());
+ proposedPSBTInput.setRedeemScript(originalPSBTInput.getFinalScriptSig() == null ? null : originalPSBTInput.getFinalScriptSig().getFirstNestedScript());
+ proposedPSBTInput.setWitnessScript(originalPSBTInput.getFinalScriptWitness() == null ? null : originalPSBTInput.getFinalScriptWitness().getWitnessScript());
proposedPSBTInput.setSigHash(originalPSBTInput.getSigHash());
} else {
// Verify the PSBT input is finalizedWhy this scored 40/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.