Line data Source code
1 : // Copyright (c) 2018-2021 The Dash Core developers
2 : // Copyright (c) 2022 The PIVX Core developers
3 : // Distributed under the MIT/X11 software license, see the accompanying
4 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 :
6 : #include "llmq/quorums_dkgsession.h"
7 :
8 : #include "activemasternode.h"
9 : #include "bls/key_io.h"
10 : #include "chainparams.h"
11 : #include "cxxtimer.h"
12 : #include "init.h"
13 : #include "llmq/quorums_connections.h"
14 : #include "llmq/quorums_commitment.h"
15 : #include "llmq/quorums_debug.h"
16 : #include "llmq/quorums_dkgsessionmgr.h"
17 : #include "net.h"
18 : #include "tiertwo/masternode_meta_manager.h"
19 : #include "univalue.h"
20 : #include "validation.h"
21 :
22 : namespace llmq
23 : {
24 :
25 : static RecursiveMutex cs_simDkgError;
26 : static std::map<std::string, double> simDkgErrorMap = {
27 : {"contribution-omit", 0.0},
28 : {"contribution-lie", 0.0},
29 : {"complain-lie", 0.0},
30 : {"justify-lie", 0.0},
31 : {"justify-omit", 0.0},
32 : {"commit-omit", 0.0},
33 : {"commit-lie", 0.0}
34 : };
35 :
36 59 : bool SetSimulatedDKGErrorRate(const std::string& error_type, double rate)
37 : {
38 59 : LOCK(cs_simDkgError);
39 59 : auto it = simDkgErrorMap.find(error_type);
40 59 : if (it != simDkgErrorMap.end()) {
41 58 : it->second = rate;
42 58 : return true;
43 : }
44 : return false;
45 : }
46 :
47 450 : static double GetSimulatedErrorRate(const std::string& error_type)
48 : {
49 450 : LOCK(cs_simDkgError);
50 450 : auto it = simDkgErrorMap.find(error_type);
51 450 : if (it != simDkgErrorMap.end()) {
52 450 : return it->second;
53 : }
54 : return 0;
55 : }
56 :
57 450 : bool CDKGSession::ShouldSimulateError(const std::string& error_type)
58 : {
59 450 : if (params.type != Consensus::LLMQType::LLMQ_TEST) {
60 : // error simulation available only for LLMQ_TEST
61 : return false;
62 : }
63 450 : double rate = GetSimulatedErrorRate(error_type);
64 450 : return GetRandBool(rate);
65 : }
66 :
67 1501 : CDKGLogger::CDKGLogger(const CDKGSession& _quorumDkg, const std::string& _func) :
68 1501 : CDKGLogger(_quorumDkg.params.name, _quorumDkg.pindexQuorum->GetBlockHash(), _quorumDkg.pindexQuorum->nHeight, _quorumDkg.AreWeMember(), _func)
69 : {
70 1501 : }
71 :
72 1501 : CDKGLogger::CDKGLogger(const std::string& _llmqTypeName, const uint256& _quorumHash, int _height, bool _areWeMember, const std::string& _func) :
73 1501 : CBatchedLogger(g_logger, BCLog::DKG, strprintf("QuorumDKG(type=%s, height=%d, member=%d, func=%s)", _llmqTypeName, _height, _areWeMember, _func))
74 : {
75 1501 : }
76 :
77 :
78 62 : CDKGComplaint::CDKGComplaint(const Consensus::LLMQParams& params) :
79 186 : badMembers((size_t)params.size), complainForMembers((size_t)params.size)
80 : {
81 62 : }
82 :
83 57 : CDKGPrematureCommitment::CDKGPrematureCommitment(const Consensus::LLMQParams& params) :
84 342 : validMembers((size_t)params.size)
85 : {
86 57 : }
87 :
88 504 : CDKGMember::CDKGMember(CDeterministicMNCPtr _dmn, size_t _idx) :
89 : dmn(_dmn),
90 : idx(_idx),
91 504 : id(_dmn->proTxHash)
92 : {
93 :
94 504 : }
95 :
96 179 : bool CDKGSession::Init(const CBlockIndex* _pindexQuorum, const std::vector<CDeterministicMNCPtr>& mns, const uint256& _myProTxHash)
97 : {
98 179 : if (mns.size() < (size_t)params.minSize) {
99 : // don't use CDKGLogger until CDKGSession is fully initialized
100 11 : LogPrint(BCLog::DKG, "CDKGSession::%s: not enough members (%d < %d), aborting init\n", __func__, mns.size(), params.minSize);
101 11 : return false;
102 : }
103 :
104 168 : pindexQuorum = _pindexQuorum;
105 :
106 168 : members.resize(mns.size());
107 168 : memberIds.resize(members.size());
108 168 : receivedVvecs.resize(members.size());
109 168 : receivedSkContributions.resize(members.size());
110 :
111 672 : for (size_t i = 0; i < mns.size(); i++) {
112 504 : members[i] = std::unique_ptr<CDKGMember>(new CDKGMember(mns[i], i));
113 504 : membersMap.emplace(members[i]->dmn->proTxHash, i);
114 504 : memberIds[i] = members[i]->id;
115 : }
116 :
117 336 : if (!_myProTxHash.IsNull()) {
118 491 : for (size_t i = 0; i < members.size(); i++) {
119 409 : auto& m = members[i];
120 409 : if (m->dmn->proTxHash == _myProTxHash) {
121 82 : myIdx = i;
122 82 : myProTxHash = _myProTxHash;
123 82 : myId = m->id;
124 82 : break;
125 : }
126 : }
127 : }
128 :
129 336 : if (myProTxHash.IsNull()) {
130 86 : LogPrint(BCLog::DKG, "CDKGSession::%s: initialized as observer. mns=%d\n", __func__, mns.size());
131 : } else {
132 82 : quorumDKGDebugManager->InitLocalSessionStatus(params.type, pindexQuorum->GetBlockHash(), pindexQuorum->nHeight);
133 82 : relayMembers = GetQuorumRelayMembers(mns, myIdx);
134 82 : LogPrint(BCLog::DKG, "CDKGSession::%s: initialized as member. mns=%d\n", __func__, mns.size());
135 : }
136 :
137 : return true;
138 : }
139 :
140 143 : void CDKGSession::Contribute(CDKGPendingMessages& pendingMessages)
141 : {
142 212 : CDKGLogger logger(*this, __func__);
143 :
144 286 : if (!AreWeMember()) {
145 74 : return;
146 : }
147 :
148 138 : cxxtimer::Timer t1(true);
149 69 : logger.Batch("generating contributions");
150 69 : if (!blsWorker.GenerateContributions(params.threshold, memberIds, vvecContribution, skContributions)) {
151 : // this should never happen actually
152 0 : logger.Batch("GenerateContributions failed");
153 74 : return;
154 : }
155 69 : logger.Batch("generated contributions. time=%d", t1.count());
156 :
157 69 : SendContributions(pendingMessages);
158 : }
159 :
160 69 : void CDKGSession::SendContributions(CDKGPendingMessages& pendingMessages)
161 : {
162 137 : CDKGLogger logger(*this, __func__);
163 :
164 138 : assert(AreWeMember());
165 :
166 69 : logger.Batch("sending contributions");
167 :
168 138 : if (ShouldSimulateError("contribution-omit")) {
169 1 : logger.Batch("omitting");
170 2 : return;
171 : }
172 :
173 136 : CDKGContribution qc;
174 68 : qc.llmqType = (uint8_t)params.type;
175 68 : qc.quorumHash = pindexQuorum->GetBlockHash();
176 68 : qc.proTxHash = myProTxHash;
177 68 : qc.vvec = vvecContribution;
178 :
179 136 : cxxtimer::Timer t1(true);
180 136 : qc.contributions = std::make_shared<CBLSIESMultiRecipientObjects<CBLSSecretKey>>();
181 68 : qc.contributions->InitEncrypt(members.size());
182 :
183 272 : for (size_t i = 0; i < members.size(); i++) {
184 204 : auto& m = members[i];
185 408 : CBLSSecretKey skContrib = skContributions[i];
186 :
187 470 : if (i != myIdx && ShouldSimulateError("contribution-lie")) {
188 12 : logger.Batch("lying for %s", m->dmn->proTxHash.ToString());
189 6 : skContrib.MakeNewKey();
190 : }
191 :
192 204 : if (!qc.contributions->Encrypt(i, m->dmn->pdmnState->pubKeyOperator.Get(), skContrib, PROTOCOL_VERSION)) {
193 0 : logger.Batch("failed to encrypt contribution for %s", m->dmn->proTxHash.ToString());
194 0 : return;
195 : }
196 : }
197 :
198 68 : logger.Batch("encrypted contributions. time=%d", t1.count());
199 :
200 68 : qc.sig = activeMasternodeManager->OperatorKey()->Sign(qc.GetSignHash());
201 :
202 68 : logger.Flush();
203 :
204 68 : quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) {
205 68 : status.sentContributions = true;
206 68 : return true;
207 : });
208 :
209 68 : pendingMessages.PushPendingMessage(-1, qc, MSG_QUORUM_CONTRIB);
210 : }
211 :
212 : // only performs cheap verifications, but not the signature of the message. this is checked with batched verification
213 186 : bool CDKGSession::PreVerifyMessage(const CDKGContribution& qc, bool& retBan) const
214 : {
215 372 : CDKGLogger logger(*this, __func__);
216 :
217 186 : retBan = false;
218 :
219 186 : if (qc.quorumHash != pindexQuorum->GetBlockHash()) {
220 0 : logger.Batch("contribution for wrong quorum, rejecting");
221 0 : return false;
222 : }
223 :
224 186 : auto member = GetMember(qc.proTxHash);
225 186 : if (!member) {
226 0 : logger.Batch("contributor not a member of this quorum, rejecting contribution");
227 0 : retBan = true;
228 0 : return false;
229 : }
230 :
231 186 : if (qc.contributions->blobs.size() != members.size()) {
232 0 : logger.Batch("invalid contributions count");
233 0 : retBan = true;
234 0 : return false;
235 : }
236 186 : if (qc.vvec->size() != (size_t)params.threshold) {
237 0 : logger.Batch("invalid verification vector length");
238 0 : retBan = true;
239 0 : return false;
240 : }
241 :
242 186 : if (!blsWorker.VerifyVerificationVector(*qc.vvec)) {
243 0 : logger.Batch("invalid verification vector");
244 0 : retBan = true;
245 0 : return false;
246 : }
247 :
248 186 : if (member->contributions.size() >= 2) {
249 : // don't do any further processing if we got more than 1 valid contributions already
250 : // this is a DoS protection against members sending multiple contributions with valid signatures to us
251 : // we must bail out before any expensive BLS verification happens
252 0 : logger.Batch("dropping contribution from %s as we already got %d contributions", member->dmn->proTxHash.ToString(), member->contributions.size());
253 0 : return false;
254 : }
255 :
256 : return true;
257 : }
258 :
259 186 : void CDKGSession::ReceiveMessage(const uint256& hash, const CDKGContribution& qc, bool& retBan)
260 : {
261 370 : CDKGLogger logger(*this, __func__);
262 :
263 186 : retBan = false;
264 :
265 186 : auto member = GetMember(qc.proTxHash);
266 :
267 186 : cxxtimer::Timer t1(true);
268 372 : logger.Batch("received contribution from %s", qc.proTxHash.ToString());
269 :
270 186 : {
271 : // relay, no matter if further verification fails
272 : // This ensures the whole quorum sees the bad behavior
273 186 : LOCK(invCs);
274 :
275 186 : if (member->contributions.size() >= 2) {
276 : // only relay up to 2 contributions, that's enough to let the other members know about his bad behavior
277 0 : return;
278 : }
279 :
280 186 : contributions.emplace(hash, qc);
281 186 : member->contributions.emplace(hash);
282 :
283 186 : CInv inv(MSG_QUORUM_CONTRIB, hash);
284 186 : RelayInvToParticipants(inv);
285 :
286 186 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) {
287 186 : status.receivedContribution = true;
288 186 : return true;
289 : });
290 :
291 186 : if (member->contributions.size() > 1) {
292 : // don't do any further processing if we got more than 1 contribution. we already relayed it,
293 : // so others know about his bad behavior
294 0 : MarkBadMember(member);
295 0 : logger.Batch("%s did send multiple contributions", member->dmn->proTxHash.ToString());
296 0 : return;
297 : }
298 : }
299 :
300 186 : receivedVvecs[member->idx] = qc.vvec;
301 :
302 186 : int receivedCount = 0;
303 744 : for (const auto& m : members) {
304 558 : if (!m->contributions.empty()) {
305 358 : receivedCount++;
306 : }
307 : }
308 :
309 186 : logger.Batch("received and relayed contribution. received=%d/%d, time=%d", receivedCount, members.size(), t1.count());
310 :
311 370 : cxxtimer::Timer t2(true);
312 :
313 375 : if (!AreWeMember()) {
314 : // can't further validate
315 2 : return;
316 : }
317 :
318 186 : dkgManager.WriteVerifiedVvecContribution(params.type, pindexQuorum, qc.proTxHash, qc.vvec);
319 :
320 186 : bool complain = false;
321 370 : CBLSSecretKey skContribution;
322 186 : if (!qc.contributions->Decrypt(myIdx, *activeMasternodeManager->OperatorKey(), skContribution, PROTOCOL_VERSION)) {
323 0 : logger.Batch("contribution from %s could not be decrypted", member->dmn->proTxHash.ToString());
324 0 : complain = true;
325 422 : } else if (member->idx != myIdx && ShouldSimulateError("complain-lie")) {
326 4 : logger.Batch("lying/complaining for %s", member->dmn->proTxHash.ToString());
327 2 : complain = true;
328 : }
329 :
330 2 : if (complain) {
331 2 : member->weComplain = true;
332 2 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) {
333 2 : status.weComplain = true;
334 2 : return true;
335 : });
336 4 : return;
337 : }
338 :
339 184 : logger.Batch("decrypted our contribution share. time=%d", t2.count());
340 :
341 184 : bool verifyPending = false;
342 184 : receivedSkContributions[member->idx] = skContribution;
343 184 : pendingContributionVerifications.emplace_back(member->idx);
344 184 : if (pendingContributionVerifications.size() >= 32) {
345 0 : verifyPending = true;
346 : }
347 :
348 0 : if (verifyPending) {
349 0 : VerifyPendingContributions();
350 : }
351 : }
352 :
353 : // Verifies all pending secret key contributions in one batch
354 : // This is done by aggregating the verification vectors belonging to the secret key contributions
355 : // The resulting aggregated vvec is then used to recover a public key share
356 : // The public key share must match the public key belonging to the aggregated secret key contributions
357 : // See CBLSWorker::VerifyContributionShares for more details.
358 62 : void CDKGSession::VerifyPendingContributions()
359 : {
360 123 : CDKGLogger logger(*this, __func__);
361 :
362 123 : cxxtimer::Timer t1(true);
363 :
364 123 : std::vector<size_t> pend = std::move(pendingContributionVerifications);
365 62 : if (pend.empty()) {
366 1 : return;
367 : }
368 :
369 122 : std::vector<size_t> memberIndexes;
370 61 : std::vector<BLSVerificationVectorPtr> vvecs;
371 0 : BLSSecretKeyVector skContributions;
372 :
373 228 : for (const auto& idx : pend) {
374 167 : auto& m = members[idx];
375 167 : if (m->bad || m->weComplain) {
376 0 : continue;
377 : }
378 167 : memberIndexes.emplace_back(idx);
379 167 : vvecs.emplace_back(receivedVvecs[idx]);
380 167 : skContributions.emplace_back(receivedSkContributions[idx]);
381 : }
382 :
383 122 : auto result = blsWorker.VerifyContributionShares(myId, vvecs, skContributions);
384 61 : if (result.size() != memberIndexes.size()) {
385 0 : logger.Batch("VerifyContributionShares returned result of size %d but size %d was expected, something is wrong", result.size(), memberIndexes.size());
386 0 : return;
387 : }
388 :
389 228 : for (size_t i = 0; i < memberIndexes.size(); i++) {
390 167 : if (!result[i]) {
391 6 : auto& m = members[memberIndexes[i]];
392 12 : logger.Batch("invalid contribution from %s. will complain later", m->dmn->proTxHash.ToString());
393 6 : m->weComplain = true;
394 12 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, m->idx, [&](CDKGDebugMemberStatus& status) {
395 6 : status.weComplain = true;
396 6 : return true;
397 : });
398 : } else {
399 161 : size_t memberIdx = memberIndexes[i];
400 161 : dkgManager.WriteVerifiedSkContribution(params.type, pindexQuorum, members[memberIdx]->dmn->proTxHash, skContributions[i]);
401 : }
402 : }
403 :
404 122 : logger.Batch("verified %d pending contributions. time=%d", pend.size(), t1.count());
405 : }
406 :
407 124 : void CDKGSession::VerifyAndComplain(CDKGPendingMessages& pendingMessages)
408 : {
409 248 : if (!AreWeMember()) {
410 62 : return;
411 : }
412 :
413 62 : VerifyPendingContributions();
414 :
415 124 : CDKGLogger logger(*this, __func__);
416 :
417 : // we check all members if they sent us their contributions
418 : // we consider members as bad if they missed to send anything or if they sent multiple
419 : // in both cases we won't give him a second chance as he is either down, buggy or an adversary
420 : // we assume that such a participant will be marked as bad by the whole network in most cases,
421 : // as propagation will ensure that all nodes see the same vvecs/contributions. In case nodes come to
422 : // different conclusions, the aggregation phase will handle this (most voted quorum key wins)
423 :
424 124 : cxxtimer::Timer t1(true);
425 :
426 248 : for (const auto& m : members) {
427 186 : if (m->bad) {
428 0 : continue;
429 : }
430 186 : if (m->contributions.empty()) {
431 34 : logger.Batch("%s did not send any contribution", m->dmn->proTxHash.ToString());
432 17 : MarkBadMember(m.get());
433 17 : continue;
434 : }
435 : }
436 :
437 62 : logger.Batch("verified contributions. time=%d", t1.count());
438 62 : logger.Flush();
439 :
440 62 : VerifyConnectionAndMinProtoVersions();
441 :
442 62 : SendComplaint(pendingMessages);
443 : }
444 :
445 62 : void CDKGSession::VerifyConnectionAndMinProtoVersions()
446 : {
447 62 : CDKGLogger logger(*this, __func__);
448 :
449 124 : std::unordered_map<uint256, int, StaticSaltedHasher> protoMap;
450 0 : g_connman->ForEachNode([&](const CNode* pnode) {
451 1178 : if (pnode->verifiedProRegTxHash.IsNull()) {
452 : return;
453 : }
454 195 : protoMap.emplace(pnode->verifiedProRegTxHash, pnode->nVersion);
455 : });
456 :
457 248 : for (auto& m : members) {
458 186 : if (m->dmn->proTxHash == myProTxHash) {
459 62 : continue;
460 : }
461 :
462 124 : auto it = protoMap.find(m->dmn->proTxHash);
463 124 : if (it == protoMap.end()) {
464 7 : m->bad = true;
465 21 : logger.Batch("%s is not connected to us", m->dmn->proTxHash.ToString());
466 117 : } else if (it != protoMap.end() && it->second < MNAUTH_NODE_VER_VERSION) {
467 0 : m->bad = true;
468 0 : logger.Batch("%s does not have min proto version %d (has %d)", m->dmn->proTxHash.ToString(), MNAUTH_NODE_VER_VERSION, it->second);
469 : }
470 :
471 124 : auto lastOutbound = g_mmetaman.GetMetaInfo(m->dmn->proTxHash)->GetLastOutboundSuccess();
472 124 : if (GetAdjustedTime() - lastOutbound > 60 * 60) {
473 5 : m->bad = true;
474 15 : logger.Batch("%s no outbound connection since %d seconds", m->dmn->proTxHash.ToString(), GetAdjustedTime() - lastOutbound);
475 : }
476 : }
477 62 : }
478 :
479 62 : void CDKGSession::SendComplaint(CDKGPendingMessages& pendingMessages)
480 : {
481 82 : CDKGLogger logger(*this, __func__);
482 :
483 124 : assert(AreWeMember());
484 :
485 82 : CDKGComplaint qc(params);
486 62 : qc.llmqType = (uint8_t)params.type;
487 62 : qc.quorumHash = pindexQuorum->GetBlockHash();
488 62 : qc.proTxHash = myProTxHash;
489 :
490 62 : int badCount = 0;
491 62 : int complaintCount = 0;
492 248 : for (size_t i = 0; i < members.size(); i++) {
493 186 : auto& m = members[i];
494 186 : if (m->bad) {
495 17 : qc.badMembers[i] = true;
496 17 : badCount++;
497 169 : } else if (m->weComplain) {
498 8 : qc.complainForMembers[i] = true;
499 8 : complaintCount++;
500 : }
501 : }
502 :
503 62 : if (badCount == 0 && complaintCount == 0) {
504 84 : return;
505 : }
506 :
507 20 : logger.Batch("sending complaint. badCount=%d, complaintCount=%d", badCount, complaintCount);
508 :
509 20 : qc.sig = activeMasternodeManager->OperatorKey()->Sign(qc.GetSignHash());
510 :
511 20 : logger.Flush();
512 :
513 20 : quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) {
514 20 : status.sentComplaint = true;
515 20 : return true;
516 : });
517 :
518 20 : pendingMessages.PushPendingMessage(-1, qc, MSG_QUORUM_COMPLAINT);
519 : }
520 :
521 : // only performs cheap verifications, but not the signature of the message. this is checked with batched verification
522 46 : bool CDKGSession::PreVerifyMessage(const CDKGComplaint& qc, bool& retBan) const
523 : {
524 92 : CDKGLogger logger(*this, __func__);
525 :
526 46 : retBan = false;
527 :
528 46 : if (qc.quorumHash != pindexQuorum->GetBlockHash()) {
529 0 : logger.Batch("complaint for wrong quorum, rejecting");
530 0 : return false;
531 : }
532 :
533 46 : auto member = GetMember(qc.proTxHash);
534 46 : if (!member) {
535 0 : logger.Batch("complainer not a member of this quorum, rejecting complaint");
536 0 : retBan = true;
537 0 : return false;
538 : }
539 :
540 46 : if (qc.badMembers.size() != (size_t)params.size) {
541 0 : logger.Batch("invalid badMembers bitset size");
542 0 : retBan = true;
543 0 : return false;
544 : }
545 :
546 46 : if (qc.complainForMembers.size() != (size_t)params.size) {
547 0 : logger.Batch("invalid complainForMembers bitset size");
548 0 : retBan = true;
549 0 : return false;
550 : }
551 :
552 46 : if (member->complaints.size() >= 2) {
553 : // don't do any further processing if we got more than 1 valid complaints already
554 : // this is a DoS protection against members sending multiple complaints with valid signatures to us
555 : // we must bail out before any expensive BLS verification happens
556 0 : logger.Batch("dropping complaint from %s as we already got %d complaints",
557 0 : member->dmn->proTxHash.ToString(), member->complaints.size());
558 0 : return false;
559 : }
560 :
561 : return true;
562 : }
563 :
564 46 : void CDKGSession::ReceiveMessage(const uint256& hash, const CDKGComplaint& qc, bool& retBan)
565 : {
566 92 : CDKGLogger logger(*this, __func__);
567 :
568 46 : retBan = false;
569 :
570 92 : logger.Batch("received complaint from %s", qc.proTxHash.ToString());
571 :
572 46 : auto member = GetMember(qc.proTxHash);
573 :
574 46 : {
575 46 : LOCK(invCs);
576 :
577 46 : if (member->complaints.size() >= 2) {
578 : // only relay up to 2 complaints, that's enough to let the other members know about his bad behavior
579 0 : return;
580 : }
581 :
582 46 : complaints.emplace(hash, qc);
583 46 : member->complaints.emplace(hash);
584 :
585 46 : CInv inv(MSG_QUORUM_COMPLAINT, hash);
586 46 : RelayInvToParticipants(inv);
587 :
588 46 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) {
589 46 : status.receivedComplaint = true;
590 46 : return true;
591 : });
592 :
593 46 : if (member->complaints.size() > 1) {
594 : // don't do any further processing if we got more than 1 complaint. we already relayed it,
595 : // so others know about his bad behavior
596 0 : MarkBadMember(member);
597 0 : logger.Batch("%s did send multiple complaints", member->dmn->proTxHash.ToString());
598 0 : return;
599 : }
600 : }
601 :
602 46 : int receivedCount = 0;
603 184 : for (size_t i = 0; i < members.size(); i++) {
604 138 : auto& m = members[i];
605 138 : if (qc.badMembers[i]) {
606 87 : logger.Batch("%s voted for %s to be bad", member->dmn->proTxHash.ToString(), m->dmn->proTxHash.ToString());
607 29 : m->badMemberVotes.emplace(qc.proTxHash);
608 58 : if (AreWeMember() && i == myIdx) {
609 12 : logger.Batch("%s voted for us to be bad", member->dmn->proTxHash.ToString());
610 : }
611 : }
612 138 : if (qc.complainForMembers[i]) {
613 24 : m->complaintsFromOthers.emplace(qc.proTxHash);
614 24 : m->someoneComplain = true;
615 24 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, m->idx, [&](CDKGDebugMemberStatus& status) {
616 24 : return status.complaintsFromMembers.emplace(member->idx).second;
617 : });
618 48 : if (AreWeMember() && i == myIdx) {
619 24 : logger.Batch("%s complained about us", member->dmn->proTxHash.ToString());
620 : }
621 : }
622 138 : if (!m->complaints.empty()) {
623 70 : receivedCount++;
624 : }
625 : }
626 :
627 92 : logger.Batch("received and relayed complaint. received=%d", receivedCount);
628 : }
629 :
630 114 : void CDKGSession::VerifyAndJustify(CDKGPendingMessages& pendingMessages)
631 : {
632 228 : if (!AreWeMember()) {
633 56 : return;
634 : }
635 :
636 116 : CDKGLogger logger(*this, __func__);
637 :
638 116 : std::set<uint256> justifyFor;
639 :
640 232 : for (const auto& m : members) {
641 174 : if (m->bad) {
642 12 : continue;
643 : }
644 162 : if (m->badMemberVotes.size() >= (size_t)params.dkgBadVotesThreshold) {
645 0 : logger.Batch("%s marked as bad as %d other members voted for this", m->dmn->proTxHash.ToString(), m->badMemberVotes.size());
646 0 : MarkBadMember(m.get());
647 0 : continue;
648 : }
649 162 : if (m->complaints.empty()) {
650 123 : continue;
651 : }
652 39 : if (m->complaints.size() != 1) {
653 0 : logger.Batch("%s sent multiple complaints", m->dmn->proTxHash.ToString());
654 0 : MarkBadMember(m.get());
655 0 : continue;
656 : }
657 :
658 39 : auto& qc = complaints.at(*m->complaints.begin());
659 39 : if (qc.complainForMembers[myIdx]) {
660 182 : justifyFor.emplace(qc.proTxHash);
661 : }
662 : }
663 :
664 58 : logger.Flush();
665 58 : if (!justifyFor.empty()) {
666 5 : SendJustification(pendingMessages, justifyFor);
667 : }
668 : }
669 :
670 5 : void CDKGSession::SendJustification(CDKGPendingMessages& pendingMessages, const std::set<uint256>& forMembers)
671 : {
672 9 : CDKGLogger logger(*this, __func__);
673 :
674 10 : assert(AreWeMember());
675 :
676 5 : logger.Batch("sending justification for %d members", forMembers.size());
677 :
678 9 : CDKGJustification qj;
679 5 : qj.llmqType = (uint8_t)params.type;
680 5 : qj.quorumHash = pindexQuorum->GetBlockHash();
681 5 : qj.proTxHash = myProTxHash;
682 5 : qj.contributions.reserve(forMembers.size());
683 :
684 20 : for (size_t i = 0; i < members.size(); i++) {
685 15 : auto& m = members[i];
686 15 : if (!forMembers.count(m->dmn->proTxHash)) {
687 7 : continue;
688 : }
689 16 : logger.Batch("justifying for %s", m->dmn->proTxHash.ToString());
690 :
691 16 : CBLSSecretKey skContribution = skContributions[i];
692 :
693 22 : if (i != myIdx && ShouldSimulateError("justify-lie")) {
694 4 : logger.Batch("lying for %s", m->dmn->proTxHash.ToString());
695 2 : skContribution.MakeNewKey();
696 : }
697 :
698 8 : qj.contributions.emplace_back(i, skContribution);
699 : }
700 :
701 5 : if (ShouldSimulateError("justify-omit")) {
702 1 : logger.Batch("omitting");
703 1 : return;
704 : }
705 :
706 4 : qj.sig = activeMasternodeManager->OperatorKey()->Sign(qj.GetSignHash());
707 :
708 4 : logger.Flush();
709 :
710 4 : quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) {
711 4 : status.sentJustification = true;
712 4 : return true;
713 : });
714 :
715 4 : pendingMessages.PushPendingMessage(-1, qj, MSG_QUORUM_JUSTIFICATION);
716 : }
717 :
718 : // only performs cheap verifications, but not the signature of the message. this is checked with batched verification
719 12 : bool CDKGSession::PreVerifyMessage(const CDKGJustification& qj, bool& retBan) const
720 : {
721 24 : CDKGLogger logger(*this, __func__);
722 :
723 12 : retBan = false;
724 :
725 12 : if (qj.quorumHash != pindexQuorum->GetBlockHash()) {
726 0 : logger.Batch("justification for wrong quorum, rejecting");
727 0 : return false;
728 : }
729 :
730 12 : auto member = GetMember(qj.proTxHash);
731 12 : if (!member) {
732 0 : logger.Batch("justifier not a member of this quorum, rejecting justification");
733 0 : retBan = true;
734 0 : return false;
735 : }
736 :
737 12 : if (qj.contributions.empty()) {
738 0 : logger.Batch("justification with no contributions");
739 0 : retBan = true;
740 0 : return false;
741 : }
742 :
743 24 : std::set<size_t> contributionsSet;
744 30 : for (const auto& p : qj.contributions) {
745 18 : if (p.first > members.size()) {
746 0 : logger.Batch("invalid contribution index");
747 0 : retBan = true;
748 0 : return false;
749 : }
750 :
751 18 : if (!contributionsSet.emplace(p.first).second) {
752 0 : logger.Batch("duplicate contribution index");
753 0 : retBan = true;
754 0 : return false;
755 : }
756 :
757 18 : auto& skShare = p.second;
758 18 : if (!skShare.IsValid()) {
759 0 : logger.Batch("invalid contribution");
760 0 : retBan = true;
761 0 : return false;
762 : }
763 : }
764 :
765 12 : if (member->justifications.size() >= 2) {
766 : // don't do any further processing if we got more than 1 valid justification already
767 : // this is a DoS protection against members sending multiple justifications with valid signatures to us
768 : // we must bail out before any expensive BLS verification happens
769 0 : logger.Batch("dropping justification from %s as we already got %d justifications",
770 0 : member->dmn->proTxHash.ToString(), member->justifications.size());
771 0 : return false;
772 : }
773 :
774 : return true;
775 : }
776 :
777 12 : void CDKGSession::ReceiveMessage(const uint256& hash, const CDKGJustification& qj, bool& retBan)
778 : {
779 24 : CDKGLogger logger(*this, __func__);
780 :
781 12 : retBan = false;
782 :
783 24 : logger.Batch("received justification from %s", qj.proTxHash.ToString());
784 :
785 12 : auto member = GetMember(qj.proTxHash);
786 :
787 12 : {
788 12 : LOCK(invCs);
789 :
790 12 : if (member->justifications.size() >= 2) {
791 : // only relay up to 2 justifications, that's enough to let the other members know about his bad behavior
792 0 : return;
793 : }
794 :
795 12 : justifications.emplace(hash, qj);
796 12 : member->justifications.emplace(hash);
797 :
798 : // we always relay, even if further verification fails
799 12 : CInv inv(MSG_QUORUM_JUSTIFICATION, hash);
800 12 : RelayInvToParticipants(inv);
801 :
802 12 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) {
803 12 : status.receivedJustification = true;
804 12 : return true;
805 : });
806 :
807 12 : if (member->justifications.size() > 1) {
808 : // don't do any further processing if we got more than 1 justification. we already relayed it,
809 : // so others know about his bad behavior
810 0 : logger.Batch("%s did send multiple justifications", member->dmn->proTxHash.ToString());
811 0 : MarkBadMember(member);
812 : return;
813 : }
814 :
815 12 : if (member->bad) {
816 : // we locally determined him to be bad (sent none or more then one contributions)
817 : // don't give him a second chance (but we relay the justification in case other members disagree)
818 : return;
819 : }
820 : }
821 :
822 30 : for (const auto& p : qj.contributions) {
823 18 : auto& member2 = members[p.first];
824 :
825 36 : if (!member->complaintsFromOthers.count(member2->dmn->proTxHash)) {
826 0 : logger.Batch("got justification from %s for %s even though he didn't complain",
827 0 : member->dmn->proTxHash.ToString(), member2->dmn->proTxHash.ToString());
828 0 : MarkBadMember(member);
829 : }
830 : }
831 12 : if (member->bad) {
832 : return;
833 : }
834 :
835 24 : cxxtimer::Timer t1(true);
836 :
837 24 : std::list<std::future<bool>> futures;
838 30 : for (const auto& p : qj.contributions) {
839 18 : auto& member2 = members[p.first];
840 18 : auto& skContribution = p.second;
841 :
842 : // watch out to not bail out before these async calls finish (they rely on valid references)
843 36 : futures.emplace_back(blsWorker.AsyncVerifyContributionShare(member2->id, receivedVvecs[member->idx], skContribution));
844 : }
845 12 : auto resultIt = futures.begin();
846 30 : for (const auto& p : qj.contributions) {
847 18 : auto& member2 = members[p.first];
848 18 : auto& skContribution = p.second;
849 :
850 18 : bool result = (resultIt++)->get();
851 18 : if (!result) {
852 18 : logger.Batch(" %s did send an invalid justification for %s", member->dmn->proTxHash.ToString(), member2->dmn->proTxHash.ToString());
853 6 : MarkBadMember(member);
854 : } else {
855 36 : logger.Batch(" %s justified for %s", member->dmn->proTxHash.ToString(), member2->dmn->proTxHash.ToString());
856 24 : if (AreWeMember() && member2->id == myId) {
857 4 : receivedSkContributions[member->idx] = skContribution;
858 4 : member->weComplain = false;
859 :
860 4 : dkgManager.WriteVerifiedSkContribution(params.type, pindexQuorum, member->dmn->proTxHash, skContribution);
861 : }
862 18 : member->complaintsFromOthers.erase(member2->dmn->proTxHash);
863 : }
864 : }
865 :
866 12 : int receivedCount = 0;
867 12 : int expectedCount = 0;
868 :
869 48 : for (const auto& m : members) {
870 36 : if (!m->justifications.empty()) {
871 15 : receivedCount++;
872 : }
873 :
874 36 : if (m->someoneComplain) {
875 18 : expectedCount++;
876 : }
877 : }
878 :
879 24 : logger.Batch("verified justification: received=%d/%d time=%d", receivedCount, expectedCount, t1.count());
880 : }
881 :
882 113 : void CDKGSession::VerifyAndCommit(CDKGPendingMessages& pendingMessages)
883 : {
884 226 : if (!AreWeMember()) {
885 56 : return;
886 : }
887 :
888 114 : CDKGLogger logger(*this, __func__);
889 :
890 114 : std::vector<size_t> badMembers;
891 57 : std::vector<size_t> openComplaintMembers;
892 :
893 228 : for (const auto& m : members) {
894 171 : if (m->bad) {
895 12 : badMembers.emplace_back(m->idx);
896 12 : continue;
897 : }
898 159 : if (!m->complaintsFromOthers.empty()) {
899 3 : MarkBadMember(m.get());
900 3 : openComplaintMembers.emplace_back(m->idx);
901 : }
902 : }
903 :
904 57 : if (!badMembers.empty() || !openComplaintMembers.empty()) {
905 30 : logger.Batch("verification result:");
906 : }
907 57 : if (!badMembers.empty()) {
908 12 : logger.Batch(" members previously determined as bad:");
909 24 : for (const auto& idx : badMembers) {
910 36 : logger.Batch(" %s", members[idx]->dmn->proTxHash.ToString());
911 : }
912 : }
913 57 : if (!openComplaintMembers.empty()) {
914 3 : logger.Batch(" members with open complaints and now marked as bad:");
915 6 : for (const auto& idx : openComplaintMembers) {
916 9 : logger.Batch(" %s", members[idx]->dmn->proTxHash.ToString());
917 : }
918 : }
919 :
920 57 : logger.Flush();
921 :
922 57 : SendCommitment(pendingMessages);
923 : }
924 :
925 57 : void CDKGSession::SendCommitment(CDKGPendingMessages& pendingMessages)
926 : {
927 113 : CDKGLogger logger(*this, __func__);
928 :
929 114 : assert(AreWeMember());
930 :
931 57 : logger.Batch("sending commitment");
932 :
933 113 : CDKGPrematureCommitment qc(params);
934 57 : qc.llmqType = (uint8_t)params.type;
935 57 : qc.quorumHash = pindexQuorum->GetBlockHash();
936 57 : qc.proTxHash = myProTxHash;
937 :
938 228 : for (size_t i = 0; i < members.size(); i++) {
939 171 : auto& m = members[i];
940 171 : if (!m->bad) {
941 156 : qc.validMembers[i] = true;
942 : }
943 : }
944 :
945 57 : if (qc.CountValidMembers() < params.minSize) {
946 0 : logger.Batch("not enough valid members. not sending commitment");
947 1 : return;
948 : }
949 :
950 57 : if (ShouldSimulateError("commit-omit")) {
951 1 : logger.Batch("omitting");
952 1 : return;
953 : }
954 :
955 56 : cxxtimer::Timer timerTotal(true);
956 :
957 112 : cxxtimer::Timer t1(true);
958 112 : std::vector<uint16_t> memberIndexes;
959 56 : std::vector<BLSVerificationVectorPtr> vvecs;
960 112 : BLSSecretKeyVector skContributions;
961 56 : if (!dkgManager.GetVerifiedContributions(params.type, pindexQuorum, qc.validMembers, memberIndexes, vvecs, skContributions)) {
962 0 : logger.Batch("failed to get valid contributions");
963 0 : return;
964 : }
965 :
966 112 : BLSVerificationVectorPtr vvec = cache.BuildQuorumVerificationVector(::SerializeHash(memberIndexes), vvecs);
967 56 : if (vvec == nullptr) {
968 0 : logger.Batch("failed to build quorum verification vector");
969 0 : return;
970 : }
971 56 : t1.stop();
972 :
973 112 : cxxtimer::Timer t2(true);
974 112 : CBLSSecretKey skShare = cache.AggregateSecretKeys(::SerializeHash(memberIndexes), skContributions);
975 56 : if (!skShare.IsValid()) {
976 0 : logger.Batch("failed to build own secret share");
977 0 : return;
978 : }
979 56 : t2.stop();
980 :
981 112 : logger.Batch("pubKeyShare=%s", bls::EncodePublic(Params(), skShare.GetPublicKey()));
982 :
983 112 : cxxtimer::Timer t3(true);
984 56 : qc.quorumPublicKey = (*vvec)[0];
985 56 : qc.quorumVvecHash = ::SerializeHash(*vvec);
986 :
987 56 : int lieType = -1;
988 56 : if (ShouldSimulateError("commit-lie")) {
989 1 : lieType = GetRandInt(5);
990 2 : logger.Batch("lying on commitment. lieType=%d", lieType);
991 : }
992 :
993 56 : if (lieType == 0) {
994 0 : CBLSSecretKey k;
995 0 : k.MakeNewKey();
996 0 : qc.quorumPublicKey = k.GetPublicKey();
997 56 : } else if (lieType == 1) {
998 0 : (*qc.quorumVvecHash.begin())++;
999 : }
1000 :
1001 56 : uint256 commitmentHash = utils::BuildCommitmentHash((Consensus::LLMQType)qc.llmqType, qc.quorumHash, qc.validMembers, qc.quorumPublicKey, qc.quorumVvecHash);
1002 :
1003 56 : if (lieType == 2) {
1004 0 : (*commitmentHash.begin())++;
1005 : }
1006 :
1007 56 : qc.sig = activeMasternodeManager->OperatorKey()->Sign(commitmentHash);
1008 56 : qc.quorumSig = skShare.Sign(commitmentHash);
1009 :
1010 56 : if (lieType == 3) {
1011 0 : std::vector<uint8_t> buf = qc.sig.ToByteVector();
1012 0 : buf[5]++;
1013 0 : qc.sig.SetByteVector(buf);
1014 56 : } else if (lieType == 4) {
1015 2 : std::vector<uint8_t> buf = qc.quorumSig.ToByteVector();
1016 1 : buf[5]++;
1017 1 : qc.quorumSig.SetByteVector(buf);
1018 : }
1019 :
1020 56 : t3.stop();
1021 56 : timerTotal.stop();
1022 :
1023 56 : logger.Batch("built premature commitment. time1=%d, time2=%d, time3=%d, totalTime=%d",
1024 56 : t1.count(), t2.count(), t3.count(), timerTotal.count());
1025 :
1026 56 : logger.Flush();
1027 :
1028 56 : quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) {
1029 56 : status.sentPrematureCommitment = true;
1030 56 : return true;
1031 : });
1032 :
1033 56 : pendingMessages.PushPendingMessage(-1, qc, MSG_QUORUM_PREMATURE_COMMITMENT);
1034 : }
1035 :
1036 : // only performs cheap verifications, but not the signature of the message. this is checked with batched verification
1037 160 : bool CDKGSession::PreVerifyMessage(const CDKGPrematureCommitment& qc, bool& retBan) const
1038 : {
1039 320 : CDKGLogger logger(*this, __func__);
1040 :
1041 160 : retBan = false;
1042 :
1043 160 : if (qc.quorumHash != pindexQuorum->GetBlockHash()) {
1044 0 : logger.Batch("commitment for wrong quorum, rejecting");
1045 0 : return false;
1046 : }
1047 :
1048 160 : auto member = GetMember(qc.proTxHash);
1049 160 : if (!member) {
1050 0 : logger.Batch("committer not a member of this quorum, rejecting premature commitment");
1051 0 : retBan = true;
1052 0 : return false;
1053 : }
1054 :
1055 160 : if (qc.validMembers.size() != (size_t)params.size) {
1056 0 : logger.Batch("invalid validMembers bitset size");
1057 0 : retBan = true;
1058 0 : return false;
1059 : }
1060 :
1061 160 : if (qc.CountValidMembers() < params.minSize) {
1062 0 : logger.Batch("invalid validMembers count. validMembersCount=%d", qc.CountValidMembers());
1063 0 : retBan = true;
1064 0 : return false;
1065 : }
1066 160 : if (!qc.sig.IsValid()) {
1067 0 : logger.Batch("invalid membersSig");
1068 0 : retBan = true;
1069 0 : return false;
1070 : }
1071 160 : if (!qc.quorumSig.IsValid()) {
1072 1 : logger.Batch("invalid quorumSig");
1073 1 : retBan = true;
1074 1 : return false;
1075 : }
1076 :
1077 159 : for (size_t i = members.size(); i < (size_t)params.size; i++) {
1078 0 : if (qc.validMembers[i]) {
1079 0 : retBan = true;
1080 0 : logger.Batch("invalid validMembers bitset. bit %d should not be set", i);
1081 0 : return false;
1082 : }
1083 : }
1084 :
1085 159 : if (member->prematureCommitments.size() >= 2) {
1086 : // don't do any further processing if we got more than 1 valid commitment already
1087 : // this is a DoS protection against members sending multiple commitments with valid signatures to us
1088 : // we must bail out before any expensive BLS verification happens
1089 0 : logger.Batch("dropping commitment from %s as we already got %d commitments",
1090 0 : member->dmn->proTxHash.ToString(), member->prematureCommitments.size());
1091 0 : return false;
1092 : }
1093 :
1094 : return true;
1095 : }
1096 :
1097 159 : void CDKGSession::ReceiveMessage(const uint256& hash, const CDKGPrematureCommitment& qc, bool& retBan)
1098 : {
1099 318 : CDKGLogger logger(*this, __func__);
1100 :
1101 159 : retBan = false;
1102 :
1103 318 : cxxtimer::Timer t1(true);
1104 :
1105 318 : logger.Batch("received premature commitment from %s. validMembers=%d", qc.proTxHash.ToString(), qc.CountValidMembers());
1106 :
1107 159 : auto member = GetMember(qc.proTxHash);
1108 :
1109 159 : {
1110 159 : LOCK(invCs);
1111 :
1112 : // keep track of ALL commitments but only relay valid ones (or if we couldn't build the vvec)
1113 : // relaying is done further down
1114 159 : prematureCommitments.emplace(hash, qc);
1115 159 : member->prematureCommitments.emplace(hash);
1116 : }
1117 :
1118 318 : std::vector<uint16_t> memberIndexes;
1119 159 : std::vector<BLSVerificationVectorPtr> vvecs;
1120 0 : BLSSecretKeyVector skContributions;
1121 159 : BLSVerificationVectorPtr quorumVvec;
1122 159 : if (dkgManager.GetVerifiedContributions(params.type, pindexQuorum, qc.validMembers, memberIndexes, vvecs, skContributions)) {
1123 318 : quorumVvec = cache.BuildQuorumVerificationVector(::SerializeHash(memberIndexes), vvecs);
1124 : }
1125 :
1126 159 : if (quorumVvec == nullptr) {
1127 0 : logger.Batch("failed to build quorum verification vector. skipping full verification");
1128 : // we might be the unlucky one who didn't receive all contributions, but we still have to relay
1129 : // the premature commitment as others might be luckier
1130 : } else {
1131 : // we got all information that is needed to verify everything (even though we might not be a member of the quorum)
1132 : // if any of this verification fails, we won't relay this message. This ensures that invalid messages are lost
1133 : // in the network. Nodes relaying such invalid messages to us are not punished as they might have not known
1134 : // all contributions. We only handle up to 2 commitments per member, so a DoS shouldn't be possible
1135 :
1136 159 : if ((*quorumVvec)[0] != qc.quorumPublicKey) {
1137 0 : logger.Batch("calculated quorum public key does not match");
1138 0 : return;
1139 : }
1140 159 : uint256 vvecHash = ::SerializeHash(*quorumVvec);
1141 159 : if (qc.quorumVvecHash != vvecHash) {
1142 0 : logger.Batch("calculated quorum vvec hash does not match");
1143 0 : return;
1144 : }
1145 :
1146 318 : CBLSPublicKey pubKeyShare = cache.BuildPubKeyShare(::SerializeHash(std::make_pair(memberIndexes, member->id)), quorumVvec, member->id);
1147 159 : if (!pubKeyShare.IsValid()) {
1148 0 : logger.Batch("failed to calculate public key share");
1149 0 : return;
1150 : }
1151 :
1152 159 : if (!qc.quorumSig.VerifyInsecure(pubKeyShare, qc.GetSignHash())) {
1153 0 : logger.Batch("failed to verify quorumSig");
1154 0 : return;
1155 : }
1156 : }
1157 :
1158 318 : LOCK(invCs);
1159 159 : validCommitments.emplace(hash);
1160 :
1161 159 : CInv inv(MSG_QUORUM_PREMATURE_COMMITMENT, hash);
1162 159 : RelayInvToParticipants(inv);
1163 :
1164 159 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) {
1165 159 : status.receivedPrematureCommitment = true;
1166 159 : return true;
1167 : });
1168 :
1169 159 : int receivedCount = 0;
1170 636 : for (const auto& m : members) {
1171 477 : if (!m->prematureCommitments.empty()) {
1172 306 : receivedCount++;
1173 : }
1174 : }
1175 :
1176 159 : t1.stop();
1177 :
1178 318 : logger.Batch("verified premature commitment. received=%d/%d, time=%d", receivedCount, members.size(), t1.count());
1179 : }
1180 :
1181 113 : std::vector<CFinalCommitment> CDKGSession::FinalizeCommitments()
1182 : {
1183 226 : if (!AreWeMember()) {
1184 56 : return {};
1185 : }
1186 :
1187 170 : CDKGLogger logger(*this, __func__);
1188 :
1189 114 : cxxtimer::Timer totalTimer(true);
1190 :
1191 114 : std::vector<CBLSPublicKey> allkeys;
1192 228 : for (const auto& m : members) {
1193 171 : allkeys.emplace_back(m->dmn->pdmnState->pubKeyOperator.Get());
1194 : }
1195 :
1196 57 : typedef std::vector<bool> Key;
1197 114 : std::map<Key, std::vector<CDKGPrematureCommitment>> commitmentsMap;
1198 :
1199 216 : for (const auto& p : prematureCommitments) {
1200 159 : auto& qc = p.second;
1201 159 : if (!validCommitments.count(p.first)) {
1202 0 : continue;
1203 : }
1204 :
1205 : // should have been verified before
1206 159 : assert(qc.CountValidMembers() >= params.minSize);
1207 :
1208 159 : auto it = commitmentsMap.find(qc.validMembers);
1209 159 : if (it == commitmentsMap.end()) {
1210 57 : it = commitmentsMap.emplace(qc.validMembers, std::vector<CDKGPrematureCommitment>()).first;
1211 : }
1212 :
1213 159 : it->second.emplace_back(qc);
1214 : }
1215 :
1216 57 : const CChainParams& chainparams = Params();
1217 :
1218 114 : std::vector<CFinalCommitment> finalCommitments;
1219 114 : for (const auto& p : commitmentsMap) {
1220 57 : auto& cvec = p.second;
1221 57 : if (cvec.size() < (size_t)params.minSize) {
1222 : // commitment was signed by a minority
1223 0 : continue;
1224 : }
1225 :
1226 114 : std::vector<CBLSId> signerIds;
1227 57 : std::vector<CBLSSignature> thresholdSigs;
1228 :
1229 57 : auto& first = cvec[0];
1230 :
1231 114 : CFinalCommitment fqc(params, first.quorumHash);
1232 57 : fqc.validMembers = first.validMembers;
1233 57 : fqc.quorumPublicKey = first.quorumPublicKey;
1234 57 : fqc.quorumVvecHash = first.quorumVvecHash;
1235 :
1236 57 : uint256 commitmentHash = utils::BuildCommitmentHash((Consensus::LLMQType)fqc.llmqType, fqc.quorumHash, fqc.validMembers, fqc.quorumPublicKey, fqc.quorumVvecHash);
1237 :
1238 114 : std::vector<CBLSSignature> aggSigs;
1239 57 : std::vector<CBLSPublicKey> aggPks;
1240 57 : aggSigs.reserve(cvec.size());
1241 57 : aggPks.reserve(cvec.size());
1242 :
1243 216 : for (size_t i = 0; i < cvec.size(); i++) {
1244 159 : auto& qc = cvec[i];
1245 :
1246 318 : if (qc.quorumPublicKey != first.quorumPublicKey || qc.quorumVvecHash != first.quorumVvecHash) {
1247 0 : logger.Batch("quorumPublicKey or quorumVvecHash does not match, skipping");
1248 0 : continue;
1249 : }
1250 :
1251 159 : size_t signerIndex = membersMap[qc.proTxHash];
1252 159 : const auto& m = members[signerIndex];
1253 :
1254 159 : fqc.signers[signerIndex] = true;
1255 159 : aggSigs.emplace_back(qc.sig);
1256 159 : aggPks.emplace_back(m->dmn->pdmnState->pubKeyOperator.Get());
1257 :
1258 159 : signerIds.emplace_back(m->id);
1259 159 : thresholdSigs.emplace_back(qc.quorumSig);
1260 : }
1261 :
1262 57 : cxxtimer::Timer t1(true);
1263 57 : fqc.membersSig = CBLSSignature::AggregateSecure(aggSigs, aggPks, commitmentHash);
1264 57 : t1.stop();
1265 :
1266 57 : cxxtimer::Timer t2(true);
1267 57 : if (!fqc.quorumSig.Recover(thresholdSigs, signerIds)) {
1268 0 : logger.Batch("failed to recover quorum sig");
1269 0 : continue;
1270 : }
1271 57 : t2.stop();
1272 :
1273 114 : cxxtimer::Timer t3(true);
1274 57 : if (!fqc.Verify(allkeys, params)) {
1275 0 : logger.Batch("failed to verify final commitment");
1276 0 : continue;
1277 : }
1278 57 : t3.stop();
1279 :
1280 57 : finalCommitments.emplace_back(fqc);
1281 :
1282 57 : logger.Batch("final commitment: validMembers=%d, signers=%d, quorumPublicKey=%s, time1=%d, time2=%d, time3=%d, total=%d",
1283 114 : fqc.CountValidMembers(), fqc.CountSigners(), bls::EncodePublic(chainparams, fqc.quorumPublicKey),
1284 114 : t1.count(), t2.count(), t3.count(), totalTimer.count());
1285 : }
1286 :
1287 57 : totalTimer.stop();
1288 57 : logger.Flush();
1289 :
1290 57 : return finalCommitments;
1291 : }
1292 :
1293 1252 : CDKGMember* CDKGSession::GetMember(const uint256& proTxHash) const
1294 : {
1295 1252 : auto it = membersMap.find(proTxHash);
1296 1252 : if (it == membersMap.end()) {
1297 : return nullptr;
1298 : }
1299 1252 : return members[it->second].get();
1300 : }
1301 :
1302 26 : void CDKGSession::MarkBadMember(CDKGMember* member)
1303 : {
1304 26 : if (member->bad) {
1305 : return;
1306 : }
1307 23 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) {
1308 23 : status.bad = true;
1309 23 : return true;
1310 : });
1311 23 : member->bad = true;
1312 : }
1313 :
1314 403 : void CDKGSession::RelayInvToParticipants(const CInv& inv) const
1315 : {
1316 403 : LOCK(invCs);
1317 403 : g_connman->ForEachNode([&](CNode* pnode) {
1318 8184 : if (!pnode->verifiedProRegTxHash.IsNull() && relayMembers.count(pnode->verifiedProRegTxHash)) {
1319 767 : pnode->PushInventory(inv);
1320 : }
1321 3843 : });
1322 403 : }
1323 :
1324 : }
|