// Copyright (c) 2018-2022 The Dash Core developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_LLMQ_DKGSESSION_H #define BITCOIN_LLMQ_DKGSESSION_H #include #include #include #include #include #include #include class UniValue; class CInv; class CConnman; namespace llmq { class CFinalCommitment; class CDKGDebugManager; class CDKGSession; class CDKGSessionManager; class CDKGPendingMessages; class CDKGContribution { public: Consensus::LLMQType llmqType; uint256 quorumHash; uint256 proTxHash; BLSVerificationVectorPtr vvec; std::shared_ptr> contributions; CBLSSignature sig; public: template inline void SerializeWithoutSig(Stream& s) const { s << ToUnderlying(llmqType); s << quorumHash; s << proTxHash; s << *vvec; s << *contributions; } template inline void Serialize(Stream& s) const { SerializeWithoutSig(s); s << sig; } template inline void Unserialize(Stream& s) { std::vector tmp1; CBLSIESMultiRecipientObjects tmp2; s >> llmqType; s >> quorumHash; s >> proTxHash; s >> tmp1; s >> tmp2; s >> sig; vvec = std::make_shared>(std::move(tmp1)); contributions = std::make_shared>(std::move(tmp2)); } [[nodiscard]] uint256 GetSignHash() const { CHashWriter hw(SER_GETHASH, 0); SerializeWithoutSig(hw); hw << CBLSSignature(); return hw.GetHash(); } }; class CDKGComplaint { public: Consensus::LLMQType llmqType{Consensus::LLMQType::LLMQ_NONE}; uint256 quorumHash; uint256 proTxHash; std::vector badMembers; std::vector complainForMembers; CBLSSignature sig; public: CDKGComplaint() = default; explicit CDKGComplaint(const Consensus::LLMQParams& params) : badMembers((size_t)params.size), complainForMembers((size_t)params.size) {}; SERIALIZE_METHODS(CDKGComplaint, obj) { READWRITE( obj.llmqType, obj.quorumHash, obj.proTxHash, DYNBITSET(obj.badMembers), DYNBITSET(obj.complainForMembers), obj.sig ); } [[nodiscard]] uint256 GetSignHash() const { CDKGComplaint tmp(*this); tmp.sig = CBLSSignature(); return ::SerializeHash(tmp); } }; class CDKGJustification { public: Consensus::LLMQType llmqType; uint256 quorumHash; uint256 proTxHash; struct Contribution { uint32_t index; CBLSSecretKey key; SERIALIZE_METHODS(Contribution, obj) { READWRITE(obj.index, obj.key); } }; std::vector contributions; CBLSSignature sig; public: SERIALIZE_METHODS(CDKGJustification, obj) { READWRITE(obj.llmqType, obj.quorumHash, obj.proTxHash, obj.contributions, obj.sig); } [[nodiscard]] uint256 GetSignHash() const { CDKGJustification tmp(*this); tmp.sig = CBLSSignature(); return ::SerializeHash(tmp); } }; // each member commits to a single set of valid members with this message // then each node aggregate all received premature commitments // into a single CFinalCommitment, which is only valid if // enough (>=minSize) premature commitments were aggregated class CDKGPrematureCommitment { public: Consensus::LLMQType llmqType{Consensus::LLMQType::LLMQ_NONE}; uint256 quorumHash; uint256 proTxHash; std::vector validMembers; CBLSPublicKey quorumPublicKey; uint256 quorumVvecHash; CBLSSignature quorumSig; // threshold sig share of quorumHash+validMembers+pubKeyHash+vvecHash CBLSSignature sig; // single member sig of quorumHash+validMembers+pubKeyHash+vvecHash public: CDKGPrematureCommitment() = default; explicit CDKGPrematureCommitment(const Consensus::LLMQParams& params) : validMembers((size_t)params.size) {}; [[nodiscard]] int CountValidMembers() const { return int(std::count(validMembers.begin(), validMembers.end(), true)); } public: SERIALIZE_METHODS(CDKGPrematureCommitment, obj) { READWRITE( obj.llmqType, obj.quorumHash, obj.proTxHash, DYNBITSET(obj.validMembers), obj.quorumPublicKey, obj.quorumVvecHash, obj.quorumSig, obj.sig ); } [[nodiscard]] uint256 GetSignHash() const { return utils::BuildCommitmentHash(llmqType, quorumHash, validMembers, quorumPublicKey, quorumVvecHash); } }; class CDKGMember { public: CDKGMember(const CDeterministicMNCPtr& _dmn, size_t _idx); CDeterministicMNCPtr dmn; size_t idx; CBLSId id; std::set contributions; std::set complaints; std::set justifications; std::set prematureCommitments; std::set badMemberVotes; std::set complaintsFromOthers; bool bad{false}; bool badConnection{false}; bool weComplain{false}; bool someoneComplain{false}; }; class DKGError { public: enum type { COMPLAIN_LIE = 0, COMMIT_OMIT, COMMIT_LIE, CONTRIBUTION_OMIT, CONTRIBUTION_LIE, JUSTIFY_OMIT, JUSTIFY_LIE, _COUNT }; static constexpr DKGError::type from_string(std::string_view in) { if (in == "complain-lie") return COMPLAIN_LIE; if (in == "commit-omit") return COMMIT_OMIT; if (in == "commit-lie") return COMMIT_LIE; if (in == "contribution-omit") return CONTRIBUTION_OMIT; if (in == "contribution-lie") return CONTRIBUTION_LIE; if (in == "justify-lie") return JUSTIFY_LIE; if (in == "justify-omit") return JUSTIFY_OMIT; return _COUNT; } }; /** * The DKG session is a single instance of the DKG process. It is owned and called by CDKGSessionHandler, which passes * received DKG messages to the session. The session is not persistent and will loose it's state (the whole object is * discarded) when it finishes (after the mining phase) or is aborted. * * When incoming contributions are received and the verification vector is valid, it is passed to CDKGSessionManager * which will store it in the evo DB. Secret key contributions which are meant for the local member are also passed * to CDKGSessionManager to store them in the evo DB. If verification of the SK contribution initially fails, it is * not passed to CDKGSessionManager. If the justification phase later gives a valid SK contribution from the same * member, it is then passed to CDKGSessionManager and after this handled the same way. * * The contributions stored by CDKGSessionManager are then later loaded by the quorum instances and used for signing * sessions, but only if the local node is a member of the quorum. */ class CDKGSession { friend class CDKGSessionHandler; friend class CDKGSessionManager; friend class CDKGLogger; private: const Consensus::LLMQParams params; CBLSWorker& blsWorker; CBLSWorkerCache cache; CDKGSessionManager& dkgManager; CDKGDebugManager& dkgDebugManager; const CBlockIndex* m_quorum_base_block_index{nullptr}; int quorumIndex{0}; private: std::vector> members; std::map membersMap; std::set relayMembers; BLSVerificationVectorPtr vvecContribution; std::vector m_sk_contributions; std::vector memberIds; std::vector receivedVvecs; // these are not necessarily verified yet. Only trust in what was written to the DB std::vector receivedSkContributions; /// Contains the received unverified/encrypted DKG contributions std::vector>> vecEncryptedContributions; uint256 myProTxHash; CBLSId myId; CConnman& connman; std::optional myIdx; // all indexed by msg hash // we expect to only receive a single vvec and contribution per member, but we must also be able to relay // conflicting messages as otherwise an attacker might be able to broadcast conflicting (valid+invalid) messages // and thus split the quorum. Such members are later removed from the quorum. mutable RecursiveMutex invCs; std::map contributions GUARDED_BY(invCs); std::map complaints GUARDED_BY(invCs); std::map justifications GUARDED_BY(invCs); std::map prematureCommitments GUARDED_BY(invCs); mutable RecursiveMutex cs_pending; std::vector pendingContributionVerifications GUARDED_BY(cs_pending); // filled by ReceivePrematureCommitment and used by FinalizeCommitments std::set validCommitments GUARDED_BY(invCs); public: CDKGSession(const Consensus::LLMQParams& _params, CBLSWorker& _blsWorker, CDKGSessionManager& _dkgManager, CDKGDebugManager& _dkgDebugManager, CConnman& _connman) : params(_params), blsWorker(_blsWorker), cache(_blsWorker), dkgManager(_dkgManager), dkgDebugManager(_dkgDebugManager), connman(_connman) {} bool Init(gsl::not_null pQuorumBaseBlockIndex, Span mns, const uint256& _myProTxHash, int _quorumIndex); [[nodiscard]] std::optional GetMyMemberIndex() const { return myIdx; } /** * The following sets of methods are for the first 4 phases handled in the session. The flow of message calls * is identical for all phases: * 1. Execute local action (e.g. create/send own contributions) * 2. PreVerify incoming messages for this phase. Preverification means that everything from the message is checked * that does not require too much resources for verification. This specifically excludes all CPU intensive BLS * operations. * 3. CDKGSessionHandler will collect pre verified messages in batches and perform batched BLS signature verification * on these. * 4. ReceiveMessage is called for each pre verified message with a valid signature. ReceiveMessage is also * responsible for further verification of validity (e.g. validate vvecs and SK contributions). */ // Phase 1: contribution void Contribute(CDKGPendingMessages& pendingMessages); void SendContributions(CDKGPendingMessages& pendingMessages); bool PreVerifyMessage(const CDKGContribution& qc, bool& retBan) const; void ReceiveMessage(const CDKGContribution& qc, bool& retBan); void VerifyPendingContributions() EXCLUSIVE_LOCKS_REQUIRED(cs_pending); // Phase 2: complaint void VerifyAndComplain(CDKGPendingMessages& pendingMessages); void VerifyConnectionAndMinProtoVersions() const; void SendComplaint(CDKGPendingMessages& pendingMessages); bool PreVerifyMessage(const CDKGComplaint& qc, bool& retBan) const; void ReceiveMessage(const CDKGComplaint& qc, bool& retBan); // Phase 3: justification void VerifyAndJustify(CDKGPendingMessages& pendingMessages); void SendJustification(CDKGPendingMessages& pendingMessages, const std::set& forMembers); bool PreVerifyMessage(const CDKGJustification& qj, bool& retBan) const; void ReceiveMessage(const CDKGJustification& qj, bool& retBan); // Phase 4: commit void VerifyAndCommit(CDKGPendingMessages& pendingMessages); void SendCommitment(CDKGPendingMessages& pendingMessages); bool PreVerifyMessage(const CDKGPrematureCommitment& qc, bool& retBan) const; void ReceiveMessage(const CDKGPrematureCommitment& qc, bool& retBan); // Phase 5: aggregate/finalize std::vector FinalizeCommitments(); [[nodiscard]] bool AreWeMember() const { return !myProTxHash.IsNull(); } void MarkBadMember(size_t idx); void RelayInvToParticipants(const CInv& inv) const; public: [[nodiscard]] CDKGMember* GetMember(const uint256& proTxHash) const; private: [[nodiscard]] bool ShouldSimulateError(DKGError::type type) const; }; void SetSimulatedDKGErrorRate(DKGError::type type, double rate); double GetSimulatedErrorRate(DKGError::type type); } // namespace llmq #endif // BITCOIN_LLMQ_DKGSESSION_H