Skip to content

Commit 81a03b9

Browse files
Merge #6826: refactor: introduce SignHash type for improved type safety in LLMQ signing
fd6ad86 refactor: Use uniform initialization syntax for SignHash (pasta) b34fd1b chore: resolve clang-format issue (pasta) ac9e5b7 refactor: standardize SignHash usage across LLMQ components (pasta) f446223 refactor: leverage SignHash type throughout LLMQ signing code (pasta) 0465463 refactor: extract BuildSignHash to SignHash type for better static typing (pasta) Pull request description: ## Summary This PR introduces a dedicated `SignHash` type to replace the generic `uint256` usage for LLMQ sign hashes, improving type safety and code clarity throughout the signing subsystem. ## Motivation Previously, `BuildSignHash` was a standalone function returning `uint256`, which: - Required forward declarations to avoid circular dependencies - Lacked type safety (any uint256 could be passed where a sign hash was expected) - Made code intent less clear ## Changes ### New SignHash Type - Created `SignHash` class extending `BaseHash<uint256>` (following existing patterns like `AssumeutxoHash`) - Encapsulates the hash calculation logic in the constructor - Provides serialization support for database compatibility - Includes `std::hash` specialization for use as unordered_map keys ### Refactored APIs - `CSigBase::buildSignHash()` now returns `SignHash` instead of `uint256` - Sign hash calculation moved from standalone function to `SignHash` constructor - Removed forward declarations from `assetlocktx.cpp` and `quorums.cpp` ### Type Safety Improvements - SignHash is now a distinct type throughout LLMQ code - Conversion to `uint256` via `.Get()` only at API boundaries (BLS verification, database operations) - Maintains clean abstraction - BLS library continues using `uint256` as it shouldn't depend on higher-level LLMQ types ## Benefits - **Elimination of circular dependencies** - No more forward declarations needed - **Better type safety** - Compiler catches type mismatches - **Clearer code intent** - Explicit when working with sign hashes vs other hashes - **Future flexibility** - Can add SignHash-specific methods if needed - **Zero runtime overhead** - All changes are compile-time type safety improvements ## Test plan - [x] Unit tests pass (`test_dash --run_test=evo_islock_tests`) - [x] Code compiles without warnings - [x] No circular dependencies introduced 🤖 Generated with [Claude Code](https://claude.ai/code) ACKs for top commit: UdjinM6: utACK fd6ad86 Tree-SHA512: dc4c54ce4154cebceaeca48e79fa8cf0fdeee2cd3025fa0c05f84b130dffe993af03bca0f80f9c3dc950bf0b2cd3986e706f5bb4824e9cf03058c97b98aaa1d5
2 parents 8b5310a + fd6ad86 commit 81a03b9

File tree

13 files changed

+150
-58
lines changed

13 files changed

+150
-58
lines changed

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ BITCOIN_CORE_H = \
255255
llmq/options.h \
256256
llmq/params.h \
257257
llmq/quorums.h \
258+
llmq/signhash.h \
258259
llmq/signing.h \
259260
llmq/signing_shares.h \
260261
llmq/snapshot.h \
@@ -517,6 +518,7 @@ libbitcoin_node_a_SOURCES = \
517518
llmq/ehf_signals.cpp \
518519
llmq/options.cpp \
519520
llmq/quorums.cpp \
521+
llmq/signhash.cpp \
520522
llmq/signing.cpp \
521523
llmq/signing_shares.cpp \
522524
llmq/snapshot.cpp \

src/evo/assetlocktx.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <llmq/commitment.h>
99
#include <llmq/quorums.h>
10+
#include <llmq/signhash.h>
1011

1112
#include <chainparams.h>
1213
#include <consensus/params.h>
@@ -21,10 +22,6 @@
2122

2223
using node::BlockManager;
2324

24-
namespace llmq {
25-
// forward declaration to avoid circular dependency
26-
uint256 BuildSignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash);
27-
} // namespace llmq
2825

2926
/**
3027
* Common code for Asset Lock and Asset Unlock
@@ -149,8 +146,8 @@ bool CAssetUnlockPayload::VerifySig(const llmq::CQuorumManager& qman, const uint
149146

150147
const uint256 requestId = ::SerializeHash(std::make_pair(ASSETUNLOCK_REQUESTID_PREFIX, index));
151148

152-
if (const uint256 signHash = llmq::BuildSignHash(llmqType, quorum->qc->quorumHash, requestId, msgHash);
153-
quorumSig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash)) {
149+
if (const llmq::SignHash signHash(llmqType, quorum->qc->quorumHash, requestId, msgHash);
150+
quorumSig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get())) {
154151
return true;
155152
}
156153

src/evo/mnhftx.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <evo/specialtx.h>
1010
#include <llmq/commitment.h>
1111
#include <llmq/quorums.h>
12+
#include <llmq/signhash.h>
1213
#include <llmq/signing.h>
1314
#include <node/blockstorage.h>
1415

@@ -102,8 +103,8 @@ bool MNHFTx::Verify(const llmq::CQuorumManager& qman, const uint256& quorumHash,
102103
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-missing-quorum");
103104
}
104105

105-
const uint256 signHash = llmq::BuildSignHash(llmqType, quorum->qc->quorumHash, requestId, msgHash);
106-
if (!sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash)) {
106+
const llmq::SignHash signHash{llmqType, quorum->qc->quorumHash, requestId, msgHash};
107+
if (!sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get())) {
107108
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-invalid");
108109
}
109110

src/instantsend/instantsend.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <instantsend/signing.h>
1818
#include <llmq/commitment.h>
1919
#include <llmq/quorums.h>
20+
#include <llmq/signhash.h>
2021
#include <masternode/sync.h>
2122
#include <spork.h>
2223
#include <stats/client.h>
@@ -282,7 +283,7 @@ std::unordered_set<uint256, StaticSaltedHasher> CInstantSendManager::ProcessPend
282283
// should not happen, but if one fails to select, all others will also fail to select
283284
return {};
284285
}
285-
uint256 signHash = BuildSignHash(llmq_params.type, quorum->qc->quorumHash, id, islock->txid);
286+
uint256 signHash = llmq::SignHash{llmq_params.type, quorum->qc->quorumHash, id, islock->txid}.Get();
286287
batchVerifier.PushMessage(nodeId, hash, signHash, islock->sig.Get(), quorum->qc->quorumPublicKey);
287288
verifyCount++;
288289

src/llmq/quorums.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
// Distributed under the MIT/X11 software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

5-
#include <llmq/quorums.h>
6-
#include <llmq/commitment.h>
75
#include <llmq/blockprocessor.h>
6+
#include <llmq/commitment.h>
87
#include <llmq/dkgsessionmgr.h>
98
#include <llmq/options.h>
109
#include <llmq/params.h>
10+
#include <llmq/quorums.h>
11+
#include <llmq/signhash.h>
1112
#include <llmq/utils.h>
1213

1314
#include <bls/bls.h>
@@ -36,8 +37,6 @@ static const std::string DB_QUORUM_QUORUM_VVEC = "q_Qqvvec";
3637
RecursiveMutex cs_data_requests;
3738
static std::unordered_map<CQuorumDataRequestKey, CQuorumDataRequest, StaticSaltedHasher> mapQuorumDataRequests GUARDED_BY(cs_data_requests);
3839

39-
// forward declaration to avoid circular dependency
40-
uint256 BuildSignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash);
4140

4241
static uint256 MakeQuorumKey(const CQuorum& q)
4342
{
@@ -1279,8 +1278,8 @@ VerifyRecSigStatus VerifyRecoveredSig(Consensus::LLMQType llmqType, const CChain
12791278
return VerifyRecSigStatus::NoQuorum;
12801279
}
12811280

1282-
uint256 signHash = BuildSignHash(llmqType, quorum->qc->quorumHash, id, msgHash);
1283-
const bool ret = sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash);
1281+
SignHash signHash{llmqType, quorum->qc->quorumHash, id, msgHash};
1282+
const bool ret = sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get());
12841283
return ret ? VerifyRecSigStatus::Valid : VerifyRecSigStatus::Invalid;
12851284
}
12861285
} // namespace llmq

src/llmq/signhash.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) 2025 The Dash Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <llmq/signhash.h>
6+
7+
#include <hash.h>
8+
#include <serialize.h>
9+
10+
#include <vector>
11+
12+
namespace llmq {
13+
14+
SignHash::SignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash)
15+
{
16+
CHashWriter h(SER_GETHASH, 0);
17+
h << llmqType;
18+
h << quorumHash;
19+
h << id;
20+
h << msgHash;
21+
m_hash = h.GetHash();
22+
}
23+
24+
} // namespace llmq

src/llmq/signhash.h

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright (c) 2025 The Dash Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_LLMQ_SIGNHASH_H
6+
#define BITCOIN_LLMQ_SIGNHASH_H
7+
8+
#include <llmq/params.h>
9+
#include <uint256.h>
10+
11+
#include <cstring>
12+
#include <vector>
13+
14+
#include <saltedhasher.h>
15+
#include <util/hash_type.h>
16+
17+
namespace llmq {
18+
19+
/**
20+
* SignHash is a strongly-typed wrapper for the hash used in LLMQ signing operations.
21+
* It encapsulates the hash calculation for quorum signatures, replacing the need for
22+
* BuildSignHash function and avoiding circular dependencies.
23+
*/
24+
class SignHash : public BaseHash<uint256>
25+
{
26+
public:
27+
SignHash() = default;
28+
using BaseHash<uint256>::BaseHash;
29+
30+
/**
31+
* Constructs a SignHash from the given parameters.
32+
* This replaces the previous BuildSignHash function.
33+
*/
34+
SignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash);
35+
36+
/**
37+
* Get the underlying uint256 hash value.
38+
*/
39+
const uint256& Get() const { return m_hash; }
40+
41+
// Serialization support
42+
template <typename Stream>
43+
void Serialize(Stream& s) const
44+
{
45+
s << m_hash;
46+
}
47+
48+
template <typename Stream>
49+
void Unserialize(Stream& s)
50+
{
51+
s >> m_hash;
52+
}
53+
};
54+
55+
// Salted hasher for llmq::SignHash that reuses the salted hasher for uint256
56+
struct SignHashSaltedHasher {
57+
std::size_t operator()(const SignHash& signHash) const noexcept { return StaticSaltedHasher{}(signHash.Get()); }
58+
};
59+
60+
} // namespace llmq
61+
62+
// Make SignHash hashable for use in unordered_map
63+
template <>
64+
struct std::hash<llmq::SignHash> {
65+
std::size_t operator()(const llmq::SignHash& signHash) const noexcept
66+
{
67+
// Use the first 8 bytes of the hash as the hash value
68+
const unsigned char* data = signHash.data();
69+
std::size_t result;
70+
std::memcpy(&result, data, sizeof(result));
71+
return result;
72+
}
73+
};
74+
75+
76+
#endif // BITCOIN_LLMQ_SIGNHASH_H

src/llmq/signing.cpp

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <llmq/commitment.h>
88
#include <llmq/quorums.h>
9+
#include <llmq/signhash.h>
910
#include <llmq/signing_shares.h>
1011

1112
#include <bls/bls_batchverifier.h>
@@ -161,7 +162,7 @@ void CRecoveredSigsDb::WriteRecoveredSig(const llmq::CRecoveredSig& recSig)
161162

162163
// store by signHash
163164
auto signHash = recSig.buildSignHash();
164-
auto k4 = std::make_tuple(std::string("rs_s"), signHash);
165+
auto k4 = std::make_tuple(std::string("rs_s"), signHash.Get());
165166
batch.Write(k4, (uint8_t)1);
166167

167168
// store by current time. Allows fast cleanup of old recSigs
@@ -173,7 +174,7 @@ void CRecoveredSigsDb::WriteRecoveredSig(const llmq::CRecoveredSig& recSig)
173174
{
174175
LOCK(cs_cache);
175176
hasSigForIdCache.insert(std::make_pair(recSig.getLlmqType(), recSig.getId()), true);
176-
hasSigForSessionCache.insert(signHash, true);
177+
hasSigForSessionCache.insert(signHash.Get(), true);
177178
hasSigForHashCache.insert(recSig.GetHash(), true);
178179
}
179180
}
@@ -190,7 +191,7 @@ void CRecoveredSigsDb::RemoveRecoveredSig(CDBBatch& batch, Consensus::LLMQType l
190191
auto k1 = std::make_tuple(std::string("rs_r"), recSig.getLlmqType(), recSig.getId());
191192
auto k2 = std::make_tuple(std::string("rs_r"), recSig.getLlmqType(), recSig.getId(), recSig.getMsgHash());
192193
auto k3 = std::make_tuple(std::string("rs_h"), recSig.GetHash());
193-
auto k4 = std::make_tuple(std::string("rs_s"), signHash);
194+
auto k4 = std::make_tuple(std::string("rs_s"), signHash.Get());
194195
batch.Erase(k1);
195196
batch.Erase(k2);
196197
if (deleteHashKey) {
@@ -211,7 +212,7 @@ void CRecoveredSigsDb::RemoveRecoveredSig(CDBBatch& batch, Consensus::LLMQType l
211212

212213
LOCK(cs_cache);
213214
hasSigForIdCache.erase(std::make_pair(recSig.getLlmqType(), recSig.getId()));
214-
hasSigForSessionCache.erase(signHash);
215+
hasSigForSessionCache.erase(signHash.Get());
215216
if (deleteHashKey) {
216217
hasSigForHashCache.erase(recSig.GetHash());
217218
}
@@ -469,7 +470,7 @@ void CSigningManager::CollectPendingRecoveredSigsToVerify(
469470

470471
bool alreadyHave = db.HasRecoveredSigForHash(recSig->GetHash());
471472
if (!alreadyHave) {
472-
uniqueSignHashes.emplace(nodeId, recSig->buildSignHash());
473+
uniqueSignHashes.emplace(nodeId, recSig->buildSignHash().Get());
473474
retSigShares[nodeId].emplace_back(recSig);
474475
}
475476
ns.erase(ns.begin());
@@ -553,7 +554,8 @@ bool CSigningManager::ProcessPendingRecoveredSigs(PeerManager& peerman)
553554
}
554555

555556
const auto& quorum = quorums.at(std::make_pair(recSig->getLlmqType(), recSig->getQuorumHash()));
556-
batchVerifier.PushMessage(nodeId, recSig->GetHash(), recSig->buildSignHash(), recSig->sig.Get(), quorum->qc->quorumPublicKey);
557+
batchVerifier.PushMessage(nodeId, recSig->GetHash(), recSig->buildSignHash().Get(), recSig->sig.Get(),
558+
quorum->qc->quorumPublicKey);
557559
verifyCount++;
558560
}
559561
}
@@ -605,7 +607,7 @@ void CSigningManager::ProcessRecoveredSig(const std::shared_ptr<const CRecovered
605607
CRecoveredSig otherRecoveredSig;
606608
if (db.GetRecoveredSigById(llmqType, recoveredSig->getId(), otherRecoveredSig)) {
607609
auto otherSignHash = otherRecoveredSig.buildSignHash();
608-
if (signHash != otherSignHash) {
610+
if (signHash.Get() != otherSignHash.Get()) {
609611
// this should really not happen, as each masternode is participating in only one vote,
610612
// even if it's a member of multiple quorums. so a majority is only possible on one quorum and one msgHash per id
611613
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)
836838
}
837839
}
838840

839-
uint256 CSigBase::buildSignHash() const
840-
{
841-
return BuildSignHash(llmqType, quorumHash, id, msgHash);
842-
}
841+
SignHash CSigBase::buildSignHash() const { return SignHash(llmqType, quorumHash, id, msgHash); }
843842

844-
uint256 BuildSignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash)
845-
{
846-
CHashWriter h(SER_GETHASH, 0);
847-
h << llmqType;
848-
h << quorumHash;
849-
h << id;
850-
h << msgHash;
851-
return h.GetHash();
852-
}
853843

854844
bool IsQuorumActive(Consensus::LLMQType llmqType, const CQuorumManager& qman, const uint256& quorumHash)
855845
{

src/llmq/signing.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
#include <bls/bls.h>
99
#include <gsl/pointers.h>
1010
#include <llmq/params.h>
11+
#include <llmq/signhash.h>
1112
#include <net_types.h>
1213
#include <protocol.h>
1314
#include <random.h>
1415
#include <saltedhasher.h>
1516
#include <sync.h>
16-
#include <util/threadinterrupt.h>
1717
#include <unordered_lru_cache.h>
18+
#include <util/threadinterrupt.h>
1819

1920
#include <string_view>
2021
#include <unordered_map>
@@ -67,7 +68,7 @@ class CSigBase
6768
return msgHash;
6869
}
6970

70-
[[nodiscard]] uint256 buildSignHash() const;
71+
[[nodiscard]] SignHash buildSignHash() const;
7172
};
7273

7374
class CRecoveredSig : virtual public CSigBase
@@ -263,8 +264,6 @@ void IterateNodesRandom(NodesContainer& nodeStates, Continue&& cont, Callback&&
263264
}
264265
}
265266

266-
uint256 BuildSignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash);
267-
268267
bool IsQuorumActive(Consensus::LLMQType llmqType, const CQuorumManager& qman, const uint256& quorumHash);
269268

270269
} // namespace llmq

0 commit comments

Comments
 (0)