blindpsbt: reject off-curve blinding pubkey before ECDH
What changed, and why it matters
This commit fixes a crash bug in Elements' confidential-transaction blinding code for PSBTs (Partially Signed Bitcoin Transactions). A malformed, attacker-chosen 'blinding public key' that is not actually a valid point on the cryptographic curve could slip through earlier checks and reach a low-level ECDH key-exchange routine. That routine only had an internal assertion for validation, so the invalid key would trigger an assertion failure and abort the running process. The fix adds an explicit validity check and returns a controlled error instead of crashing.
Apply the patch. Consider auditing all call sites that pass attacker-controlled public keys to CKey::ECDH or other low-level crypto routines to ensure IsFullyValid() or equivalent validation is performed. Add regression tests with off-curve and invalid pubkeys for the PSBT blinding path.
Security signals we found
Off-curve / invalid elliptic-curve public key reaches ECDH
Assertion abort (DoS / process termination) via crafted input
Missing input validation in PSBT blinding path compared to non-PSBT path
Return of controlled BlindingStatus::INVALID_BLINDER instead of crash
Evidence from the diff
In src/blindpsbt.cpp, BlindPSBT() now calls output.m_blinding_pubkey.IsFullyValid() before passing the pubkey to GenerateRangeproofECDHKey(), which calls CKey::ECDH. Previously, IsBlinded() apparently accepted pubkeys that were not fully valid secp256k1 points, and CKey::ECDH only asserted peer-key validity, causing a process abort on an off-curve pubkey. The patch mirrors the existing non-PSBT blinding path (blind.cpp) by returning BlindingStatus::INVALID_BLINDER rather than crashing.
Changed components
src/blindpsbt.cppBlindPSBT()Confidential PSBT blinding / rangeproof generationInspect captured patch +7 / −0
### src/blindpsbt.cpp
@@ -571,6 +571,13 @@ BlindingStatus BlindPSBT(PartiallySignedTransaction& psbt, std::map<uint32_t, st
CreateValueCommitment(value_commitment, value_commit, value_blinder, asset_generator, *output.amount);
// Generate rangproof nonce
+ if (!output.m_blinding_pubkey.IsFullyValid()) {
+ // An attacker-controlled (off-curve) blinding pubkey would otherwise
+ // reach CKey::ECDH, whose only validation is an assert on the peer
+ // key, aborting the process. The non-PSET path (blind.cpp) requires
+ // IsFullyValid() before ECDH; mirror it here.
+ return BlindingStatus::INVALID_BLINDER;
+ }
uint256 nonce = GenerateRangeproofECDHKey(ecdh_key, output.m_blinding_pubkey);
// Generate rangeproofWhy this scored 62/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.