Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
23 changes: 18 additions & 5 deletions doc/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,12 @@ connections to it advertise:
- transaction relay (`relay=1` in the `version` message);
- compact block filters (`NODE_COMPACT_FILTERS`). (#7755)

A DKG member whose verification vector shares a coefficient with another
member's, as happens when one member copies another's contribution, is now
excluded from the commitment together with that member instead of the round
failing. Both receive the usual PoSe penalty for failed DKG participation.
(#8016)

## Bitcoin Core backports

This release continues the backporting of Bitcoin Core, adding well over 200
Expand Down Expand Up @@ -629,11 +635,9 @@ and RPC parameters.
cache format version was bumped for this: on the first start after upgrading,
the existing cache is discarded and governance objects and votes are synced
again from the network. (#7831)
- This release includes the fixes shipped in Dash Core v23.1.9, including the
consensus rule that rejects transactions with a negative `nVersion` once DIP3
is active, and the change that stopped masternodes from InstantSend-locking
provider transactions (#7858; software waiting for an islock on a provider
transaction should wait for a ChainLock or confirmations instead).
- Provider (masternode) transactions are no longer InstantSend-locked.
Software that waits for an InstantSend lock on a provider transaction should
wait for a ChainLock or for confirmations instead. (#7858)
- Network-adjusted time is no longer used to validate block timestamps or to
build block templates; both use the system clock. Keep the system clock
accurate. `-maxtimeadjustment` now only affects Dash subsystems that still use
Expand Down Expand Up @@ -720,6 +724,12 @@ and RPC parameters.
policy (previously 83 bytes). This better reflects the actual afforded
protections against CVE-2017-12842 and opens up additional use-cases of
smaller transaction sizes. (#7666)
- Asset Lock transactions with more than 100 inputs or larger than 20480 bytes
are now non-standard (`assetlocktx-too-many-inputs`, `assetlocktx-too-big`),
because Dash Platform cannot process them and the locked funds could be
lost. They are not accepted into the mempool or relayed, but remain valid in
a block. Software that funds Platform should keep its Asset Locks within these
limits. (#7359)
- Block template creation now checks credit pool limits across complete
transaction packages. Because Asset Unlock limits are cumulative, a package
may exceed the block's limit even though its transactions were accepted
Expand Down Expand Up @@ -949,6 +959,9 @@ and RPC parameters.
- Additional flags "in" and "out" have been added to `-whitelist` to control
whether permissions apply to incoming connections and/or manual (default:
incoming only). (#8005)
- `-allowprivatenet` no longer affects which masternode addresses are accepted.
On mainnet, testnet and devnets a masternode address in a private (RFC 1918)
range is rejected whether or not the option is set. (#7888)

### Removed options

Expand Down
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