What changed, and why it matters
This commit makes four small fixes in the Elements sidechain codebase. Two changes remove incorrect assumptions in block-signature size checks. One tightens the peg-in witness format check from 'at least 5 items' to 'exactly 6 items'. One adds explicit-type checks before counting burned pegged-asset outputs as a subsidy. And one prevents a wallet RPC call from crashing when asked to blind a transaction that has no outputs. The changes look like routine bug fixes rather than a single coordinated security patch, and no exploit is directly demonstrated.
Review the validation.cpp subsidy change for consensus correctness, especially whether confidential or non-explicit burned outputs were ever intended to count toward subsidy. Test peg-in transactions with 5-item witnesses to confirm the stricter check does not break valid legacy behavior. Verify the block_proof.cpp assertions were not masking a deeper invariant. The RPC guard is low risk and can be accepted with a regression test.
Security signals we found
Consensus-adjacent change in subsidy calculation (validation.cpp) adding explicit asset/value checks
Peg-in witness parsing tightened from minimum-size to exact-size
Block proof signature-size assertions removed in favor of unconditional failure
RPC crash guard added for empty-output blinding request
Evidence from the diff
The diff touches four areas: (1) block_proof.cpp removes two assertions that tied signature-size failures to the dynamic-federation flag, replacing them with plain return false; (2) pegins.cpp changes DecomposePeginWitness to require exactly six witness stack items instead of at least five; (3) validation.cpp adds explicit-asset and explicit-value checks before summing unspendable pegged-asset outputs as burned subsidy; (4) wallet/rpc/elements.cpp guards blindrawtransaction against an empty vout before creating a dummy OP_RETURN output to balance blinded inputs. The validation change is the most security-relevant because it prevents counting confidential/unblinded outputs that do not meet the expected explicit format, which could affect consensus subsidy calculation. The pegins change could reject previously accepted witness structures. The RPC change is a local crash/DoS guard. No CVE, advisory, or vendor security statement is present in the supplied materials.
Changed components
src/block_proof.cppsrc/pegins.cppsrc/validation.cppsrc/wallet/rpc/elements.cppInspect captured patch +5 / −4
diff --git a/src/block_proof.cpp b/src/block_proof.cpp
index 54fd516..bd8e588 100644
--- a/src/block_proof.cpp
+++ b/src/block_proof.cpp
@@ -33,10 +33,8 @@ static bool CheckProofGeneric(const CBlockHeader& block, const uint32_t max_bloc
// Check signature limits for blocks
if (scriptSig.size() > max_block_signature_size) {
- assert(!is_dyna);
return false;
} else if (witness.GetSerializedSize() > max_block_signature_size) {
- assert(is_dyna);
return false;
}
diff --git a/src/pegins.cpp b/src/pegins.cpp
index 48bdff2..6ac2094 100644
--- a/src/pegins.cpp
+++ b/src/pegins.cpp
@@ -550,7 +550,7 @@ bool DecomposePeginWitness(const CScriptWitness& witness, CAmount& value, CAsset
{
const auto& stack = witness.stack;
- if (stack.size() < 5) return false;
+ if (stack.size() != 6) return false;
DataStream stream{stack[0]};
stream >> value;
diff --git a/src/validation.cpp b/src/validation.cpp
index 3fa0976..d32faa9 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -746,7 +746,7 @@ private:
// calculate the burned subsidy value from the tx
CAmount subsidy = 0;
for (const CTxOut& txout : tx.vout) {
- if (txout.scriptPubKey.IsUnspendable() && txout.nAsset.GetAsset() == Params().GetConsensus().pegged_asset && !txout.IsFee()) {
+ if (txout.scriptPubKey.IsUnspendable() && !txout.IsFee() && txout.nAsset.IsExplicit() && txout.nValue.IsExplicit() && txout.nAsset.GetAsset() == Params().GetConsensus().pegged_asset) {
subsidy += txout.nValue.GetAmount();
}
}
diff --git a/src/wallet/rpc/elements.cpp b/src/wallet/rpc/elements.cpp
index b32abae..b455c33 100644
--- a/src/wallet/rpc/elements.cpp
+++ b/src/wallet/rpc/elements.cpp
@@ -1379,6 +1379,9 @@ RPCHelpMan blindrawtransaction()
// Vacuous, just return the transaction
return EncodeHexTx(CTransaction(tx));
} else if (n_blinded_ins > 0 && num_pubkeys == 0) {
+ if (tx.vout.empty()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to blind transaction: transaction has no outputs to balance blinded inputs against.");
+ }
// Blinded inputs need to balanced with something to be valid, make a dummy.
CTxOut newTxOut(tx.vout.back().nAsset.GetAsset(), 0, CScript() << OP_RETURN);
tx.vout.push_back(newTxOut);
Why this scored 48/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.