blindpsbt: return error instead of asserting on surjection proof failure
What changed, and why it matters
This commit fixes a denial-of-service bug in Elements' confidential asset handling. Previously, a malformed transaction file (PSET) could trigger a hard program crash (assert/abort) by feeding in mismatched cryptographic tags. The patch turns that crash into a normal, recoverable error so the process stays alive and simply rejects the bad input.
Treat this as a security hardening fix and include it in the next maintenance release. Users and integrators handling externally supplied PSETs should upgrade to avoid remote-triggered aborts during blinding.
Security signals we found
assert-to-error conversion
denial-of-service vector from untrusted PSET input
surjection proof failure on attacker-chosen tags/generators
recoverable error instead of process abort
Evidence from the diff
In src/blindpsbt.cpp, CreateAssetSurjectionProof replaced two assert(ret == 1) checks around secp256k1_surjectionproof_generate and secp256k1_surjectionproof_verify with conditional returns false. The secp256k1-zkp surjection proof generation can fail when the supplied ephemeral input tags/generators have no discrete-log relationship to the output asset tag. A crafted PSET can supply arbitrary tags, so the prior assertion caused an abort(); now the failure propagates as a recoverable error.
Changed components
src/blindpsbt.cppCreateAssetSurjectionProofPSET blinding flowInspect captured patch +9 / −2
### src/blindpsbt.cpp
@@ -52,10 +52,17 @@ bool CreateAssetSurjectionProof(std::vector<unsigned char>& output_proof, const
}
// Using the input chosen, build proof
ret = secp256k1_surjectionproof_generate(secp256k1_blind_context, &proof, &ephemeral_input_tags[0], ephemeral_input_tags.size(), &output_asset_tag, input_index, input_asset_blinders[input_index].begin(), output_asset_blinder.begin());
- assert(ret == 1);
+ if (ret != 1) {
+ // Attacker-selected tags/generators without a known discrete-log
+ // relationship cause generation to fail; this must be a recoverable
+ // PSET error, not a process abort.
+ return false;
+ }
// Double-check answer
ret = secp256k1_surjectionproof_verify(secp256k1_blind_context, &proof, &ephemeral_input_tags[0], ephemeral_input_tags.size(), &output_asset_tag);
- assert(ret == 1);
+ if (ret != 1) {
+ return false;
+ }
// Serialize into output witness structure
size_t output_len = secp256k1_surjectionproof_serialized_size(secp256k1_blind_context, &proof);Why this scored 74/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.