Line data Source code
1 : // Copyright (c) 2018-2025 The Dash Core developers
2 : // Distributed under the MIT/X11 software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #ifndef BITCOIN_LLMQ_DKGSESSION_H
6 : #define BITCOIN_LLMQ_DKGSESSION_H
7 :
8 : #include <protocol.h>
9 :
10 : #include <batchedlogger.h>
11 : #include <bls/bls.h>
12 : #include <bls/bls_ies.h>
13 : #include <bls/bls_worker.h>
14 : #include <evo/types.h>
15 : #include <llmq/params.h>
16 : #include <util/std23.h>
17 :
18 : #include <saltedhasher.h>
19 :
20 : #include <sync.h>
21 :
22 : #include <optional>
23 :
24 : class CConnman;
25 : class CBLSWorker;
26 : class CDeterministicMNManager;
27 : class ChainstateManager;
28 : class CBlockIndex;
29 : namespace llmq {
30 : class CDKGDebugManager;
31 : class CDKGSession;
32 : class CDKGSessionManager;
33 : class CFinalCommitment;
34 : class CQuorumSnapshotManager;
35 : } // namespace llmq
36 :
37 : namespace llmq {
38 0 : class CDKGContribution
39 : {
40 : public:
41 : Consensus::LLMQType llmqType;
42 : uint256 quorumHash;
43 : uint256 proTxHash;
44 : BLSVerificationVectorPtr vvec;
45 : std::shared_ptr<CBLSIESMultiRecipientObjects<CBLSSecretKey>> contributions;
46 : CBLSSignature sig;
47 :
48 : public:
49 : template<typename Stream>
50 0 : inline void SerializeWithoutSig(Stream& s) const
51 : {
52 0 : s << std23::to_underlying(llmqType);
53 0 : s << quorumHash;
54 0 : s << proTxHash;
55 0 : s << *vvec;
56 0 : s << *contributions;
57 0 : }
58 : template<typename Stream>
59 0 : inline void Serialize(Stream& s) const
60 : {
61 0 : SerializeWithoutSig(s);
62 0 : s << sig;
63 0 : }
64 : template<typename Stream>
65 0 : inline void Unserialize(Stream& s)
66 : {
67 0 : std::vector<CBLSPublicKey> tmp1;
68 0 : CBLSIESMultiRecipientObjects<CBLSSecretKey> tmp2;
69 :
70 0 : s >> llmqType;
71 0 : s >> quorumHash;
72 0 : s >> proTxHash;
73 0 : s >> tmp1;
74 0 : s >> tmp2;
75 0 : s >> sig;
76 :
77 0 : vvec = std::make_shared<std::vector<CBLSPublicKey>>(std::move(tmp1));
78 0 : contributions = std::make_shared<CBLSIESMultiRecipientObjects<CBLSSecretKey>>(std::move(tmp2));
79 0 : }
80 :
81 0 : [[nodiscard]] uint256 GetSignHash() const
82 : {
83 0 : CHashWriter hw(SER_GETHASH, 0);
84 0 : SerializeWithoutSig(hw);
85 0 : hw << CBLSSignature();
86 0 : return hw.GetHash();
87 0 : }
88 : };
89 :
90 0 : class CDKGComplaint
91 : {
92 : public:
93 0 : Consensus::LLMQType llmqType{Consensus::LLMQType::LLMQ_NONE};
94 : uint256 quorumHash;
95 : uint256 proTxHash;
96 : std::vector<bool> badMembers;
97 : std::vector<bool> complainForMembers;
98 : CBLSSignature sig;
99 :
100 : public:
101 0 : CDKGComplaint() = default;
102 0 : explicit CDKGComplaint(const Consensus::LLMQParams& params) :
103 0 : badMembers((size_t)params.size), complainForMembers((size_t)params.size) {};
104 :
105 0 : SERIALIZE_METHODS(CDKGComplaint, obj)
106 : {
107 0 : READWRITE(
108 : obj.llmqType,
109 : obj.quorumHash,
110 : obj.proTxHash,
111 : DYNBITSET(obj.badMembers),
112 : DYNBITSET(obj.complainForMembers),
113 : obj.sig
114 : );
115 0 : }
116 :
117 0 : [[nodiscard]] uint256 GetSignHash() const
118 : {
119 0 : CDKGComplaint tmp(*this);
120 0 : tmp.sig = CBLSSignature();
121 0 : return ::SerializeHash(tmp);
122 0 : }
123 : };
124 :
125 0 : class CDKGJustification
126 : {
127 : public:
128 : Consensus::LLMQType llmqType;
129 : uint256 quorumHash;
130 : uint256 proTxHash;
131 : struct Contribution {
132 : uint32_t index;
133 : CBLSSecretKey key;
134 0 : SERIALIZE_METHODS(Contribution, obj)
135 : {
136 0 : READWRITE(obj.index, obj.key);
137 0 : }
138 : };
139 : std::vector<Contribution> contributions;
140 : CBLSSignature sig;
141 :
142 : public:
143 0 : SERIALIZE_METHODS(CDKGJustification, obj)
144 : {
145 0 : READWRITE(obj.llmqType, obj.quorumHash, obj.proTxHash, obj.contributions, obj.sig);
146 0 : }
147 :
148 0 : [[nodiscard]] uint256 GetSignHash() const
149 : {
150 0 : CDKGJustification tmp(*this);
151 0 : tmp.sig = CBLSSignature();
152 0 : return ::SerializeHash(tmp);
153 0 : }
154 : };
155 :
156 : // each member commits to a single set of valid members with this message
157 : // then each node aggregate all received premature commitments
158 : // into a single CFinalCommitment, which is only valid if
159 : // enough (>=minSize) premature commitments were aggregated
160 : class CDKGPrematureCommitment
161 : {
162 : public:
163 0 : Consensus::LLMQType llmqType{Consensus::LLMQType::LLMQ_NONE};
164 : uint256 quorumHash;
165 : uint256 proTxHash;
166 : std::vector<bool> validMembers;
167 :
168 : CBLSPublicKey quorumPublicKey;
169 : uint256 quorumVvecHash;
170 :
171 : CBLSSignature quorumSig; // threshold sig share of quorumHash+validMembers+pubKeyHash+vvecHash
172 : CBLSSignature sig; // single member sig of quorumHash+validMembers+pubKeyHash+vvecHash
173 :
174 : public:
175 0 : CDKGPrematureCommitment() = default;
176 0 : explicit CDKGPrematureCommitment(const Consensus::LLMQParams& params) :
177 0 : validMembers((size_t)params.size) {};
178 :
179 0 : [[nodiscard]] int CountValidMembers() const
180 : {
181 0 : return int(std::count(validMembers.begin(), validMembers.end(), true));
182 : }
183 :
184 : public:
185 0 : SERIALIZE_METHODS(CDKGPrematureCommitment, obj)
186 : {
187 0 : READWRITE(
188 : obj.llmqType,
189 : obj.quorumHash,
190 : obj.proTxHash,
191 : DYNBITSET(obj.validMembers),
192 : obj.quorumPublicKey,
193 : obj.quorumVvecHash,
194 : obj.quorumSig,
195 : obj.sig
196 : );
197 0 : }
198 :
199 : [[nodiscard]] uint256 GetSignHash() const;
200 : };
201 :
202 : class CDKGMember
203 : {
204 : public:
205 : CDKGMember(const CDeterministicMNCPtr& _dmn, size_t _idx);
206 :
207 : CDeterministicMNCPtr dmn;
208 : size_t idx;
209 : CBLSId id;
210 :
211 : Uint256HashSet contributions;
212 : Uint256HashSet complaints;
213 : Uint256HashSet justifications;
214 : Uint256HashSet prematureCommitments;
215 :
216 : Uint256HashSet badMemberVotes;
217 : Uint256HashSet complaintsFromOthers;
218 :
219 : bool bad{false};
220 : bool badConnection{false};
221 : bool weComplain{false};
222 : bool someoneComplain{false};
223 : };
224 :
225 : class DKGError {
226 : public:
227 : enum type {
228 : COMPLAIN_LIE = 0,
229 : COMMIT_OMIT,
230 : COMMIT_LIE,
231 : CONTRIBUTION_OMIT,
232 : CONTRIBUTION_LIE,
233 : JUSTIFY_OMIT,
234 : JUSTIFY_LIE,
235 : _COUNT
236 : };
237 0 : static constexpr DKGError::type from_string(std::string_view in) {
238 0 : if (in == "complain-lie") return COMPLAIN_LIE;
239 0 : if (in == "commit-omit") return COMMIT_OMIT;
240 0 : if (in == "commit-lie") return COMMIT_LIE;
241 0 : if (in == "contribution-omit") return CONTRIBUTION_OMIT;
242 0 : if (in == "contribution-lie") return CONTRIBUTION_LIE;
243 0 : if (in == "justify-lie") return JUSTIFY_LIE;
244 0 : if (in == "justify-omit") return JUSTIFY_OMIT;
245 0 : return _COUNT;
246 0 : }
247 : };
248 :
249 : class CDKGLogger : public CBatchedLogger
250 : {
251 : public:
252 : CDKGLogger(const CDKGSession& _quorumDkg, std::string_view _func, int source_line);
253 : };
254 :
255 : /**
256 : * The DKG session is a single instance of the DKG process. It is owned and called by CDKGSessionHandler, which passes
257 : * received DKG messages to the session. The session is not persistent and will loose it's state (the whole object is
258 : * discarded) when it finishes (after the mining phase) or is aborted.
259 : *
260 : * When incoming contributions are received and the verification vector is valid, it is passed to CDKGSessionManager
261 : * which will store it in the evo DB. Secret key contributions which are meant for the local member are also passed
262 : * to CDKGSessionManager to store them in the evo DB. If verification of the SK contribution initially fails, it is
263 : * not passed to CDKGSessionManager. If the justification phase later gives a valid SK contribution from the same
264 : * member, it is then passed to CDKGSessionManager and after this handled the same way.
265 : *
266 : * The contributions stored by CDKGSessionManager are then later loaded by the quorum instances and used for signing
267 : * sessions, but only if the local node is a member of the quorum.
268 : */
269 : class CDKGSession
270 : {
271 : friend class CDKGLogger;
272 :
273 : protected:
274 : enum class MsgPhase : uint8_t {
275 : Contribution,
276 : Complaint,
277 : Justification
278 : };
279 :
280 : struct ReceiveMessageState {
281 : CDKGMember* member{nullptr};
282 : uint256 hash{};
283 : CInv inv{};
284 : bool should_process{true};
285 : };
286 :
287 : protected:
288 : CBLSWorker& blsWorker;
289 : CBLSWorkerCache cache;
290 : CDeterministicMNManager& m_dmnman;
291 : CDKGDebugManager& dkgDebugManager;
292 : CDKGSessionManager& dkgManager;
293 : CQuorumSnapshotManager& m_qsnapman;
294 : const ChainstateManager& m_chainman;
295 : const Consensus::LLMQParams& params;
296 : const CBlockIndex* const m_quorum_base_block_index;
297 :
298 : protected:
299 : int quorumIndex{0};
300 : std::vector<std::unique_ptr<CDKGMember>> members;
301 : std::map<uint256, size_t> membersMap;
302 : Uint256HashSet relayMembers;
303 : BLSVerificationVectorPtr vvecContribution;
304 : std::vector<CBLSSecretKey> m_sk_contributions;
305 :
306 : std::vector<CBLSId> memberIds;
307 : std::vector<BLSVerificationVectorPtr> receivedVvecs;
308 : // these are not necessarily verified yet. Only trust in what was written to the DB
309 : std::vector<CBLSSecretKey> receivedSkContributions;
310 : /// Contains the received unverified/encrypted DKG contributions
311 : std::vector<std::shared_ptr<CBLSIESMultiRecipientObjects<CBLSSecretKey>>> vecEncryptedContributions;
312 :
313 : uint256 myProTxHash;
314 : CBLSId myId;
315 : std::optional<size_t> myIdx;
316 :
317 : // all indexed by msg hash
318 : // we expect to only receive a single vvec and contribution per member, but we must also be able to relay
319 : // conflicting messages as otherwise an attacker might be able to broadcast conflicting (valid+invalid) messages
320 : // and thus split the quorum. Such members are later removed from the quorum.
321 : mutable Mutex invCs;
322 : std::map<uint256, CDKGContribution> contributions GUARDED_BY(invCs);
323 : std::map<uint256, CDKGComplaint> complaints GUARDED_BY(invCs);
324 : std::map<uint256, CDKGJustification> justifications GUARDED_BY(invCs);
325 : std::map<uint256, CDKGPrematureCommitment> prematureCommitments GUARDED_BY(invCs);
326 :
327 : mutable Mutex cs_pending;
328 : std::vector<size_t> pendingContributionVerifications GUARDED_BY(cs_pending);
329 :
330 : // filled by ReceivePrematureCommitment and used by FinalizeCommitments
331 : Uint256HashSet validCommitments GUARDED_BY(invCs);
332 :
333 : public:
334 : CDKGSession(CBLSWorker& _blsWorker, CDeterministicMNManager& dmnman, CDKGDebugManager& _dkgDebugManager,
335 : CDKGSessionManager& _dkgManager, CQuorumSnapshotManager& qsnapman, const ChainstateManager& chainman,
336 : const CBlockIndex* pQuorumBaseBlockIndex, const Consensus::LLMQParams& _params);
337 : virtual ~CDKGSession();
338 :
339 : // TODO: remove Init completely
340 : bool Init(const uint256& _myProTxHash, int _quorumIndex);
341 :
342 : /**
343 : * The following sets of methods are for the first 4 phases handled in the session. The flow of message calls
344 : * is identical for all phases:
345 : * 1. Execute local action (e.g. create/send own contributions)
346 : * 2. PreVerify incoming messages for this phase. Preverification means that everything from the message is checked
347 : * that does not require too much resources for verification. This specifically excludes all CPU intensive BLS
348 : * operations.
349 : * 3. CDKGSessionHandler will collect pre verified messages in batches and perform batched BLS signature verification
350 : * on these.
351 : * 4. ReceiveMessage is called for each pre verified message with a valid signature. ReceiveMessage is also
352 : * responsible for further verification of validity (e.g. validate vvecs and SK contributions).
353 : */
354 :
355 : // Phase 1: contribution
356 0 : virtual std::optional<CDKGContribution> Contribute() { return std::nullopt; }
357 0 : virtual std::optional<CDKGContribution> SendContributions() { return std::nullopt; }
358 : bool PreVerifyMessage(const CDKGContribution& qc, bool& retBan) const;
359 : std::optional<CInv> ReceiveMessage(const CDKGContribution& qc) EXCLUSIVE_LOCKS_REQUIRED(!invCs, !cs_pending);
360 0 : virtual void VerifyPendingContributions() EXCLUSIVE_LOCKS_REQUIRED(cs_pending) {}
361 :
362 : // Phase 2: complaint
363 0 : virtual std::optional<CDKGComplaint> VerifyAndComplain(CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_pending)
364 : {
365 0 : return std::nullopt;
366 : }
367 0 : virtual void VerifyConnectionAndMinProtoVersions(CConnman& connman) const {}
368 0 : virtual std::optional<CDKGComplaint> SendComplaint() { return std::nullopt; }
369 : bool PreVerifyMessage(const CDKGComplaint& qc, bool& retBan) const;
370 : std::optional<CInv> ReceiveMessage(const CDKGComplaint& qc) EXCLUSIVE_LOCKS_REQUIRED(!invCs);
371 :
372 : // Phase 3: justification
373 0 : virtual std::optional<CDKGJustification> VerifyAndJustify() EXCLUSIVE_LOCKS_REQUIRED(!invCs)
374 : {
375 0 : return std::nullopt;
376 : }
377 0 : virtual std::optional<CDKGJustification> SendJustification(const Uint256HashSet& forMembers)
378 : {
379 0 : return std::nullopt;
380 : }
381 : bool PreVerifyMessage(const CDKGJustification& qj, bool& retBan) const;
382 : std::optional<CInv> ReceiveMessage(const CDKGJustification& qj) EXCLUSIVE_LOCKS_REQUIRED(!invCs);
383 :
384 : // Phase 4: commit
385 0 : virtual std::optional<CDKGPrematureCommitment> VerifyAndCommit() { return std::nullopt; }
386 0 : virtual std::optional<CDKGPrematureCommitment> SendCommitment() { return std::nullopt; }
387 : bool PreVerifyMessage(const CDKGPrematureCommitment& qc, bool& retBan) const;
388 : std::optional<CInv> ReceiveMessage(const CDKGPrematureCommitment& qc) EXCLUSIVE_LOCKS_REQUIRED(!invCs);
389 :
390 : // Phase 5: aggregate/finalize
391 : virtual std::vector<CFinalCommitment> FinalizeCommitments() EXCLUSIVE_LOCKS_REQUIRED(!invCs);
392 :
393 : // All Phases 5-in-1 for single-node-quorum
394 : virtual CFinalCommitment FinalizeSingleCommitment();
395 :
396 : //! Look up a received message by hash. Used by CDKGSessionHandler subclasses to implement their Get* virtuals.
397 : [[nodiscard]] bool GetContribution(const uint256& hash, CDKGContribution& ret) const EXCLUSIVE_LOCKS_REQUIRED(!invCs);
398 : [[nodiscard]] bool GetComplaint(const uint256& hash, CDKGComplaint& ret) const EXCLUSIVE_LOCKS_REQUIRED(!invCs);
399 : [[nodiscard]] bool GetJustification(const uint256& hash, CDKGJustification& ret) const EXCLUSIVE_LOCKS_REQUIRED(!invCs);
400 : [[nodiscard]] bool GetPrematureCommitment(const uint256& hash, CDKGPrematureCommitment& ret) const EXCLUSIVE_LOCKS_REQUIRED(!invCs);
401 :
402 : public:
403 0 : [[nodiscard]] bool AreWeMember() const { return !myProTxHash.IsNull(); }
404 : [[nodiscard]] CDKGMember* GetMember(const uint256& proTxHash) const;
405 : [[nodiscard]] CDKGMember* GetMemberAtIndex(size_t index) const;
406 0 : [[nodiscard]] std::optional<size_t> GetMyMemberIndex() const { return myIdx; }
407 0 : [[nodiscard]] const Uint256HashSet& RelayMembers() const { return relayMembers; }
408 0 : [[nodiscard]] const CBlockIndex* BlockIndex() const { return m_quorum_base_block_index; }
409 0 : [[nodiscard]] const uint256& ProTx() const { return myProTxHash; }
410 0 : [[nodiscard]] Consensus::LLMQType GetType() const { return params.type; }
411 :
412 : protected:
413 0 : virtual bool MaybeDecrypt(const CBLSIESMultiRecipientObjects<CBLSSecretKey>& obj, size_t idx,
414 : CBLSSecretKey& ret_obj, int version)
415 : {
416 0 : return false;
417 : }
418 :
419 : [[nodiscard]] bool ShouldSimulateError(DKGError::type type) const;
420 :
421 : template <typename MsgType>
422 : [[nodiscard]] std::optional<ReceiveMessageState> ReceiveMessagePreamble(const MsgType& msg, MsgPhase phase, CDKGLogger& logger)
423 : EXCLUSIVE_LOCKS_REQUIRED(invCs);
424 :
425 : void MarkBadMember(size_t idx);
426 : };
427 :
428 : void SetSimulatedDKGErrorRate(DKGError::type type, double rate);
429 : double GetSimulatedErrorRate(DKGError::type type);
430 : } // namespace llmq
431 :
432 : #endif // BITCOIN_LLMQ_DKGSESSION_H
|