From 14d3ae17851615a69c33cb7eed623b904b140e3d Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 13 Oct 2023 04:22:24 -0700 Subject: [PATCH] Reject ztxs with duplicate zkproofs This is a greatly simplified and slightly tweaked version of https://github.com/PirateNetwork/pirate/commit/af2e3713e286d61353edffd0b4c89c931b455cb4 Their version will detect duplicate zkproofs across transactions while this code will only detect duplicate zkproofs in a single ztx. If dupes are found, the tx will be denied entry into the mempool. This provides most of the benefit (increased CPU cost to attackers) with the least code change and no annoyance to full node operators. Detecting duplicate zkproofs across transactions requires a one-time reindex of all of history, which means significant downtime for nodes. Since Hush + HSCs have a much more strict policy on number of shielded outputs and shielded inputs, only detecting duplicate zkproofs in individual ztxs seems sufficient for now. No correctly functioning node or wallet will ever create duplicate zkproofs, so there is no worry of this accidentally affecting normal users. Currently this is not a consensus rule but it could become one in the future. --- src/consensus/validation.h | 2 ++ src/main.cpp | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/consensus/validation.h b/src/consensus/validation.h index 8ae53c89a..f3d7b3b31 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -29,6 +29,8 @@ static const unsigned char REJECT_MALFORMED = 0x01; static const unsigned char REJECT_INVALID = 0x10; static const unsigned char REJECT_OBSOLETE = 0x11; static const unsigned char REJECT_DUPLICATE = 0x12; +static const unsigned char REJECT_DUPLICATE_OUTPUT_PROOF = 0x13; +static const unsigned char REJECT_DUPLICATE_SPEND_PROOF = 0x14; static const unsigned char REJECT_NONSTANDARD = 0x40; static const unsigned char REJECT_DUST = 0x41; static const unsigned char REJECT_INSUFFICIENTFEE = 0x42; diff --git a/src/main.cpp b/src/main.cpp index 55b26b70b..0d132f952 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1751,7 +1751,31 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa { return error("AcceptToMemoryPool: CheckTransaction failed"); } - + + // Reject duplicate output proofs in a single ztx in mempool + // Migrate this to CheckTransaction() to make it a consensus requirement + { + set vSaplingOutputProof; + BOOST_FOREACH(const OutputDescription& output, tx.vShieldedOutput) + { + if (vSaplingOutputProof.count(output.zkproof)) + return state.Invalid(error("AcceptToMemoryPool: duplicate output proof"),REJECT_DUPLICATE_OUTPUT_PROOF, "bad-txns-duplicate-output-proof"); + vSaplingOutputProof.insert(output.zkproof); + } + } + + // Reject duplicate spend proofs in a single ztx in mempool + // Migrate this to CheckTransaction() to make it a consensus requirement + { + set vSaplingSpendProof; + BOOST_FOREACH(const SpendDescription& spend, tx.vShieldedSpend) + { + if (vSaplingSpendProof.count(spend.zkproof)) + return state.Invalid(error("AcceptToMemoryPool: duplicate spend proof"),REJECT_DUPLICATE_SPEND_PROOF, "bad-txns-duplicate-spend-proof"); + vSaplingSpendProof.insert(spend.zkproof); + } + } + // DoS level set to 10 to be more forgiving. // Check transaction contextually against the set of consensus rules which apply in the next block to be mined. if (!ContextualCheckTransaction(0,0,0,tx, state, nextBlockHeight, (dosLevel == -1) ? 10 : dosLevel))