diff --git a/src/Makefile.am b/src/Makefile.am index 009f91437a96d..fbbd0ff000066 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -255,6 +255,7 @@ BITCOIN_CORE_H = \ llmq/options.h \ llmq/params.h \ llmq/quorums.h \ + llmq/signhash.h \ llmq/signing.h \ llmq/signing_shares.h \ llmq/snapshot.h \ @@ -515,6 +516,7 @@ libbitcoin_node_a_SOURCES = \ llmq/ehf_signals.cpp \ llmq/options.cpp \ llmq/quorums.cpp \ + llmq/signhash.cpp \ llmq/signing.cpp \ llmq/signing_shares.cpp \ llmq/snapshot.cpp \ diff --git a/src/evo/assetlocktx.cpp b/src/evo/assetlocktx.cpp index 62a4ef259ad62..9624a21151cb6 100644 --- a/src/evo/assetlocktx.cpp +++ b/src/evo/assetlocktx.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -21,10 +22,6 @@ using node::BlockManager; -namespace llmq { -// forward declaration to avoid circular dependency -uint256 BuildSignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash); -} // namespace llmq /** * Common code for Asset Lock and Asset Unlock @@ -149,8 +146,8 @@ bool CAssetUnlockPayload::VerifySig(const llmq::CQuorumManager& qman, const uint const uint256 requestId = ::SerializeHash(std::make_pair(ASSETUNLOCK_REQUESTID_PREFIX, index)); - if (const uint256 signHash = llmq::BuildSignHash(llmqType, quorum->qc->quorumHash, requestId, msgHash); - quorumSig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash)) { + if (const llmq::SignHash signHash(llmqType, quorum->qc->quorumHash, requestId, msgHash); + quorumSig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get())) { return true; } diff --git a/src/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index c1c1e76df740b..71f9d3dc4a9ad 100644 --- a/src/evo/mnhftx.cpp +++ b/src/evo/mnhftx.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -102,8 +103,8 @@ bool MNHFTx::Verify(const llmq::CQuorumManager& qman, const uint256& quorumHash, return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-missing-quorum"); } - const uint256 signHash = llmq::BuildSignHash(llmqType, quorum->qc->quorumHash, requestId, msgHash); - if (!sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash)) { + const llmq::SignHash signHash{llmqType, quorum->qc->quorumHash, requestId, msgHash}; + if (!sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get())) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-invalid"); } diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 2435ae3690d88..426efeb8f530e 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -285,7 +286,7 @@ std::unordered_set CInstantSendManager::ProcessPend // should not happen, but if one fails to select, all others will also fail to select return {}; } - uint256 signHash = BuildSignHash(llmq_params.type, quorum->qc->quorumHash, id, islock->txid); + uint256 signHash = llmq::SignHash{llmq_params.type, quorum->qc->quorumHash, id, islock->txid}.Get(); batchVerifier.PushMessage(nodeId, hash, signHash, islock->sig.Get(), quorum->qc->quorumPublicKey); verifyCount++; diff --git a/src/llmq/quorums.cpp b/src/llmq/quorums.cpp index 9c7e60e46706a..4a2642b01404c 100644 --- a/src/llmq/quorums.cpp +++ b/src/llmq/quorums.cpp @@ -2,12 +2,13 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include -#include #include +#include #include #include #include +#include +#include #include #include @@ -36,8 +37,6 @@ static const std::string DB_QUORUM_QUORUM_VVEC = "q_Qqvvec"; RecursiveMutex cs_data_requests; static std::unordered_map mapQuorumDataRequests GUARDED_BY(cs_data_requests); -// forward declaration to avoid circular dependency -uint256 BuildSignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash); static uint256 MakeQuorumKey(const CQuorum& q) { @@ -1279,8 +1278,8 @@ VerifyRecSigStatus VerifyRecoveredSig(Consensus::LLMQType llmqType, const CChain return VerifyRecSigStatus::NoQuorum; } - uint256 signHash = BuildSignHash(llmqType, quorum->qc->quorumHash, id, msgHash); - const bool ret = sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash); + SignHash signHash{llmqType, quorum->qc->quorumHash, id, msgHash}; + const bool ret = sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get()); return ret ? VerifyRecSigStatus::Valid : VerifyRecSigStatus::Invalid; } } // namespace llmq diff --git a/src/llmq/signhash.cpp b/src/llmq/signhash.cpp new file mode 100644 index 0000000000000..7b1a01c3f4dca --- /dev/null +++ b/src/llmq/signhash.cpp @@ -0,0 +1,24 @@ +// Copyright (c) 2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include + +#include + +namespace llmq { + +SignHash::SignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash) +{ + CHashWriter h(SER_GETHASH, 0); + h << llmqType; + h << quorumHash; + h << id; + h << msgHash; + m_hash = h.GetHash(); +} + +} // namespace llmq diff --git a/src/llmq/signhash.h b/src/llmq/signhash.h new file mode 100644 index 0000000000000..0bb19beaa629b --- /dev/null +++ b/src/llmq/signhash.h @@ -0,0 +1,76 @@ +// Copyright (c) 2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_LLMQ_SIGNHASH_H +#define BITCOIN_LLMQ_SIGNHASH_H + +#include +#include + +#include +#include + +#include +#include + +namespace llmq { + +/** + * SignHash is a strongly-typed wrapper for the hash used in LLMQ signing operations. + * It encapsulates the hash calculation for quorum signatures, replacing the need for + * BuildSignHash function and avoiding circular dependencies. + */ +class SignHash : public BaseHash +{ +public: + SignHash() = default; + using BaseHash::BaseHash; + + /** + * Constructs a SignHash from the given parameters. + * This replaces the previous BuildSignHash function. + */ + SignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash); + + /** + * Get the underlying uint256 hash value. + */ + const uint256& Get() const { return m_hash; } + + // Serialization support + template + void Serialize(Stream& s) const + { + s << m_hash; + } + + template + void Unserialize(Stream& s) + { + s >> m_hash; + } +}; + +// Salted hasher for llmq::SignHash that reuses the salted hasher for uint256 +struct SignHashSaltedHasher { + std::size_t operator()(const SignHash& signHash) const noexcept { return StaticSaltedHasher{}(signHash.Get()); } +}; + +} // namespace llmq + +// Make SignHash hashable for use in unordered_map +template <> +struct std::hash { + std::size_t operator()(const llmq::SignHash& signHash) const noexcept + { + // Use the first 8 bytes of the hash as the hash value + const unsigned char* data = signHash.data(); + std::size_t result; + std::memcpy(&result, data, sizeof(result)); + return result; + } +}; + + +#endif // BITCOIN_LLMQ_SIGNHASH_H diff --git a/src/llmq/signing.cpp b/src/llmq/signing.cpp index 810e543c7f788..7c9d043a887b0 100644 --- a/src/llmq/signing.cpp +++ b/src/llmq/signing.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -161,7 +162,7 @@ void CRecoveredSigsDb::WriteRecoveredSig(const llmq::CRecoveredSig& recSig) // store by signHash auto signHash = recSig.buildSignHash(); - auto k4 = std::make_tuple(std::string("rs_s"), signHash); + auto k4 = std::make_tuple(std::string("rs_s"), signHash.Get()); batch.Write(k4, (uint8_t)1); // store by current time. Allows fast cleanup of old recSigs @@ -173,7 +174,7 @@ void CRecoveredSigsDb::WriteRecoveredSig(const llmq::CRecoveredSig& recSig) { LOCK(cs_cache); hasSigForIdCache.insert(std::make_pair(recSig.getLlmqType(), recSig.getId()), true); - hasSigForSessionCache.insert(signHash, true); + hasSigForSessionCache.insert(signHash.Get(), true); hasSigForHashCache.insert(recSig.GetHash(), true); } } @@ -190,7 +191,7 @@ void CRecoveredSigsDb::RemoveRecoveredSig(CDBBatch& batch, Consensus::LLMQType l auto k1 = std::make_tuple(std::string("rs_r"), recSig.getLlmqType(), recSig.getId()); auto k2 = std::make_tuple(std::string("rs_r"), recSig.getLlmqType(), recSig.getId(), recSig.getMsgHash()); auto k3 = std::make_tuple(std::string("rs_h"), recSig.GetHash()); - auto k4 = std::make_tuple(std::string("rs_s"), signHash); + auto k4 = std::make_tuple(std::string("rs_s"), signHash.Get()); batch.Erase(k1); batch.Erase(k2); if (deleteHashKey) { @@ -211,7 +212,7 @@ void CRecoveredSigsDb::RemoveRecoveredSig(CDBBatch& batch, Consensus::LLMQType l LOCK(cs_cache); hasSigForIdCache.erase(std::make_pair(recSig.getLlmqType(), recSig.getId())); - hasSigForSessionCache.erase(signHash); + hasSigForSessionCache.erase(signHash.Get()); if (deleteHashKey) { hasSigForHashCache.erase(recSig.GetHash()); } @@ -469,7 +470,7 @@ void CSigningManager::CollectPendingRecoveredSigsToVerify( bool alreadyHave = db.HasRecoveredSigForHash(recSig->GetHash()); if (!alreadyHave) { - uniqueSignHashes.emplace(nodeId, recSig->buildSignHash()); + uniqueSignHashes.emplace(nodeId, recSig->buildSignHash().Get()); retSigShares[nodeId].emplace_back(recSig); } ns.erase(ns.begin()); @@ -553,7 +554,8 @@ bool CSigningManager::ProcessPendingRecoveredSigs(PeerManager& peerman) } const auto& quorum = quorums.at(std::make_pair(recSig->getLlmqType(), recSig->getQuorumHash())); - batchVerifier.PushMessage(nodeId, recSig->GetHash(), recSig->buildSignHash(), recSig->sig.Get(), quorum->qc->quorumPublicKey); + batchVerifier.PushMessage(nodeId, recSig->GetHash(), recSig->buildSignHash().Get(), recSig->sig.Get(), + quorum->qc->quorumPublicKey); verifyCount++; } } @@ -605,7 +607,7 @@ void CSigningManager::ProcessRecoveredSig(const std::shared_ptrgetId(), otherRecoveredSig)) { auto otherSignHash = otherRecoveredSig.buildSignHash(); - if (signHash != otherSignHash) { + if (signHash.Get() != otherSignHash.Get()) { // this should really not happen, as each masternode is participating in only one vote, // even if it's a member of multiple quorums. so a majority is only possible on one quorum and one msgHash per id LogPrintf("CSigningManager::%s -- conflicting recoveredSig for signHash=%s, id=%s, msgHash=%s, otherSignHash=%s\n", __func__, @@ -836,20 +838,8 @@ void CSigningManager::WorkThreadMain(PeerManager& peerman) } } -uint256 CSigBase::buildSignHash() const -{ - return BuildSignHash(llmqType, quorumHash, id, msgHash); -} +SignHash CSigBase::buildSignHash() const { return SignHash(llmqType, quorumHash, id, msgHash); } -uint256 BuildSignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash) -{ - CHashWriter h(SER_GETHASH, 0); - h << llmqType; - h << quorumHash; - h << id; - h << msgHash; - return h.GetHash(); -} bool IsQuorumActive(Consensus::LLMQType llmqType, const CQuorumManager& qman, const uint256& quorumHash) { diff --git a/src/llmq/signing.h b/src/llmq/signing.h index b36af4eb8d4a4..7293892467a7e 100644 --- a/src/llmq/signing.h +++ b/src/llmq/signing.h @@ -8,13 +8,14 @@ #include #include #include +#include #include #include #include #include #include -#include #include +#include #include #include @@ -67,7 +68,7 @@ class CSigBase return msgHash; } - [[nodiscard]] uint256 buildSignHash() const; + [[nodiscard]] SignHash buildSignHash() const; }; class CRecoveredSig : virtual public CSigBase @@ -263,8 +264,6 @@ void IterateNodesRandom(NodesContainer& nodeStates, Continue&& cont, Callback&& } } -uint256 BuildSignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash); - bool IsQuorumActive(Consensus::LLMQType llmqType, const CQuorumManager& qman, const uint256& quorumHash); } // namespace llmq diff --git a/src/llmq/signing_shares.cpp b/src/llmq/signing_shares.cpp index 29d013eda5777..01af5c8f721e1 100644 --- a/src/llmq/signing_shares.cpp +++ b/src/llmq/signing_shares.cpp @@ -4,9 +4,10 @@ #include +#include #include #include -#include +#include #include #include @@ -27,7 +28,7 @@ namespace llmq { void CSigShare::UpdateKey() { - key.first = this->buildSignHash(); + key.first = this->buildSignHash().Get(); key.second = quorumMember; } @@ -97,7 +98,7 @@ std::string CBatchedSigShares::ToInvString() const return inv.ToString(); } -static void InitSession(CSigSharesNodeState::Session& s, const uint256& signHash, CSigBase from) +static void InitSession(CSigSharesNodeState::Session& s, const llmq::SignHash& signHash, CSigBase from) { const auto& llmq_params_opt = Params().GetLLMQ(from.getLlmqType()); assert(llmq_params_opt.has_value()); @@ -117,7 +118,7 @@ CSigSharesNodeState::Session& CSigSharesNodeState::GetOrCreateSessionFromShare(c { auto& s = sessions[sigShare.GetSignHash()]; if (s.announced.inv.empty()) { - InitSession(s, sigShare.GetSignHash(), sigShare); + InitSession(s, sigShare.buildSignHash(), sigShare); } return s; } @@ -125,7 +126,7 @@ CSigSharesNodeState::Session& CSigSharesNodeState::GetOrCreateSessionFromShare(c CSigSharesNodeState::Session& CSigSharesNodeState::GetOrCreateSessionFromAnn(const llmq::CSigSesAnn& ann) { auto signHash = ann.buildSignHash(); - auto& s = sessions[signHash]; + auto& s = sessions[signHash.Get()]; if (s.announced.inv.empty()) { InitSession(s, signHash, ann); } @@ -347,7 +348,7 @@ bool CSigSharesManager::ProcessMessageSigSharesInv(const CNode& pfrom, const CSi } // TODO for PoSe, we should consider propagating shares even if we already have a recovered sig - if (sigman.HasRecoveredSigForSession(sessionInfo.signHash)) { + if (sigman.HasRecoveredSigForSession(sessionInfo.signHash.Get())) { return true; } @@ -384,7 +385,7 @@ bool CSigSharesManager::ProcessMessageGetSigShares(const CNode& pfrom, const CSi } // TODO for PoSe, we should consider propagating shares even if we already have a recovered sig - if (sigman.HasRecoveredSigForSession(sessionInfo.signHash)) { + if (sigman.HasRecoveredSigForSession(sessionInfo.signHash.Get())) { return true; } @@ -772,7 +773,7 @@ void CSigSharesManager::TryRecoverSig(PeerManager& peerman, const CQuorumCPtr& q { LOCK(cs); - auto signHash = BuildSignHash(quorum->params.type, quorum->qc->quorumHash, id, msgHash); + auto signHash = SignHash(quorum->params.type, quorum->qc->quorumHash, id, msgHash).Get(); const auto* sigSharesForSignHash = sigShares.GetAllForSignHash(signHash); if (sigSharesForSignHash == nullptr) { return; @@ -830,7 +831,7 @@ void CSigSharesManager::TryRecoverSig(PeerManager& peerman, const CQuorumCPtr& q // verification because this is unbatched and thus slow verification that happens here. if (((recoveredSigsCounter++) % 100) == 0) { auto signHash = rs->buildSignHash(); - bool valid = recoveredSig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash); + bool valid = recoveredSig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get()); if (!valid) { // this should really not happen as we have verified all signature shares before LogPrintf("CSigSharesManager::%s -- own recovered signature is invalid. id=%s, msgHash=%s\n", __func__, @@ -1556,7 +1557,7 @@ std::optional CSigSharesManager::CreateSigShare(const CQuorumCPtr& qu } CSigShare sigShare(quorum->params.type, quorum->qc->quorumHash, id, msgHash, uint16_t(memberIdx), {}); - uint256 signHash = sigShare.buildSignHash(); + uint256 signHash = sigShare.buildSignHash().Get(); // TODO: This one should be SIGN by QUORUM key, not by OPERATOR key // see TODO in CDKGSession::FinalizeSingleCommitment for details @@ -1592,7 +1593,7 @@ std::optional CSigSharesManager::CreateSigShare(const CQuorumCPtr& qu } CSigShare sigShare(quorum->params.type, quorum->qc->quorumHash, id, msgHash, uint16_t(memberIdx), {}); - uint256 signHash = sigShare.buildSignHash(); + uint256 signHash = sigShare.buildSignHash().Get(); sigShare.sigShare.Set(skShare.Sign(signHash, bls::bls_legacy_scheme.load()), bls::bls_legacy_scheme.load()); if (!sigShare.sigShare.Get().IsValid()) { @@ -1617,7 +1618,7 @@ void CSigSharesManager::ForceReAnnouncement(const CQuorumCPtr& quorum, Consensus } LOCK(cs); - auto signHash = BuildSignHash(llmqType, quorum->qc->quorumHash, id, msgHash); + auto signHash = SignHash(llmqType, quorum->qc->quorumHash, id, msgHash).Get(); if (const auto *const sigs = sigShares.GetAllForSignHash(signHash)) { for (const auto& [quorumMemberIndex, _] : *sigs) { // re-announce every sigshare to every node @@ -1639,7 +1640,7 @@ void CSigSharesManager::ForceReAnnouncement(const CQuorumCPtr& quorum, Consensus MessageProcessingResult CSigSharesManager::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig) { LOCK(cs); - RemoveSigSharesForSession(recoveredSig.buildSignHash()); + RemoveSigSharesForSession(recoveredSig.buildSignHash().Get()); return {}; } diff --git a/src/llmq/signing_shares.h b/src/llmq/signing_shares.h index 8bbd9b24774cb..aeeedd2112888 100644 --- a/src/llmq/signing_shares.h +++ b/src/llmq/signing_shares.h @@ -308,7 +308,7 @@ class CSigSharesNodeState uint256 quorumHash; uint256 id; uint256 msgHash; - uint256 signHash; + llmq::SignHash signHash; CQuorumCPtr quorum; }; @@ -321,7 +321,7 @@ class CSigSharesNodeState uint256 quorumHash; uint256 id; uint256 msgHash; - uint256 signHash; + llmq::SignHash signHash; CQuorumCPtr quorum; diff --git a/src/rpc/quorums.cpp b/src/rpc/quorums.cpp index c25cb717dfb2a..db8ddb4c5c1c5 100644 --- a/src/rpc/quorums.cpp +++ b/src/rpc/quorums.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -607,8 +608,8 @@ static RPCHelpMan quorum_verify() throw JSONRPCError(RPC_INVALID_PARAMETER, "quorum not found"); } - uint256 signHash = llmq::BuildSignHash(llmqType, quorum->qc->quorumHash, id, msgHash); - return sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash); + llmq::SignHash signHash{llmqType, quorum->qc->quorumHash, id, msgHash}; + return sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get()); }, }; } diff --git a/src/test/evo_islock_tests.cpp b/src/test/evo_islock_tests.cpp index 1e35775c235f7..eb6767b6216dd 100644 --- a/src/test/evo_islock_tests.cpp +++ b/src/test/evo_islock_tests.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -80,8 +81,8 @@ BOOST_AUTO_TEST_CASE(deserialize_instantlock_from_realdata2) ss >> islock; // Verify the calculated signHash - auto signHash = llmq::BuildSignHash(Consensus::LLMQType::LLMQ_60_75, uint256S(quorumHashStr), islock.GetRequestId(), - islock.txid); + auto signHash = + llmq::SignHash(Consensus::LLMQType::LLMQ_60_75, uint256S(quorumHashStr), islock.GetRequestId(), islock.txid).Get(); BOOST_CHECK_EQUAL(signHash.ToString(), expectedSignHashStr); // Verify the txid field.