Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3174d5f
fix(instantsend): propagate !cs_timingsTxSeen to the callers of Remov…
UdjinM6 Oct 29, 2026
c8627a6
fix(llmq): drop DKG members whose verification vectors share coeffici…
UdjinM6 Oct 29, 2026
1ec6aaa
test: cover DKG members dealing identical contributions
UdjinM6 Oct 29, 2026
12bbd21
fix(llmq): weight batched DKG contribution verification
UdjinM6 Oct 29, 2026
593f252
fix(bls): weight batch-verified signatures so complementary errors ca…
UdjinM6 Oct 29, 2026
5d9fe74
fix(governance): don't vote NO-FUNDING on triggers before their matur…
PastaPastaPasta Oct 29, 2026
bd948aa
fix(governance): don't vote on triggers while not synced
PastaPastaPasta Oct 29, 2026
95fbf22
fix(bls): stop weighted contribution batch verification on an invalid…
PastaPastaPasta Oct 29, 2026
dfd9820
fix(llmq): accept equal DKG secret key shares when aggregating a memb…
PastaPastaPasta Oct 29, 2026
a28c030
Merge dashpay/dash-internal#15: fix(llmq): v23.1.x werror build, DKG …
PastaPastaPasta Oct 30, 2026
a08d35e
Merge dashpay/dash-internal#19: fix(governance): prevent governance D…
PastaPastaPasta Oct 30, 2026
5f17c2e
Merge dashpay/dash-internal#16: fix(bls): weight batch-verified signa…
PastaPastaPasta Oct 30, 2026
aeffa7e
Merge dashpay/dash-internal#17: fix(llmq): weight batched DKG contrib…
PastaPastaPasta Oct 30, 2026
01dc7e0
chore: prepare v23.1.10 release
PastaPastaPasta Oct 30, 2026
703b2a0
Merge #7983: chore: prepare v23.1.10 release
PastaPastaPasta Oct 30, 2026
f23f748
fix(llmq): drop DKG members whose verification vectors share coeffici…
UdjinM6 Oct 29, 2026
1c356f4
test: cover DKG members dealing identical contributions
UdjinM6 Oct 29, 2026
8615244
fix(llmq): accept equal DKG secret key shares when aggregating a memb…
PastaPastaPasta Oct 29, 2026
a79f432
fix(governance): don't vote NO-FUNDING on triggers before their matur…
PastaPastaPasta Oct 29, 2026
b5c7985
fix(governance): don't vote on triggers while not synced
PastaPastaPasta Oct 29, 2026
ed96227
fix(bls): weight batch-verified signatures so complementary errors ca…
UdjinM6 Oct 29, 2026
e04acba
fix(llmq): weight batched DKG contribution verification
UdjinM6 Oct 29, 2026
e6f3ad4
fix(bls): stop weighted contribution batch verification on an invalid…
PastaPastaPasta Oct 29, 2026
66bbbcc
Merge tag 'v23.1.10' into v24.0.x
PastaPastaPasta Nov 2, 2026
fcd332b
doc: state the provider-transaction InstantSend change on its own
PastaPastaPasta Nov 2, 2026
cdb250c
doc: note operator-relevant v23.1.x patch-release changes for v24.0.0
PastaPastaPasta Nov 2, 2026
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
55 changes: 50 additions & 5 deletions src/active/dkgsession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include <chain.h>
#include <deploymentstatus.h>
#include <hash.h>
#include <validation.h>

#include <cxxtimer.hpp>
Expand Down Expand Up @@ -49,7 +50,23 @@ std::optional<CDKGContribution> ActiveDKGSession::Contribute()

cxxtimer::Timer t1(true);
logger.Batch("generating contributions");
if (!blsWorker.GenerateContributions(params.threshold, memberIds, vvecContribution, m_sk_contributions)) {
if (ShouldSimulateError(DKGError::type::CONTRIBUTION_CLONE)) {
// Derive the contribution from the quorum alone, so every member simulating this deals the same
// contribution, as if it had copied another member's
logger.Batch("dealing a cloned contribution");
std::vector<CBLSSecretKey> svec(params.threshold);
vvecContribution = std::make_shared<std::vector<CBLSPublicKey>>();
for (const auto i : util::irange(svec.size())) {
uint256 seed{::SerializeHash(std::make_pair(m_quorum_base_block_index->GetBlockHash(), uint32_t(i)))};
*seed.begin() = 0; // keep it below the group order
svec[i] = CBLSSecretKey{Span{seed}};
vvecContribution->emplace_back(svec[i].GetPublicKey());
}
m_sk_contributions.resize(memberIds.size());
for (const auto i : util::irange(memberIds.size())) {
m_sk_contributions[i].SecretKeyShare(svec, memberIds[i]);
}
} else if (!blsWorker.GenerateContributions(params.threshold, memberIds, vvecContribution, m_sk_contributions)) {
// this should never happen actually
logger.Batch("GenerateContributions failed");
return std::nullopt;
Expand Down Expand Up @@ -121,9 +138,9 @@ std::optional<CDKGContribution> ActiveDKGSession::SendContributions()
}

// Verifies all pending secret key contributions in one batch
// This is done by aggregating the verification vectors belonging to the secret key contributions
// This is done by aggregating randomly weighted verification vectors belonging to the secret key contributions
// The resulting aggregated vvec is then used to recover a public key share
// The public key share must match the public key belonging to the aggregated secret key contributions
// The public key share must match the equally weighted public keys of the secret key contributions
// See CBLSWorker::VerifyContributionShares for more details.
void ActiveDKGSession::VerifyPendingContributions()
{
Expand All @@ -140,6 +157,7 @@ void ActiveDKGSession::VerifyPendingContributions()
std::vector<size_t> memberIndexes;
std::vector<BLSVerificationVectorPtr> vvecs;
std::vector<CBLSSecretKey> skContributions;
bool anyLegacyVvec{false};

for (const auto& idx : pendingContributionVerifications) {
const auto& m = members[idx];
Expand All @@ -148,6 +166,7 @@ void ActiveDKGSession::VerifyPendingContributions()
}
memberIndexes.emplace_back(idx);
vvecs.emplace_back(receivedVvecs[idx]);
anyLegacyVvec |= receivedVvecsLegacyScheme[idx];
skContributions.emplace_back(receivedSkContributions[idx]);
// Write here to definitely store one contribution for each member no matter if
// our share is valid or not, could be that others are still correct
Expand All @@ -165,7 +184,9 @@ void ActiveDKGSession::VerifyPendingContributions()
return;
}

auto result = blsWorker.VerifyContributionShares(myId, vvecs, skContributions);
// Weighted aggregation needs points that passed subgroup checks, which legacy-scheme deserialization skips
auto result = blsWorker.VerifyContributionShares(myId, vvecs, skContributions, /*parallel=*/true,
/*aggregated=*/!anyLegacyVvec);
if (result.size() != memberIndexes.size()) {
logger.Batch("VerifyContributionShares returned result of size %d but size %d was expected, something is wrong", result.size(), memberIndexes.size());
return;
Expand Down Expand Up @@ -453,7 +474,25 @@ std::optional<CDKGPrematureCommitment> ActiveDKGSession::VerifyAndCommit()
}
}

if (!badMembers.empty() || !openComplaintMembers.empty()) {
// A member can copy another member's whole contribution: every share verifies, but the duplicated
// coefficients make the quorum verification vector impossible to build, so no commitment could be made.
// The copy can't be told from the original, so drop both; members that agree on the remaining members
// and hold the same contributions drop the same ones and still agree on the commitment.
std::vector<size_t> remainingMembers;
std::vector<BLSVerificationVectorPtr> remainingVvecs;
for (const auto& m : members) {
if (!m->bad) {
remainingMembers.emplace_back(m->idx);
remainingVvecs.emplace_back(receivedVvecs[m->idx]);
}
}
std::vector<size_t> sharedCoefficientMembers;
for (const auto i : CBLSWorker::FindVerificationVectorsWithSharedCoefficients(remainingVvecs)) {
MarkBadMember(remainingMembers[i]);
sharedCoefficientMembers.emplace_back(remainingMembers[i]);
}

if (!badMembers.empty() || !openComplaintMembers.empty() || !sharedCoefficientMembers.empty()) {
logger.Batch("verification result:");
}
if (!badMembers.empty()) {
Expand All @@ -468,6 +507,12 @@ std::optional<CDKGPrematureCommitment> ActiveDKGSession::VerifyAndCommit()
logger.Batch(" %s", members[idx]->dmn->proTxHash.ToString());
}
}
if (!sharedCoefficientMembers.empty()) {
logger.Batch(" members sharing verification vector coefficients and now marked as bad:");
for (const auto& idx : sharedCoefficientMembers) {
logger.Batch(" %s", members[idx]->dmn->proTxHash.ToString());
}
}

logger.Flush();

Expand Down
3 changes: 2 additions & 1 deletion src/bench/bls_dkg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ class DKG

void VerifyContributionShares(size_t whoAmI, const std::set<size_t>& invalidIndexes, bool aggregated)
{
auto result = blsWorker.VerifyContributionShares(members[whoAmI].id, receivedVvecs, receivedSkShares, aggregated);
auto result = blsWorker.VerifyContributionShares(members[whoAmI].id, receivedVvecs, receivedSkShares,
/*parallel=*/true, aggregated);
for (const size_t i : util::irange(receivedVvecs.size())) {
if (invalidIndexes.count(i)) {
assert(!result[i]);
Expand Down
14 changes: 14 additions & 0 deletions src/bls/bls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,13 @@ bool CBLSPublicKey::PublicKeyShare(Span<CBLSPublicKey> mpk, const CBLSId& _id)
return true;
}

void CBLSPublicKey::MulInsecure(const CBLSSecretKey& factor)
{
assert(IsValid() && factor.IsValid());
impl = factor.impl * impl;
cachedHash.SetNull();
}

bool CBLSPublicKey::DHKeyExchange(const CBLSSecretKey& sk, const CBLSPublicKey& pk)
{
fValid = false;
Expand Down Expand Up @@ -293,6 +300,13 @@ void CBLSSignature::SubInsecure(const CBLSSignature& o)
cachedHash.SetNull();
}

void CBLSSignature::MulInsecure(const CBLSSecretKey& factor)
{
assert(IsValid() && factor.IsValid());
impl = factor.impl * impl;
cachedHash.SetNull();
}

bool CBLSSignature::VerifyInsecure(const CBLSPublicKey& pubKey, const uint256& hash, const bool specificLegacyScheme) const
{
if (!IsValid() || !pubKey.IsValid()) {
Expand Down
3 changes: 3 additions & 0 deletions src/bls/bls.h
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ class CBLSPublicKey : public CBLSWrapper<bls::G1Element, BLS_CURVE_PUBKEY_SIZE,
void AggregateInsecure(const CBLSPublicKey& o);
static CBLSPublicKey AggregateInsecure(Span<CBLSPublicKey> pks);

void MulInsecure(const CBLSSecretKey& factor);

bool PublicKeyShare(Span<CBLSPublicKey> mpk, const CBLSId& id);
bool DHKeyExchange(const CBLSSecretKey& sk, const CBLSPublicKey& pk);

Expand Down Expand Up @@ -360,6 +362,7 @@ class CBLSSignature : public CBLSWrapper<bls::G2Element, BLS_CURVE_SIG_SIZE, CBL
static CBLSSignature AggregateSecure(Span<CBLSSignature> sigs, Span<CBLSPublicKey> pks, const uint256& hash);

void SubInsecure(const CBLSSignature& o);
void MulInsecure(const CBLSSecretKey& factor);
[[nodiscard]] bool VerifyInsecure(const CBLSPublicKey& pubKey, const uint256& hash, const bool specificLegacyScheme) const;
[[nodiscard]] bool VerifyInsecure(const CBLSPublicKey& pubKey, const uint256& hash) const;
[[nodiscard]] bool VerifyInsecureAggregated(Span<CBLSPublicKey> pubKeys, Span<uint256> hashes) const;
Expand Down
36 changes: 31 additions & 5 deletions src/bls/bls_batchverifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,26 @@
#define DASH_CRYPTO_BLS_BATCHVERIFIER_H

#include <bls/bls.h>
#include <random.h>

#include <algorithm>
#include <array>
#include <map>
#include <vector>

//! A secret random nonzero scalar of numBytes bytes (at most 16, so well below the group order). Weighting each
//! signature and its public key by one makes errors in several signatures unable to cancel out in an aggregate.
inline CBLSSecretKey MakeBatchVerificationWeight(FastRandomContext& rng, size_t numBytes = 16)
{
assert(numBytes > 0 && numBytes <= 16);
std::array<uint8_t, BLS_CURVE_SECKEY_SIZE> bytes{};
do {
const uint256 rand{rng.rand256()};
std::copy(rand.begin(), rand.begin() + numBytes, bytes.end() - numBytes);
} while (std::all_of(bytes.begin(), bytes.end(), [](uint8_t b) { return b == 0; }));
return CBLSSecretKey{bytes};
}

template<typename SourceId, typename MessageId>
class CBLSBatchVerifier
{
Expand All @@ -19,6 +35,9 @@ class CBLSBatchVerifier
uint256 msgHash;
CBLSSignature sig;
CBLSPublicKey pubKey;
//! sig and pubKey multiplied by the same MakeBatchVerificationWeight()
CBLSSignature weightedSig;
CBLSPublicKey weightedPubKey;
};

using MessageMap = std::map<MessageId, Message>;
Expand All @@ -32,6 +51,8 @@ class CBLSBatchVerifier
MessageMap messages;
MessagesBySourceMap messagesBySource;

FastRandomContext rng;

public:
std::set<SourceId> badSources;
std::set<MessageId> badMessages;
Expand All @@ -48,7 +69,12 @@ class CBLSBatchVerifier
{
assert(sig.IsValid() && pubKey.IsValid());

auto it = messages.emplace(msgId, Message{msgId, msgHash, sig, pubKey}).first;
auto [it, inserted] = messages.emplace(msgId, Message{msgId, msgHash, sig, pubKey, sig, pubKey});
if (inserted) {
const CBLSSecretKey weight{MakeBatchVerificationWeight(rng)};
it->second.weightedSig.MulInsecure(weight);
it->second.weightedPubKey.MulInsecure(weight);
}
messagesBySource[sourceId].emplace_back(it);

if (subBatchSize != 0 && messages.size() >= subBatchSize) {
Expand Down Expand Up @@ -155,8 +181,8 @@ class CBLSBatchVerifier
continue;
}

sigsToAggregate.push_back(msg.sig);
pubKeysToAggregate.push_back(msg.pubKey);
sigsToAggregate.push_back(msg.weightedSig);
pubKeysToAggregate.push_back(msg.weightedPubKey);
}

CBLSPublicKey aggPubKey = CBLSPublicKey::AggregateInsecure(pubKeysToAggregate);
Expand Down Expand Up @@ -210,8 +236,8 @@ class CBLSBatchVerifier

if (dups.emplace(msg.msgId).second) {
msgHashes.emplace_back(msgHash);
pubKeys.emplace_back(msg.pubKey);
sigsToAggregate.push_back(msg.sig);
pubKeys.emplace_back(msg.weightedPubKey);
sigsToAggregate.push_back(msg.weightedSig);
}

messageIts.pop_back();
Expand Down
Loading
Loading