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 0 : bool SetSimulatedDKGErrorRate(const std::string& error_type, double rate)
37 : {
38 0 : LOCK(cs_simDkgError);
39 0 : auto it = simDkgErrorMap.find(error_type);
40 0 : if (it != simDkgErrorMap.end()) {
41 0 : it->second = rate;
42 0 : return true;
43 : }
44 : return false;
45 : }
46 :
47 0 : static double GetSimulatedErrorRate(const std::string& error_type)
48 : {
49 0 : LOCK(cs_simDkgError);
50 0 : auto it = simDkgErrorMap.find(error_type);
51 0 : if (it != simDkgErrorMap.end()) {
52 0 : return it->second;
53 : }
54 : return 0;
55 : }
56 :
57 0 : bool CDKGSession::ShouldSimulateError(const std::string& error_type)
58 : {
59 0 : if (params.type != Consensus::LLMQType::LLMQ_TEST) {
60 : // error simulation available only for LLMQ_TEST
61 : return false;
62 : }
63 0 : double rate = GetSimulatedErrorRate(error_type);
64 0 : return GetRandBool(rate);
65 : }
66 :
67 0 : CDKGLogger::CDKGLogger(const CDKGSession& _quorumDkg, const std::string& _func) :
68 0 : CDKGLogger(_quorumDkg.params.name, _quorumDkg.pindexQuorum->GetBlockHash(), _quorumDkg.pindexQuorum->nHeight, _quorumDkg.AreWeMember(), _func)
69 : {
70 0 : }
71 :
72 0 : CDKGLogger::CDKGLogger(const std::string& _llmqTypeName, const uint256& _quorumHash, int _height, bool _areWeMember, const std::string& _func) :
73 0 : CBatchedLogger(g_logger, BCLog::DKG, strprintf("QuorumDKG(type=%s, height=%d, member=%d, func=%s)", _llmqTypeName, _height, _areWeMember, _func))
74 : {
75 0 : }
76 :
77 :
78 0 : CDKGComplaint::CDKGComplaint(const Consensus::LLMQParams& params) :
79 0 : badMembers((size_t)params.size), complainForMembers((size_t)params.size)
80 : {
81 0 : }
82 :
83 0 : CDKGPrematureCommitment::CDKGPrematureCommitment(const Consensus::LLMQParams& params) :
84 0 : validMembers((size_t)params.size)
85 : {
86 0 : }
87 :
88 0 : CDKGMember::CDKGMember(CDeterministicMNCPtr _dmn, size_t _idx) :
89 : dmn(_dmn),
90 : idx(_idx),
91 0 : id(_dmn->proTxHash)
92 : {
93 :
94 0 : }
95 :
96 0 : bool CDKGSession::Init(const CBlockIndex* _pindexQuorum, const std::vector<CDeterministicMNCPtr>& mns, const uint256& _myProTxHash)
97 : {
98 0 : if (mns.size() < (size_t)params.minSize) {
99 : // don't use CDKGLogger until CDKGSession is fully initialized
100 0 : LogPrint(BCLog::DKG, "CDKGSession::%s: not enough members (%d < %d), aborting init\n", __func__, mns.size(), params.minSize);
101 0 : return false;
102 : }
103 :
104 0 : pindexQuorum = _pindexQuorum;
105 :
106 0 : members.resize(mns.size());
107 0 : memberIds.resize(members.size());
108 0 : receivedVvecs.resize(members.size());
109 0 : receivedSkContributions.resize(members.size());
110 :
111 0 : for (size_t i = 0; i < mns.size(); i++) {
112 0 : members[i] = std::unique_ptr<CDKGMember>(new CDKGMember(mns[i], i));
113 0 : membersMap.emplace(members[i]->dmn->proTxHash, i);
114 0 : memberIds[i] = members[i]->id;
115 : }
116 :
117 0 : if (!_myProTxHash.IsNull()) {
118 0 : for (size_t i = 0; i < members.size(); i++) {
119 0 : auto& m = members[i];
120 0 : if (m->dmn->proTxHash == _myProTxHash) {
121 0 : myIdx = i;
122 0 : myProTxHash = _myProTxHash;
123 0 : myId = m->id;
124 0 : break;
125 : }
126 : }
127 : }
128 :
129 0 : if (myProTxHash.IsNull()) {
130 0 : LogPrint(BCLog::DKG, "CDKGSession::%s: initialized as observer. mns=%d\n", __func__, mns.size());
131 : } else {
132 0 : quorumDKGDebugManager->InitLocalSessionStatus(params.type, pindexQuorum->GetBlockHash(), pindexQuorum->nHeight);
133 0 : relayMembers = GetQuorumRelayMembers(mns, myIdx);
134 0 : LogPrint(BCLog::DKG, "CDKGSession::%s: initialized as member. mns=%d\n", __func__, mns.size());
135 : }
136 :
137 : return true;
138 : }
139 :
140 0 : void CDKGSession::Contribute(CDKGPendingMessages& pendingMessages)
141 : {
142 0 : CDKGLogger logger(*this, __func__);
143 :
144 0 : if (!AreWeMember()) {
145 0 : return;
146 : }
147 :
148 0 : cxxtimer::Timer t1(true);
149 0 : logger.Batch("generating contributions");
150 0 : if (!blsWorker.GenerateContributions(params.threshold, memberIds, vvecContribution, skContributions)) {
151 : // this should never happen actually
152 0 : logger.Batch("GenerateContributions failed");
153 0 : return;
154 : }
155 0 : logger.Batch("generated contributions. time=%d", t1.count());
156 :
157 0 : SendContributions(pendingMessages);
158 : }
159 :
160 0 : void CDKGSession::SendContributions(CDKGPendingMessages& pendingMessages)
161 : {
162 0 : CDKGLogger logger(*this, __func__);
163 :
164 0 : assert(AreWeMember());
165 :
166 0 : logger.Batch("sending contributions");
167 :
168 0 : if (ShouldSimulateError("contribution-omit")) {
169 0 : logger.Batch("omitting");
170 0 : return;
171 : }
172 :
173 0 : CDKGContribution qc;
174 0 : qc.llmqType = (uint8_t)params.type;
175 0 : qc.quorumHash = pindexQuorum->GetBlockHash();
176 0 : qc.proTxHash = myProTxHash;
177 0 : qc.vvec = vvecContribution;
178 :
179 0 : cxxtimer::Timer t1(true);
180 0 : qc.contributions = std::make_shared<CBLSIESMultiRecipientObjects<CBLSSecretKey>>();
181 0 : qc.contributions->InitEncrypt(members.size());
182 :
183 0 : for (size_t i = 0; i < members.size(); i++) {
184 0 : auto& m = members[i];
185 0 : CBLSSecretKey skContrib = skContributions[i];
186 :
187 0 : if (i != myIdx && ShouldSimulateError("contribution-lie")) {
188 0 : logger.Batch("lying for %s", m->dmn->proTxHash.ToString());
189 0 : skContrib.MakeNewKey();
190 : }
191 :
192 0 : 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 0 : logger.Batch("encrypted contributions. time=%d", t1.count());
199 :
200 0 : qc.sig = activeMasternodeManager->OperatorKey()->Sign(qc.GetSignHash());
201 :
202 0 : logger.Flush();
203 :
204 0 : quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) {
205 0 : status.sentContributions = true;
206 0 : return true;
207 : });
208 :
209 0 : 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 0 : bool CDKGSession::PreVerifyMessage(const CDKGContribution& qc, bool& retBan) const
214 : {
215 0 : CDKGLogger logger(*this, __func__);
216 :
217 0 : retBan = false;
218 :
219 0 : if (qc.quorumHash != pindexQuorum->GetBlockHash()) {
220 0 : logger.Batch("contribution for wrong quorum, rejecting");
221 0 : return false;
222 : }
223 :
224 0 : auto member = GetMember(qc.proTxHash);
225 0 : 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 0 : if (qc.contributions->blobs.size() != members.size()) {
232 0 : logger.Batch("invalid contributions count");
233 0 : retBan = true;
234 0 : return false;
235 : }
236 0 : 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 0 : if (!blsWorker.VerifyVerificationVector(*qc.vvec)) {
243 0 : logger.Batch("invalid verification vector");
244 0 : retBan = true;
245 0 : return false;
246 : }
247 :
248 0 : 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 0 : void CDKGSession::ReceiveMessage(const uint256& hash, const CDKGContribution& qc, bool& retBan)
260 : {
261 0 : CDKGLogger logger(*this, __func__);
262 :
263 0 : retBan = false;
264 :
265 0 : auto member = GetMember(qc.proTxHash);
266 :
267 0 : cxxtimer::Timer t1(true);
268 0 : logger.Batch("received contribution from %s", qc.proTxHash.ToString());
269 :
270 0 : {
271 : // relay, no matter if further verification fails
272 : // This ensures the whole quorum sees the bad behavior
273 0 : LOCK(invCs);
274 :
275 0 : 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 0 : contributions.emplace(hash, qc);
281 0 : member->contributions.emplace(hash);
282 :
283 0 : CInv inv(MSG_QUORUM_CONTRIB, hash);
284 0 : RelayInvToParticipants(inv);
285 :
286 0 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) {
287 0 : status.receivedContribution = true;
288 0 : return true;
289 : });
290 :
291 0 : 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 0 : receivedVvecs[member->idx] = qc.vvec;
301 :
302 0 : int receivedCount = 0;
303 0 : for (const auto& m : members) {
304 0 : if (!m->contributions.empty()) {
305 0 : receivedCount++;
306 : }
307 : }
308 :
309 0 : logger.Batch("received and relayed contribution. received=%d/%d, time=%d", receivedCount, members.size(), t1.count());
310 :
311 0 : cxxtimer::Timer t2(true);
312 :
313 0 : if (!AreWeMember()) {
314 : // can't further validate
315 0 : return;
316 : }
317 :
318 0 : dkgManager.WriteVerifiedVvecContribution(params.type, pindexQuorum, qc.proTxHash, qc.vvec);
319 :
320 0 : bool complain = false;
321 0 : CBLSSecretKey skContribution;
322 0 : 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 0 : } else if (member->idx != myIdx && ShouldSimulateError("complain-lie")) {
326 0 : logger.Batch("lying/complaining for %s", member->dmn->proTxHash.ToString());
327 0 : complain = true;
328 : }
329 :
330 0 : if (complain) {
331 0 : member->weComplain = true;
332 0 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) {
333 0 : status.weComplain = true;
334 0 : return true;
335 : });
336 0 : return;
337 : }
338 :
339 0 : logger.Batch("decrypted our contribution share. time=%d", t2.count());
340 :
341 0 : bool verifyPending = false;
342 0 : receivedSkContributions[member->idx] = skContribution;
343 0 : pendingContributionVerifications.emplace_back(member->idx);
344 0 : 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 0 : void CDKGSession::VerifyPendingContributions()
359 : {
360 0 : CDKGLogger logger(*this, __func__);
361 :
362 0 : cxxtimer::Timer t1(true);
363 :
364 0 : std::vector<size_t> pend = std::move(pendingContributionVerifications);
365 0 : if (pend.empty()) {
366 0 : return;
367 : }
368 :
369 0 : std::vector<size_t> memberIndexes;
370 0 : std::vector<BLSVerificationVectorPtr> vvecs;
371 0 : BLSSecretKeyVector skContributions;
372 :
373 0 : for (const auto& idx : pend) {
374 0 : auto& m = members[idx];
375 0 : if (m->bad || m->weComplain) {
376 0 : continue;
377 : }
378 0 : memberIndexes.emplace_back(idx);
379 0 : vvecs.emplace_back(receivedVvecs[idx]);
380 0 : skContributions.emplace_back(receivedSkContributions[idx]);
381 : }
382 :
383 0 : auto result = blsWorker.VerifyContributionShares(myId, vvecs, skContributions);
384 0 : 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 0 : for (size_t i = 0; i < memberIndexes.size(); i++) {
390 0 : if (!result[i]) {
391 0 : auto& m = members[memberIndexes[i]];
392 0 : logger.Batch("invalid contribution from %s. will complain later", m->dmn->proTxHash.ToString());
393 0 : m->weComplain = true;
394 0 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, m->idx, [&](CDKGDebugMemberStatus& status) {
395 0 : status.weComplain = true;
396 0 : return true;
397 : });
398 : } else {
399 0 : size_t memberIdx = memberIndexes[i];
400 0 : dkgManager.WriteVerifiedSkContribution(params.type, pindexQuorum, members[memberIdx]->dmn->proTxHash, skContributions[i]);
401 : }
402 : }
403 :
404 0 : logger.Batch("verified %d pending contributions. time=%d", pend.size(), t1.count());
405 : }
406 :
407 0 : void CDKGSession::VerifyAndComplain(CDKGPendingMessages& pendingMessages)
408 : {
409 0 : if (!AreWeMember()) {
410 0 : return;
411 : }
412 :
413 0 : VerifyPendingContributions();
414 :
415 0 : 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 0 : cxxtimer::Timer t1(true);
425 :
426 0 : for (const auto& m : members) {
427 0 : if (m->bad) {
428 0 : continue;
429 : }
430 0 : if (m->contributions.empty()) {
431 0 : logger.Batch("%s did not send any contribution", m->dmn->proTxHash.ToString());
432 0 : MarkBadMember(m.get());
433 0 : continue;
434 : }
435 : }
436 :
437 0 : logger.Batch("verified contributions. time=%d", t1.count());
438 0 : logger.Flush();
439 :
440 0 : VerifyConnectionAndMinProtoVersions();
441 :
442 0 : SendComplaint(pendingMessages);
443 : }
444 :
445 0 : void CDKGSession::VerifyConnectionAndMinProtoVersions()
446 : {
447 0 : CDKGLogger logger(*this, __func__);
448 :
449 0 : std::unordered_map<uint256, int, StaticSaltedHasher> protoMap;
450 0 : g_connman->ForEachNode([&](const CNode* pnode) {
451 0 : if (pnode->verifiedProRegTxHash.IsNull()) {
452 : return;
453 : }
454 0 : protoMap.emplace(pnode->verifiedProRegTxHash, pnode->nVersion);
455 : });
456 :
457 0 : for (auto& m : members) {
458 0 : if (m->dmn->proTxHash == myProTxHash) {
459 0 : continue;
460 : }
461 :
462 0 : auto it = protoMap.find(m->dmn->proTxHash);
463 0 : if (it == protoMap.end()) {
464 0 : m->bad = true;
465 0 : logger.Batch("%s is not connected to us", m->dmn->proTxHash.ToString());
466 0 : } 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 0 : auto lastOutbound = g_mmetaman.GetMetaInfo(m->dmn->proTxHash)->GetLastOutboundSuccess();
472 0 : if (GetAdjustedTime() - lastOutbound > 60 * 60) {
473 0 : m->bad = true;
474 0 : logger.Batch("%s no outbound connection since %d seconds", m->dmn->proTxHash.ToString(), GetAdjustedTime() - lastOutbound);
475 : }
476 : }
477 0 : }
478 :
479 0 : void CDKGSession::SendComplaint(CDKGPendingMessages& pendingMessages)
480 : {
481 0 : CDKGLogger logger(*this, __func__);
482 :
483 0 : assert(AreWeMember());
484 :
485 0 : CDKGComplaint qc(params);
486 0 : qc.llmqType = (uint8_t)params.type;
487 0 : qc.quorumHash = pindexQuorum->GetBlockHash();
488 0 : qc.proTxHash = myProTxHash;
489 :
490 0 : int badCount = 0;
491 0 : int complaintCount = 0;
492 0 : for (size_t i = 0; i < members.size(); i++) {
493 0 : auto& m = members[i];
494 0 : if (m->bad) {
495 0 : qc.badMembers[i] = true;
496 0 : badCount++;
497 0 : } else if (m->weComplain) {
498 0 : qc.complainForMembers[i] = true;
499 0 : complaintCount++;
500 : }
501 : }
502 :
503 0 : if (badCount == 0 && complaintCount == 0) {
504 0 : return;
505 : }
506 :
507 0 : logger.Batch("sending complaint. badCount=%d, complaintCount=%d", badCount, complaintCount);
508 :
509 0 : qc.sig = activeMasternodeManager->OperatorKey()->Sign(qc.GetSignHash());
510 :
511 0 : logger.Flush();
512 :
513 0 : quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) {
514 0 : status.sentComplaint = true;
515 0 : return true;
516 : });
517 :
518 0 : 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 0 : bool CDKGSession::PreVerifyMessage(const CDKGComplaint& qc, bool& retBan) const
523 : {
524 0 : CDKGLogger logger(*this, __func__);
525 :
526 0 : retBan = false;
527 :
528 0 : if (qc.quorumHash != pindexQuorum->GetBlockHash()) {
529 0 : logger.Batch("complaint for wrong quorum, rejecting");
530 0 : return false;
531 : }
532 :
533 0 : auto member = GetMember(qc.proTxHash);
534 0 : 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 0 : 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 0 : 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 0 : 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 0 : void CDKGSession::ReceiveMessage(const uint256& hash, const CDKGComplaint& qc, bool& retBan)
565 : {
566 0 : CDKGLogger logger(*this, __func__);
567 :
568 0 : retBan = false;
569 :
570 0 : logger.Batch("received complaint from %s", qc.proTxHash.ToString());
571 :
572 0 : auto member = GetMember(qc.proTxHash);
573 :
574 0 : {
575 0 : LOCK(invCs);
576 :
577 0 : 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 0 : complaints.emplace(hash, qc);
583 0 : member->complaints.emplace(hash);
584 :
585 0 : CInv inv(MSG_QUORUM_COMPLAINT, hash);
586 0 : RelayInvToParticipants(inv);
587 :
588 0 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) {
589 0 : status.receivedComplaint = true;
590 0 : return true;
591 : });
592 :
593 0 : 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 0 : int receivedCount = 0;
603 0 : for (size_t i = 0; i < members.size(); i++) {
604 0 : auto& m = members[i];
605 0 : if (qc.badMembers[i]) {
606 0 : logger.Batch("%s voted for %s to be bad", member->dmn->proTxHash.ToString(), m->dmn->proTxHash.ToString());
607 0 : m->badMemberVotes.emplace(qc.proTxHash);
608 0 : if (AreWeMember() && i == myIdx) {
609 0 : logger.Batch("%s voted for us to be bad", member->dmn->proTxHash.ToString());
610 : }
611 : }
612 0 : if (qc.complainForMembers[i]) {
613 0 : m->complaintsFromOthers.emplace(qc.proTxHash);
614 0 : m->someoneComplain = true;
615 0 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, m->idx, [&](CDKGDebugMemberStatus& status) {
616 0 : return status.complaintsFromMembers.emplace(member->idx).second;
617 : });
618 0 : if (AreWeMember() && i == myIdx) {
619 0 : logger.Batch("%s complained about us", member->dmn->proTxHash.ToString());
620 : }
621 : }
622 0 : if (!m->complaints.empty()) {
623 0 : receivedCount++;
624 : }
625 : }
626 :
627 0 : logger.Batch("received and relayed complaint. received=%d", receivedCount);
628 : }
629 :
630 0 : void CDKGSession::VerifyAndJustify(CDKGPendingMessages& pendingMessages)
631 : {
632 0 : if (!AreWeMember()) {
633 0 : return;
634 : }
635 :
636 0 : CDKGLogger logger(*this, __func__);
637 :
638 0 : std::set<uint256> justifyFor;
639 :
640 0 : for (const auto& m : members) {
641 0 : if (m->bad) {
642 0 : continue;
643 : }
644 0 : 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 0 : if (m->complaints.empty()) {
650 0 : continue;
651 : }
652 0 : 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 0 : auto& qc = complaints.at(*m->complaints.begin());
659 0 : if (qc.complainForMembers[myIdx]) {
660 0 : justifyFor.emplace(qc.proTxHash);
661 : }
662 : }
663 :
664 0 : logger.Flush();
665 0 : if (!justifyFor.empty()) {
666 0 : SendJustification(pendingMessages, justifyFor);
667 : }
668 : }
669 :
670 0 : void CDKGSession::SendJustification(CDKGPendingMessages& pendingMessages, const std::set<uint256>& forMembers)
671 : {
672 0 : CDKGLogger logger(*this, __func__);
673 :
674 0 : assert(AreWeMember());
675 :
676 0 : logger.Batch("sending justification for %d members", forMembers.size());
677 :
678 0 : CDKGJustification qj;
679 0 : qj.llmqType = (uint8_t)params.type;
680 0 : qj.quorumHash = pindexQuorum->GetBlockHash();
681 0 : qj.proTxHash = myProTxHash;
682 0 : qj.contributions.reserve(forMembers.size());
683 :
684 0 : for (size_t i = 0; i < members.size(); i++) {
685 0 : auto& m = members[i];
686 0 : if (!forMembers.count(m->dmn->proTxHash)) {
687 0 : continue;
688 : }
689 0 : logger.Batch("justifying for %s", m->dmn->proTxHash.ToString());
690 :
691 0 : CBLSSecretKey skContribution = skContributions[i];
692 :
693 0 : if (i != myIdx && ShouldSimulateError("justify-lie")) {
694 0 : logger.Batch("lying for %s", m->dmn->proTxHash.ToString());
695 0 : skContribution.MakeNewKey();
696 : }
697 :
698 0 : qj.contributions.emplace_back(i, skContribution);
699 : }
700 :
701 0 : if (ShouldSimulateError("justify-omit")) {
702 0 : logger.Batch("omitting");
703 0 : return;
704 : }
705 :
706 0 : qj.sig = activeMasternodeManager->OperatorKey()->Sign(qj.GetSignHash());
707 :
708 0 : logger.Flush();
709 :
710 0 : quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) {
711 0 : status.sentJustification = true;
712 0 : return true;
713 : });
714 :
715 0 : 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 0 : bool CDKGSession::PreVerifyMessage(const CDKGJustification& qj, bool& retBan) const
720 : {
721 0 : CDKGLogger logger(*this, __func__);
722 :
723 0 : retBan = false;
724 :
725 0 : if (qj.quorumHash != pindexQuorum->GetBlockHash()) {
726 0 : logger.Batch("justification for wrong quorum, rejecting");
727 0 : return false;
728 : }
729 :
730 0 : auto member = GetMember(qj.proTxHash);
731 0 : 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 0 : if (qj.contributions.empty()) {
738 0 : logger.Batch("justification with no contributions");
739 0 : retBan = true;
740 0 : return false;
741 : }
742 :
743 0 : std::set<size_t> contributionsSet;
744 0 : for (const auto& p : qj.contributions) {
745 0 : if (p.first > members.size()) {
746 0 : logger.Batch("invalid contribution index");
747 0 : retBan = true;
748 0 : return false;
749 : }
750 :
751 0 : if (!contributionsSet.emplace(p.first).second) {
752 0 : logger.Batch("duplicate contribution index");
753 0 : retBan = true;
754 0 : return false;
755 : }
756 :
757 0 : auto& skShare = p.second;
758 0 : if (!skShare.IsValid()) {
759 0 : logger.Batch("invalid contribution");
760 0 : retBan = true;
761 0 : return false;
762 : }
763 : }
764 :
765 0 : 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 0 : void CDKGSession::ReceiveMessage(const uint256& hash, const CDKGJustification& qj, bool& retBan)
778 : {
779 0 : CDKGLogger logger(*this, __func__);
780 :
781 0 : retBan = false;
782 :
783 0 : logger.Batch("received justification from %s", qj.proTxHash.ToString());
784 :
785 0 : auto member = GetMember(qj.proTxHash);
786 :
787 0 : {
788 0 : LOCK(invCs);
789 :
790 0 : 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 0 : justifications.emplace(hash, qj);
796 0 : member->justifications.emplace(hash);
797 :
798 : // we always relay, even if further verification fails
799 0 : CInv inv(MSG_QUORUM_JUSTIFICATION, hash);
800 0 : RelayInvToParticipants(inv);
801 :
802 0 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) {
803 0 : status.receivedJustification = true;
804 0 : return true;
805 : });
806 :
807 0 : 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 0 : 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 0 : for (const auto& p : qj.contributions) {
823 0 : auto& member2 = members[p.first];
824 :
825 0 : 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 0 : if (member->bad) {
832 : return;
833 : }
834 :
835 0 : cxxtimer::Timer t1(true);
836 :
837 0 : std::list<std::future<bool>> futures;
838 0 : for (const auto& p : qj.contributions) {
839 0 : auto& member2 = members[p.first];
840 0 : auto& skContribution = p.second;
841 :
842 : // watch out to not bail out before these async calls finish (they rely on valid references)
843 0 : futures.emplace_back(blsWorker.AsyncVerifyContributionShare(member2->id, receivedVvecs[member->idx], skContribution));
844 : }
845 0 : auto resultIt = futures.begin();
846 0 : for (const auto& p : qj.contributions) {
847 0 : auto& member2 = members[p.first];
848 0 : auto& skContribution = p.second;
849 :
850 0 : bool result = (resultIt++)->get();
851 0 : if (!result) {
852 0 : logger.Batch(" %s did send an invalid justification for %s", member->dmn->proTxHash.ToString(), member2->dmn->proTxHash.ToString());
853 0 : MarkBadMember(member);
854 : } else {
855 0 : logger.Batch(" %s justified for %s", member->dmn->proTxHash.ToString(), member2->dmn->proTxHash.ToString());
856 0 : if (AreWeMember() && member2->id == myId) {
857 0 : receivedSkContributions[member->idx] = skContribution;
858 0 : member->weComplain = false;
859 :
860 0 : dkgManager.WriteVerifiedSkContribution(params.type, pindexQuorum, member->dmn->proTxHash, skContribution);
861 : }
862 0 : member->complaintsFromOthers.erase(member2->dmn->proTxHash);
863 : }
864 : }
865 :
866 0 : int receivedCount = 0;
867 0 : int expectedCount = 0;
868 :
869 0 : for (const auto& m : members) {
870 0 : if (!m->justifications.empty()) {
871 0 : receivedCount++;
872 : }
873 :
874 0 : if (m->someoneComplain) {
875 0 : expectedCount++;
876 : }
877 : }
878 :
879 0 : logger.Batch("verified justification: received=%d/%d time=%d", receivedCount, expectedCount, t1.count());
880 : }
881 :
882 0 : void CDKGSession::VerifyAndCommit(CDKGPendingMessages& pendingMessages)
883 : {
884 0 : if (!AreWeMember()) {
885 0 : return;
886 : }
887 :
888 0 : CDKGLogger logger(*this, __func__);
889 :
890 0 : std::vector<size_t> badMembers;
891 0 : std::vector<size_t> openComplaintMembers;
892 :
893 0 : for (const auto& m : members) {
894 0 : if (m->bad) {
895 0 : badMembers.emplace_back(m->idx);
896 0 : continue;
897 : }
898 0 : if (!m->complaintsFromOthers.empty()) {
899 0 : MarkBadMember(m.get());
900 0 : openComplaintMembers.emplace_back(m->idx);
901 : }
902 : }
903 :
904 0 : if (!badMembers.empty() || !openComplaintMembers.empty()) {
905 0 : logger.Batch("verification result:");
906 : }
907 0 : if (!badMembers.empty()) {
908 0 : logger.Batch(" members previously determined as bad:");
909 0 : for (const auto& idx : badMembers) {
910 0 : logger.Batch(" %s", members[idx]->dmn->proTxHash.ToString());
911 : }
912 : }
913 0 : if (!openComplaintMembers.empty()) {
914 0 : logger.Batch(" members with open complaints and now marked as bad:");
915 0 : for (const auto& idx : openComplaintMembers) {
916 0 : logger.Batch(" %s", members[idx]->dmn->proTxHash.ToString());
917 : }
918 : }
919 :
920 0 : logger.Flush();
921 :
922 0 : SendCommitment(pendingMessages);
923 : }
924 :
925 0 : void CDKGSession::SendCommitment(CDKGPendingMessages& pendingMessages)
926 : {
927 0 : CDKGLogger logger(*this, __func__);
928 :
929 0 : assert(AreWeMember());
930 :
931 0 : logger.Batch("sending commitment");
932 :
933 0 : CDKGPrematureCommitment qc(params);
934 0 : qc.llmqType = (uint8_t)params.type;
935 0 : qc.quorumHash = pindexQuorum->GetBlockHash();
936 0 : qc.proTxHash = myProTxHash;
937 :
938 0 : for (size_t i = 0; i < members.size(); i++) {
939 0 : auto& m = members[i];
940 0 : if (!m->bad) {
941 0 : qc.validMembers[i] = true;
942 : }
943 : }
944 :
945 0 : if (qc.CountValidMembers() < params.minSize) {
946 0 : logger.Batch("not enough valid members. not sending commitment");
947 0 : return;
948 : }
949 :
950 0 : if (ShouldSimulateError("commit-omit")) {
951 0 : logger.Batch("omitting");
952 0 : return;
953 : }
954 :
955 0 : cxxtimer::Timer timerTotal(true);
956 :
957 0 : cxxtimer::Timer t1(true);
958 0 : std::vector<uint16_t> memberIndexes;
959 0 : std::vector<BLSVerificationVectorPtr> vvecs;
960 0 : BLSSecretKeyVector skContributions;
961 0 : 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 0 : BLSVerificationVectorPtr vvec = cache.BuildQuorumVerificationVector(::SerializeHash(memberIndexes), vvecs);
967 0 : if (vvec == nullptr) {
968 0 : logger.Batch("failed to build quorum verification vector");
969 0 : return;
970 : }
971 0 : t1.stop();
972 :
973 0 : cxxtimer::Timer t2(true);
974 0 : CBLSSecretKey skShare = cache.AggregateSecretKeys(::SerializeHash(memberIndexes), skContributions);
975 0 : if (!skShare.IsValid()) {
976 0 : logger.Batch("failed to build own secret share");
977 0 : return;
978 : }
979 0 : t2.stop();
980 :
981 0 : logger.Batch("pubKeyShare=%s", bls::EncodePublic(Params(), skShare.GetPublicKey()));
982 :
983 0 : cxxtimer::Timer t3(true);
984 0 : qc.quorumPublicKey = (*vvec)[0];
985 0 : qc.quorumVvecHash = ::SerializeHash(*vvec);
986 :
987 0 : int lieType = -1;
988 0 : if (ShouldSimulateError("commit-lie")) {
989 0 : lieType = GetRandInt(5);
990 0 : logger.Batch("lying on commitment. lieType=%d", lieType);
991 : }
992 :
993 0 : if (lieType == 0) {
994 0 : CBLSSecretKey k;
995 0 : k.MakeNewKey();
996 0 : qc.quorumPublicKey = k.GetPublicKey();
997 0 : } else if (lieType == 1) {
998 0 : (*qc.quorumVvecHash.begin())++;
999 : }
1000 :
1001 0 : uint256 commitmentHash = utils::BuildCommitmentHash((Consensus::LLMQType)qc.llmqType, qc.quorumHash, qc.validMembers, qc.quorumPublicKey, qc.quorumVvecHash);
1002 :
1003 0 : if (lieType == 2) {
1004 0 : (*commitmentHash.begin())++;
1005 : }
1006 :
1007 0 : qc.sig = activeMasternodeManager->OperatorKey()->Sign(commitmentHash);
1008 0 : qc.quorumSig = skShare.Sign(commitmentHash);
1009 :
1010 0 : if (lieType == 3) {
1011 0 : std::vector<uint8_t> buf = qc.sig.ToByteVector();
1012 0 : buf[5]++;
1013 0 : qc.sig.SetByteVector(buf);
1014 0 : } else if (lieType == 4) {
1015 0 : std::vector<uint8_t> buf = qc.quorumSig.ToByteVector();
1016 0 : buf[5]++;
1017 0 : qc.quorumSig.SetByteVector(buf);
1018 : }
1019 :
1020 0 : t3.stop();
1021 0 : timerTotal.stop();
1022 :
1023 0 : logger.Batch("built premature commitment. time1=%d, time2=%d, time3=%d, totalTime=%d",
1024 0 : t1.count(), t2.count(), t3.count(), timerTotal.count());
1025 :
1026 0 : logger.Flush();
1027 :
1028 0 : quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) {
1029 0 : status.sentPrematureCommitment = true;
1030 0 : return true;
1031 : });
1032 :
1033 0 : 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 0 : bool CDKGSession::PreVerifyMessage(const CDKGPrematureCommitment& qc, bool& retBan) const
1038 : {
1039 0 : CDKGLogger logger(*this, __func__);
1040 :
1041 0 : retBan = false;
1042 :
1043 0 : if (qc.quorumHash != pindexQuorum->GetBlockHash()) {
1044 0 : logger.Batch("commitment for wrong quorum, rejecting");
1045 0 : return false;
1046 : }
1047 :
1048 0 : auto member = GetMember(qc.proTxHash);
1049 0 : 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 0 : 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 0 : 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 0 : if (!qc.sig.IsValid()) {
1067 0 : logger.Batch("invalid membersSig");
1068 0 : retBan = true;
1069 0 : return false;
1070 : }
1071 0 : if (!qc.quorumSig.IsValid()) {
1072 0 : logger.Batch("invalid quorumSig");
1073 0 : retBan = true;
1074 0 : return false;
1075 : }
1076 :
1077 0 : 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 0 : 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 0 : void CDKGSession::ReceiveMessage(const uint256& hash, const CDKGPrematureCommitment& qc, bool& retBan)
1098 : {
1099 0 : CDKGLogger logger(*this, __func__);
1100 :
1101 0 : retBan = false;
1102 :
1103 0 : cxxtimer::Timer t1(true);
1104 :
1105 0 : logger.Batch("received premature commitment from %s. validMembers=%d", qc.proTxHash.ToString(), qc.CountValidMembers());
1106 :
1107 0 : auto member = GetMember(qc.proTxHash);
1108 :
1109 0 : {
1110 0 : 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 0 : prematureCommitments.emplace(hash, qc);
1115 0 : member->prematureCommitments.emplace(hash);
1116 : }
1117 :
1118 0 : std::vector<uint16_t> memberIndexes;
1119 0 : std::vector<BLSVerificationVectorPtr> vvecs;
1120 0 : BLSSecretKeyVector skContributions;
1121 0 : BLSVerificationVectorPtr quorumVvec;
1122 0 : if (dkgManager.GetVerifiedContributions(params.type, pindexQuorum, qc.validMembers, memberIndexes, vvecs, skContributions)) {
1123 0 : quorumVvec = cache.BuildQuorumVerificationVector(::SerializeHash(memberIndexes), vvecs);
1124 : }
1125 :
1126 0 : 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 0 : if ((*quorumVvec)[0] != qc.quorumPublicKey) {
1137 0 : logger.Batch("calculated quorum public key does not match");
1138 0 : return;
1139 : }
1140 0 : uint256 vvecHash = ::SerializeHash(*quorumVvec);
1141 0 : if (qc.quorumVvecHash != vvecHash) {
1142 0 : logger.Batch("calculated quorum vvec hash does not match");
1143 0 : return;
1144 : }
1145 :
1146 0 : CBLSPublicKey pubKeyShare = cache.BuildPubKeyShare(::SerializeHash(std::make_pair(memberIndexes, member->id)), quorumVvec, member->id);
1147 0 : if (!pubKeyShare.IsValid()) {
1148 0 : logger.Batch("failed to calculate public key share");
1149 0 : return;
1150 : }
1151 :
1152 0 : if (!qc.quorumSig.VerifyInsecure(pubKeyShare, qc.GetSignHash())) {
1153 0 : logger.Batch("failed to verify quorumSig");
1154 0 : return;
1155 : }
1156 : }
1157 :
1158 0 : LOCK(invCs);
1159 0 : validCommitments.emplace(hash);
1160 :
1161 0 : CInv inv(MSG_QUORUM_PREMATURE_COMMITMENT, hash);
1162 0 : RelayInvToParticipants(inv);
1163 :
1164 0 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) {
1165 0 : status.receivedPrematureCommitment = true;
1166 0 : return true;
1167 : });
1168 :
1169 0 : int receivedCount = 0;
1170 0 : for (const auto& m : members) {
1171 0 : if (!m->prematureCommitments.empty()) {
1172 0 : receivedCount++;
1173 : }
1174 : }
1175 :
1176 0 : t1.stop();
1177 :
1178 0 : logger.Batch("verified premature commitment. received=%d/%d, time=%d", receivedCount, members.size(), t1.count());
1179 : }
1180 :
1181 0 : std::vector<CFinalCommitment> CDKGSession::FinalizeCommitments()
1182 : {
1183 0 : if (!AreWeMember()) {
1184 0 : return {};
1185 : }
1186 :
1187 0 : CDKGLogger logger(*this, __func__);
1188 :
1189 0 : cxxtimer::Timer totalTimer(true);
1190 :
1191 0 : std::vector<CBLSPublicKey> allkeys;
1192 0 : for (const auto& m : members) {
1193 0 : allkeys.emplace_back(m->dmn->pdmnState->pubKeyOperator.Get());
1194 : }
1195 :
1196 0 : typedef std::vector<bool> Key;
1197 0 : std::map<Key, std::vector<CDKGPrematureCommitment>> commitmentsMap;
1198 :
1199 0 : for (const auto& p : prematureCommitments) {
1200 0 : auto& qc = p.second;
1201 0 : if (!validCommitments.count(p.first)) {
1202 0 : continue;
1203 : }
1204 :
1205 : // should have been verified before
1206 0 : assert(qc.CountValidMembers() >= params.minSize);
1207 :
1208 0 : auto it = commitmentsMap.find(qc.validMembers);
1209 0 : if (it == commitmentsMap.end()) {
1210 0 : it = commitmentsMap.emplace(qc.validMembers, std::vector<CDKGPrematureCommitment>()).first;
1211 : }
1212 :
1213 0 : it->second.emplace_back(qc);
1214 : }
1215 :
1216 0 : const CChainParams& chainparams = Params();
1217 :
1218 0 : std::vector<CFinalCommitment> finalCommitments;
1219 0 : for (const auto& p : commitmentsMap) {
1220 0 : auto& cvec = p.second;
1221 0 : if (cvec.size() < (size_t)params.minSize) {
1222 : // commitment was signed by a minority
1223 0 : continue;
1224 : }
1225 :
1226 0 : std::vector<CBLSId> signerIds;
1227 0 : std::vector<CBLSSignature> thresholdSigs;
1228 :
1229 0 : auto& first = cvec[0];
1230 :
1231 0 : CFinalCommitment fqc(params, first.quorumHash);
1232 0 : fqc.validMembers = first.validMembers;
1233 0 : fqc.quorumPublicKey = first.quorumPublicKey;
1234 0 : fqc.quorumVvecHash = first.quorumVvecHash;
1235 :
1236 0 : uint256 commitmentHash = utils::BuildCommitmentHash((Consensus::LLMQType)fqc.llmqType, fqc.quorumHash, fqc.validMembers, fqc.quorumPublicKey, fqc.quorumVvecHash);
1237 :
1238 0 : std::vector<CBLSSignature> aggSigs;
1239 0 : std::vector<CBLSPublicKey> aggPks;
1240 0 : aggSigs.reserve(cvec.size());
1241 0 : aggPks.reserve(cvec.size());
1242 :
1243 0 : for (size_t i = 0; i < cvec.size(); i++) {
1244 0 : auto& qc = cvec[i];
1245 :
1246 0 : 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 0 : size_t signerIndex = membersMap[qc.proTxHash];
1252 0 : const auto& m = members[signerIndex];
1253 :
1254 0 : fqc.signers[signerIndex] = true;
1255 0 : aggSigs.emplace_back(qc.sig);
1256 0 : aggPks.emplace_back(m->dmn->pdmnState->pubKeyOperator.Get());
1257 :
1258 0 : signerIds.emplace_back(m->id);
1259 0 : thresholdSigs.emplace_back(qc.quorumSig);
1260 : }
1261 :
1262 0 : cxxtimer::Timer t1(true);
1263 0 : fqc.membersSig = CBLSSignature::AggregateSecure(aggSigs, aggPks, commitmentHash);
1264 0 : t1.stop();
1265 :
1266 0 : cxxtimer::Timer t2(true);
1267 0 : if (!fqc.quorumSig.Recover(thresholdSigs, signerIds)) {
1268 0 : logger.Batch("failed to recover quorum sig");
1269 0 : continue;
1270 : }
1271 0 : t2.stop();
1272 :
1273 0 : cxxtimer::Timer t3(true);
1274 0 : if (!fqc.Verify(allkeys, params)) {
1275 0 : logger.Batch("failed to verify final commitment");
1276 0 : continue;
1277 : }
1278 0 : t3.stop();
1279 :
1280 0 : finalCommitments.emplace_back(fqc);
1281 :
1282 0 : logger.Batch("final commitment: validMembers=%d, signers=%d, quorumPublicKey=%s, time1=%d, time2=%d, time3=%d, total=%d",
1283 0 : fqc.CountValidMembers(), fqc.CountSigners(), bls::EncodePublic(chainparams, fqc.quorumPublicKey),
1284 0 : t1.count(), t2.count(), t3.count(), totalTimer.count());
1285 : }
1286 :
1287 0 : totalTimer.stop();
1288 0 : logger.Flush();
1289 :
1290 0 : return finalCommitments;
1291 : }
1292 :
1293 0 : CDKGMember* CDKGSession::GetMember(const uint256& proTxHash) const
1294 : {
1295 0 : auto it = membersMap.find(proTxHash);
1296 0 : if (it == membersMap.end()) {
1297 : return nullptr;
1298 : }
1299 0 : return members[it->second].get();
1300 : }
1301 :
1302 0 : void CDKGSession::MarkBadMember(CDKGMember* member)
1303 : {
1304 0 : if (member->bad) {
1305 : return;
1306 : }
1307 0 : quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) {
1308 0 : status.bad = true;
1309 0 : return true;
1310 : });
1311 0 : member->bad = true;
1312 : }
1313 :
1314 0 : void CDKGSession::RelayInvToParticipants(const CInv& inv) const
1315 : {
1316 0 : LOCK(invCs);
1317 0 : g_connman->ForEachNode([&](CNode* pnode) {
1318 0 : if (!pnode->verifiedProRegTxHash.IsNull() && relayMembers.count(pnode->verifiedProRegTxHash)) {
1319 0 : pnode->PushInventory(inv);
1320 : }
1321 0 : });
1322 0 : }
1323 :
1324 : }
|