Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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 \
Expand Down
9 changes: 3 additions & 6 deletions src/evo/assetlocktx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <llmq/commitment.h>
#include <llmq/quorums.h>
#include <llmq/signhash.h>

#include <chainparams.h>
#include <consensus/params.h>
Expand All @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down
5 changes: 3 additions & 2 deletions src/evo/mnhftx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <evo/specialtx.h>
#include <llmq/commitment.h>
#include <llmq/quorums.h>
#include <llmq/signhash.h>
#include <llmq/signing.h>
#include <node/blockstorage.h>

Expand Down Expand Up @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use {}

Suggested change
const llmq::SignHash signHash(llmqType, quorum->qc->quorumHash, requestId, msgHash);
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");
}

Expand Down
3 changes: 2 additions & 1 deletion src/instantsend/instantsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <instantsend/signing.h>
#include <llmq/commitment.h>
#include <llmq/quorums.h>
#include <llmq/signhash.h>
#include <masternode/sync.h>
#include <spork.h>
#include <stats/client.h>
Expand Down Expand Up @@ -285,7 +286,7 @@ std::unordered_set<uint256, StaticSaltedHasher> 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();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
uint256 signHash = llmq::SignHash(llmq_params.type, quorum->qc->quorumHash, id, islock->txid).Get();
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++;

Expand Down
7 changes: 3 additions & 4 deletions src/llmq/quorums.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <llmq/dkgsessionmgr.h>
#include <llmq/options.h>
#include <llmq/params.h>
#include <llmq/signhash.h>
#include <llmq/utils.h>

#include <bls/bls.h>
Expand Down Expand Up @@ -36,8 +37,6 @@ static const std::string DB_QUORUM_QUORUM_VVEC = "q_Qqvvec";
RecursiveMutex cs_data_requests;
static std::unordered_map<CQuorumDataRequestKey, CQuorumDataRequest, StaticSaltedHasher> 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)
{
Expand Down Expand Up @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SignHash signHash(llmqType, quorum->qc->quorumHash, id, msgHash);
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
25 changes: 25 additions & 0 deletions src/llmq/signhash.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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 <llmq/signhash.h>

#include <hash.h>
#include <serialize.h>

#include <vector>

namespace llmq
{

SignHash::SignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash)
{
CHashWriter h(SER_GETHASH, 0);
h << llmqType;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure deterministic encoding of enum; drop unused include.

  • Serialize LLMQType explicitly as uint8_t for stability.
  • Remove unused .

Apply:

-#include <vector>
@@
-    h << llmqType;
+    h << static_cast<uint8_t>(llmqType);

Also applies to: 10-10

🤖 Prompt for AI Agents
In src/llmq/signhash.cpp around line 17, the enum value llmqType is being
serialized without an explicit fixed-size type which risks non-deterministic
encoding; cast/convert llmqType to uint8_t (or static_cast<uint8_t>(llmqType))
when writing to h so it is serialized as a single byte, and remove the
now-unused #include <vector> from the top of the file to clean up imports.

h << quorumHash;
h << id;
h << msgHash;
m_hash = h.GetHash();
}

} // namespace llmq
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: \n

67 changes: 67 additions & 0 deletions src/llmq/signhash.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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 <llmq/params.h>
#include <uint256.h>

#include <cstring>
#include <vector>

#include <util/hash_type.h>

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<uint256> {
public:
SignHash() = default;

/**
* 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; }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix inheritance: BaseHash must be tagged with the derived type, not uint256.

Using BaseHash defeats the type-tagging (and some compile-time type-safety) this PR aims to introduce. The Tag template parameter should be the wrapper type itself.

Apply this diff:

-#include <llmq/params.h>
+#include <consensus/llmq_types.h>
 #include <uint256.h>
 
-#include <cstring>
-#include <vector>
+#include <cstring>
+#include <cstdint>
+#include <functional>
 
 #include <util/hash_type.h>
 
 namespace llmq
 {
@@
-class SignHash : public BaseHash<uint256> {
+class SignHash : public BaseHash<SignHash> {
 public:
     SignHash() = default;
@@
-    SignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash);
+    explicit SignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash);

Rationale:

  • BaseHash expects T to be the tag type, not the underlying hash.
  • Include the minimal header that declares Consensus::LLMQType to avoid heavier dependencies.
  • Explicit constructor prevents unintended implicit conversions.
  • Add missing standard headers required by std::hash specialization and fixed-width integer usage.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class SignHash : public BaseHash<uint256> {
public:
SignHash() = default;
/**
* 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; }
#include <consensus/llmq_types.h>
#include <uint256.h>
#include <cstring>
#include <cstdint>
#include <functional>
#include <util/hash_type.h>
namespace llmq {
class SignHash : public BaseHash<SignHash> {
public:
SignHash() = default;
/**
* Constructs a SignHash from the given parameters.
* This replaces the previous BuildSignHash function.
*/
explicit 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<typename Stream>
void Serialize(Stream& s) const
{
s << m_hash;
}

template<typename Stream>
void Unserialize(Stream& s)
{
s >> m_hash;
}
};

} // namespace llmq

// Make SignHash hashable for use in unordered_map
template<>
struct std::hash<llmq::SignHash> {
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: \n here too

26 changes: 9 additions & 17 deletions src/llmq/signing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <llmq/commitment.h>
#include <llmq/quorums.h>
#include <llmq/signhash.h>
#include <llmq/signing_shares.h>

#include <bls/bls_batchverifier.h>
Expand Down Expand Up @@ -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
Expand All @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider further refactoring by using SignHash as a type for hasSigForSessionCache, for uniqueSignHashes, as argument in PushMessage, InitSession, as a member of SignSession, etc

hasSigForHashCache.insert(recSig.GetHash(), true);
}
}
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -553,7 +554,7 @@ 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++;
}
}
Expand Down Expand Up @@ -605,7 +606,7 @@ void CSigningManager::ProcessRecoveredSig(const std::shared_ptr<const CRecovered
CRecoveredSig otherRecoveredSig;
if (db.GetRecoveredSigById(llmqType, recoveredSig->getId(), 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__,
Expand Down Expand Up @@ -836,20 +837,11 @@ void CSigningManager::WorkThreadMain(PeerManager& peerman)
}
}

uint256 CSigBase::buildSignHash() const
SignHash CSigBase::buildSignHash() const
{
return BuildSignHash(llmqType, quorumHash, id, msgHash);
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)
{
Expand Down
5 changes: 2 additions & 3 deletions src/llmq/signing.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <bls/bls.h>
#include <gsl/pointers.h>
#include <llmq/params.h>
#include <llmq/signhash.h>
#include <net_types.h>
#include <protocol.h>
#include <random.h>
Expand Down Expand Up @@ -67,7 +68,7 @@ class CSigBase
return msgHash;
}

[[nodiscard]] uint256 buildSignHash() const;
[[nodiscard]] SignHash buildSignHash() const;
};

class CRecoveredSig : virtual public CSigBase
Expand Down Expand Up @@ -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
Expand Down
19 changes: 10 additions & 9 deletions src/llmq/signing_shares.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <llmq/options.h>
#include <llmq/quorums.h>
#include <llmq/commitment.h>
#include <llmq/signhash.h>
#include <llmq/signing.h>

#include <bls/bls_batchverifier.h>
Expand All @@ -27,7 +28,7 @@ namespace llmq
{
void CSigShare::UpdateKey()
{
key.first = this->buildSignHash();
key.first = this->buildSignHash().Get();
key.second = quorumMember;
}

Expand Down Expand Up @@ -125,9 +126,9 @@ 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);
InitSession(s, signHash.Get(), ann);
}
return s;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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__,
Expand Down Expand Up @@ -1556,7 +1557,7 @@ std::optional<CSigShare> 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
Expand Down Expand Up @@ -1592,7 +1593,7 @@ std::optional<CSigShare> 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()) {
Expand All @@ -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
Expand All @@ -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 {};
}

Expand Down
Loading
Loading