From 84351515657e7f5e338c165c4a791093aba24143 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 13 Oct 2026 13:14:21 -0500 Subject: [PATCH 1/3] fix(coinjoin): reject only the sender of an invalid signature When a DSSIGNFINALTX input failed AddScriptSig(), the masternode relayed STATUS_REJECTED to every participant of the session. Each client that receives it enters POOL_STATE_ERROR, releases its inputs and stops signing, but the server keeps the session until its signing timeout. There, the clients that obeyed the rejection are the non-signers, and one of them can be charged its collateral, while the participant that sent the bad signature may have signed everything and is not an offender at all. A participant could abort every session it joined this way, by resending a signature it had already submitted, and have an honest peer pay for it. A signature that fails validation comes from the sender, so reject only the sender and keep the session for everyone else. If the sender never supplies a valid signature its input stays unsigned, and the timeout treats it as the non-signer, under the existing probabilistic policy. A duplicate of a signature that was already accepted no longer affects the session at all. Co-Authored-By: Claude Opus 5.5 --- src/coinjoin/server.cpp | 5 +++- src/test/coinjoin_inouts_tests.cpp | 48 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/coinjoin/server.cpp b/src/coinjoin/server.cpp index b06652025431..56a7362af324 100644 --- a/src/coinjoin/server.cpp +++ b/src/coinjoin/server.cpp @@ -284,7 +284,10 @@ void CCoinJoinServer::ProcessDSSIGNFINALTX(CNode& peer, CDataStream& vRecv) if (!AddScriptSig(txin)) { LogPrint(BCLog::COINJOIN, "DSSIGNFINALTX -- AddScriptSig() failed at %d/%d, session: %d\n", nTxInIndex, nTxInsCount, session_id); - RelayStatus(STATUS_REJECTED); + // Only the sender is at fault, so only the sender is rejected. A session-wide + // rejection would let one participant abort the session for everyone, and those + // that obeyed it would be counted as non-signers at the timeout. + PushStatus(peer, STATUS_REJECTED, ERR_INVALID_INPUT); return; } LogPrint(BCLog::COINJOIN, "DSSIGNFINALTX -- AddScriptSig() %d/%d success\n", nTxInIndex, nTxInsCount); diff --git a/src/test/coinjoin_inouts_tests.cpp b/src/test/coinjoin_inouts_tests.cpp index c5dea2930c42..4e0b319614cd 100644 --- a/src/test/coinjoin_inouts_tests.cpp +++ b/src/test/coinjoin_inouts_tests.cpp @@ -17,6 +17,7 @@ #include #include