Line data Source code
1 : // Copyright (c) 2019-2025 The Dash Core developers
2 : // Distributed under the MIT software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #include <evo/mnauth.h>
6 :
7 : #include <active/masternode.h>
8 : #include <bls/bls.h>
9 : #include <evo/deterministicmns.h>
10 : #include <llmq/utils.h>
11 : #include <masternode/meta.h>
12 : #include <masternode/sync.h>
13 :
14 : #include <chainparams.h>
15 : #include <net.h>
16 : #include <netmessagemaker.h>
17 : #include <util/time.h>
18 :
19 0 : void CMNAuth::PushMNAUTH(CNode& peer, CConnman& connman, const CActiveMasternodeManager& mn_activeman)
20 : {
21 0 : CMNAuth mnauth;
22 0 : if (mn_activeman.GetProTxHash().IsNull()) {
23 0 : return;
24 : }
25 :
26 0 : const auto receivedMNAuthChallenge = peer.GetReceivedMNAuthChallenge();
27 0 : if (receivedMNAuthChallenge.IsNull()) {
28 0 : return;
29 : }
30 : // We include fInbound in signHash to forbid interchanging of challenges by a man in the middle (MITM). This way
31 : // we protect ourselves against MITM in this form:
32 : // node1 <- Eve -> node2
33 : // It does not protect against:
34 : // node1 -> Eve -> node2
35 : // This is ok as we only use MNAUTH as a DoS protection and not for sensitive stuff
36 0 : int nOurNodeVersion{PROTOCOL_VERSION};
37 0 : if (Params().NetworkIDString() != CBaseChainParams::MAIN && gArgs.IsArgSet("-pushversion")) {
38 0 : nOurNodeVersion = gArgs.GetIntArg("-pushversion", PROTOCOL_VERSION);
39 0 : }
40 0 : const uint256 signHash{::SerializeHash(std::make_tuple(mn_activeman.GetPubKey(), receivedMNAuthChallenge, peer.IsInboundConn(), nOurNodeVersion))};
41 :
42 0 : mnauth.proRegTxHash = mn_activeman.GetProTxHash();
43 :
44 : // all clients uses basic BLS
45 0 : mnauth.sig = mn_activeman.Sign(signHash, false);
46 :
47 0 : LogPrint(BCLog::NET_NETCONN, "CMNAuth::%s -- Sending MNAUTH, peer=%d\n", __func__, peer.GetId());
48 0 : connman.PushMessage(&peer, CNetMsgMaker(peer.GetCommonVersion()).Make(NetMsgType::MNAUTH, mnauth));
49 0 : }
50 :
51 0 : MessageProcessingResult CMNAuth::ProcessMessage(CNode& peer, ServiceFlags node_services, CConnman& connman, CMasternodeMetaMan& mn_metaman,
52 : const CActiveMasternodeManager* const mn_activeman, const CMasternodeSync& mn_sync,
53 : const CDeterministicMNList& tip_mn_list, std::string_view msg_type, CDataStream& vRecv)
54 : {
55 0 : assert(mn_metaman.IsValid());
56 :
57 0 : if (msg_type != NetMsgType::MNAUTH || !mn_sync.IsBlockchainSynced()) {
58 : // we can't verify MNAUTH messages when we don't have the latest MN list
59 0 : return {};
60 : }
61 :
62 0 : CMNAuth mnauth;
63 0 : vRecv >> mnauth;
64 :
65 : // only one MNAUTH allowed
66 0 : if (!peer.GetVerifiedProRegTxHash().IsNull()) {
67 0 : return MisbehavingError{100, "duplicate mnauth"};
68 : }
69 :
70 0 : if ((~node_services) & (NODE_NETWORK | NODE_BLOOM)) {
71 : // either NODE_NETWORK or NODE_BLOOM bit is missing in node's services
72 0 : return MisbehavingError{100, "mnauth from a node with invalid services"};
73 : }
74 :
75 0 : if (mnauth.proRegTxHash.IsNull()) {
76 0 : return MisbehavingError{100, "empty mnauth proRegTxHash"};
77 : }
78 :
79 0 : if (!mnauth.sig.IsValid()) {
80 0 : LogPrint(BCLog::NET_NETCONN, "CMNAuth::ProcessMessage -- invalid mnauth for protx=%s with sig=%s\n",
81 : mnauth.proRegTxHash.ToString(), mnauth.sig.ToString(false));
82 0 : return MisbehavingError{100, "invalid mnauth signature"};
83 : }
84 :
85 0 : const auto dmn = tip_mn_list.GetMN(mnauth.proRegTxHash);
86 0 : if (!dmn) {
87 : // in case node was unlucky and not up to date, just let it be connected as a regular node, which gives it
88 : // a chance to get up-to-date and thus realize that it's not a MN anymore. We still give it a
89 : // low DoS score.
90 0 : return MisbehavingError{10, "missing mnauth masternode"};
91 : }
92 :
93 0 : const CBLSPublicKey pubKey(dmn->pdmnState->pubKeyOperator.Get());
94 : // See comment in PushMNAUTH (fInbound is negated here as we're on the other side of the connection)
95 0 : const uint256 signHash{::SerializeHash(std::make_tuple(pubKey, peer.GetSentMNAuthChallenge(), !peer.IsInboundConn(), peer.nVersion.load()))};
96 0 : LogPrint(BCLog::NET_NETCONN, "CMNAuth::%s -- constructed signHash for nVersion %d, peer=%d\n", __func__, peer.nVersion, peer.GetId());
97 :
98 0 : if (!mnauth.sig.VerifyInsecure(dmn->pdmnState->pubKeyOperator.Get(), signHash, false)) {
99 : // Same as above, MN seems to not know its fate yet, so give it a chance to update. If this is a
100 : // malicious node (DoSing us), it'll get banned soon.
101 0 : return MisbehavingError{10, "mnauth signature verification failed"};
102 : }
103 :
104 0 : if (!peer.IsInboundConn()) {
105 0 : mn_metaman.SetLastOutboundSuccess(mnauth.proRegTxHash, GetTime<std::chrono::seconds>().count());
106 0 : if (peer.m_masternode_probe_connection) {
107 0 : LogPrint(BCLog::NET_NETCONN, "CMNAuth::ProcessMessage -- Masternode probe successful for %s, disconnecting. peer=%d\n",
108 : mnauth.proRegTxHash.ToString(), peer.GetId());
109 0 : peer.fDisconnect = true;
110 0 : return {};
111 : }
112 0 : }
113 :
114 0 : const uint256 myProTxHash = mn_activeman != nullptr ? mn_activeman->GetProTxHash() : uint256();
115 :
116 0 : connman.ForEachNode([&](CNode* pnode2) {
117 0 : if (peer.fDisconnect) {
118 : // we've already disconnected the new peer
119 0 : return;
120 : }
121 :
122 0 : if (pnode2->GetVerifiedProRegTxHash() == mnauth.proRegTxHash) {
123 0 : if (mn_activeman != nullptr && !myProTxHash.IsNull()) {
124 0 : const auto deterministicOutbound = llmq::utils::DeterministicOutboundConnection(myProTxHash, mnauth.proRegTxHash);
125 0 : LogPrint(BCLog::NET_NETCONN, "CMNAuth::ProcessMessage -- Masternode %s has already verified as peer %d, deterministicOutbound=%s. peer=%d\n",
126 : mnauth.proRegTxHash.ToString(), pnode2->GetId(), deterministicOutbound.ToString(), peer.GetId());
127 0 : if (deterministicOutbound == myProTxHash) {
128 : // NOTE: do not drop inbound nodes here, mark them as probes so that
129 : // they would be disconnected later in CMasternodeUtils::DoMaintenance
130 0 : if (pnode2->IsInboundConn()) {
131 0 : LogPrint(BCLog::NET_NETCONN, "CMNAuth::ProcessMessage -- marking old inbound for dropping it later, peer=%d\n", pnode2->GetId());
132 0 : pnode2->m_masternode_probe_connection = true;
133 0 : } else if (peer.IsInboundConn()) {
134 0 : LogPrint(BCLog::NET_NETCONN, "CMNAuth::ProcessMessage -- marking new inbound for dropping it later, peer=%d\n", peer.GetId());
135 0 : peer.m_masternode_probe_connection = true;
136 0 : }
137 0 : } else {
138 0 : if (!pnode2->IsInboundConn()) {
139 0 : LogPrint(BCLog::NET_NETCONN, "CMNAuth::ProcessMessage -- dropping old outbound, peer=%d\n", pnode2->GetId());
140 0 : pnode2->fDisconnect = true;
141 0 : } else if (!peer.IsInboundConn()) {
142 0 : LogPrint(BCLog::NET_NETCONN, "CMNAuth::ProcessMessage -- dropping new outbound, peer=%d\n", peer.GetId());
143 0 : peer.fDisconnect = true;
144 0 : }
145 : }
146 0 : } else {
147 0 : LogPrint(BCLog::NET_NETCONN, "CMNAuth::ProcessMessage -- Masternode %s has already verified as peer %d, dropping new connection. peer=%d\n",
148 : mnauth.proRegTxHash.ToString(), pnode2->GetId(), peer.GetId());
149 0 : peer.fDisconnect = true;
150 : }
151 0 : }
152 0 : });
153 :
154 0 : if (peer.fDisconnect) {
155 0 : return {};
156 : }
157 :
158 0 : peer.SetVerifiedProRegTxHash(mnauth.proRegTxHash);
159 0 : peer.SetVerifiedPubKeyHash(dmn->pdmnState->pubKeyOperator.GetHash());
160 :
161 0 : if (!peer.m_masternode_iqr_connection && connman.IsMasternodeQuorumRelayMember(peer.GetVerifiedProRegTxHash())) {
162 : // Tell our peer that we're interested in plain LLMQ recovered signatures.
163 : // Otherwise, the peer would only announce/send messages resulting from QRECSIG,
164 : // e.g. InstantSend locks or ChainLocks. SPV and regular full nodes should not send
165 : // this message as they are usually only interested in the higher level messages.
166 0 : const CNetMsgMaker msgMaker(peer.GetCommonVersion());
167 0 : connman.PushMessage(&peer, msgMaker.Make(NetMsgType::QSENDRECSIGS, true));
168 0 : peer.m_masternode_iqr_connection = true;
169 0 : }
170 :
171 0 : LogPrint(BCLog::NET_NETCONN, "CMNAuth::%s -- Valid MNAUTH for %s, peer=%d\n", __func__, mnauth.proRegTxHash.ToString(), peer.GetId());
172 0 : return {};
173 0 : }
174 :
175 0 : void CMNAuth::NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff, CConnman& connman)
176 : {
177 : // we're only interested in updated/removed MNs. Added MNs are of no interest for us
178 0 : if (diff.updatedMNs.empty() && diff.removedMns.empty()) {
179 0 : return;
180 : }
181 :
182 0 : connman.ForEachNode([&oldMNList, &diff](CNode* pnode) {
183 0 : const auto verifiedProRegTxHash = pnode->GetVerifiedProRegTxHash();
184 0 : if (verifiedProRegTxHash.IsNull()) {
185 0 : return;
186 : }
187 0 : const auto verifiedDmn = oldMNList.GetMN(verifiedProRegTxHash);
188 0 : if (!verifiedDmn) {
189 0 : return;
190 : }
191 0 : bool doRemove = false;
192 0 : if (diff.removedMns.count(verifiedDmn->GetInternalId())) {
193 0 : doRemove = true;
194 0 : } else if (const auto it = diff.updatedMNs.find(verifiedDmn->GetInternalId()); it != diff.updatedMNs.end()) {
195 0 : if ((it->second.fields & CDeterministicMNStateDiff::Field_pubKeyOperator) && it->second.state.pubKeyOperator.GetHash() != pnode->GetVerifiedPubKeyHash()) {
196 0 : doRemove = true;
197 0 : }
198 0 : }
199 :
200 0 : if (doRemove) {
201 0 : LogPrint(BCLog::NET_NETCONN, "CMNAuth::NotifyMasternodeListChanged -- Disconnecting MN %s due to key changed/removed, peer=%d\n",
202 : verifiedProRegTxHash.ToString(), pnode->GetId());
203 0 : pnode->fDisconnect = true;
204 0 : }
205 0 : });
206 0 : }
|