diff --git a/src/avalanche/delegation.h b/src/avalanche/delegation.h --- a/src/avalanche/delegation.h +++ b/src/avalanche/delegation.h @@ -49,6 +49,15 @@ SER_READ(obj, obj.dgid = obj.computeDelegationId()); } + /** + * Verify the delegation: check all the signatures at + * each level. + * + * @param[out] state Verification state of the delegation. + * @param[in] proof Avalanche proof corresponding to this delegation. + * @param[out] auth pubkey for the last level of the delegation. + * @return true if the verification is successful, else false + */ bool verify(DelegationState &state, const Proof &proof, CPubKey &auth) const; }; diff --git a/src/avalanche/processor.h b/src/avalanche/processor.h --- a/src/avalanche/processor.h +++ b/src/avalanche/processor.h @@ -286,6 +286,8 @@ CPubKey getSessionPubKey() const; bool sendHello(CNode *pfrom) const; + const Proof getProof() const; + std::vector getPeers() const; std::vector getNodeIdsForPeer(PeerId peerId) const; diff --git a/src/avalanche/processor.cpp b/src/avalanche/processor.cpp --- a/src/avalanche/processor.cpp +++ b/src/avalanche/processor.cpp @@ -427,6 +427,11 @@ return true; } +const Proof Processor::getProof() const { + assert(peerData); + return peerData->proof; +} + bool Processor::startEventLoop(CScheduler &scheduler) { return eventLoop.startEventLoop( scheduler, [this]() { this->runEventLoop(); }, AVALANCHE_TIME_STEP); diff --git a/src/net.h b/src/net.h --- a/src/net.h +++ b/src/net.h @@ -998,6 +998,7 @@ AvalancheState() {} avalanche::Delegation delegation; + SchnorrSig sig; }; // m_avalanche_state == nullptr if we're not using avalanche with this peer diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -2063,6 +2064,29 @@ } } + // Process an avalanche proof item. For now, we assume that peers always + // request a single avalanche proof, and it must be the one this node + // advertised in the avahello message. + if (it != pfrom.vRecvGetData.end() && it->type == MSG_AVALANCHE_PROOF) { + const CInv &inv = *it++; + + if (!g_avalanche || + !gArgs.GetBoolArg("-enableavalanche", AVALANCHE_DEFAULT_ENABLED) || + !gArgs.IsArgSet("-avaproof")) { + vNotFound.push_back(inv); + } else { + avalanche::ProofId proofid{inv.hash}; + avalanche::Proof proof = g_avalanche->getProof(); + if (proofid == proof.getId()) { + connman.PushMessage(&pfrom, + msgMaker.Make(NetMsgType::AVAPROOF, proof)); + } else { + // The requested proof is not ours. + vNotFound.push_back(inv); + } + } + } + // Only process one BLOCK item per call, since they're uncommon and can be // expensive to process. if (it != pfrom.vRecvGetData.end() && !pfrom.fPauseSend) { @@ -3954,17 +3978,34 @@ pfrom.m_avalanche_state = std::make_unique(); } + // Store the delegation and signature for later verification. CHashVerifier verifier(&vRecv); avalanche::Delegation &delegation = pfrom.m_avalanche_state->delegation; verifier >> delegation; + SchnorrSig &sig = pfrom.m_avalanche_state->sig; + verifier >> sig; + + // Ask for the proof. + std::vector vGetData; + vGetData.emplace_back( + CInv(MSG_AVALANCHE_PROOF, delegation.getProofId())); + m_connman.PushMessage(&pfrom, + msgMaker.Make(NetMsgType::GETDATA, vGetData)); + } + + if (msg_type == NetMsgType::AVAPROOF && g_avalanche) { + // Read the proof. avalanche::Proof proof; - // TODO: read proof from message + vRecv >> proof; + if (proof.getStakes().size() > AVALANCHE_MAX_PROOF_STAKES) { Misbehaving(pfrom, 100, "too-large-avalanche-proof"); return; } + // Verify the delegation is internally consistent. + avalanche::Delegation &delegation = pfrom.m_avalanche_state->delegation; avalanche::DelegationState state; CPubKey pubkey; if (!delegation.verify(state, proof, pubkey)) { @@ -3972,8 +4013,32 @@ return; } - SchnorrSig sig; - verifier >> sig; + // Use the delegated pubkey to verify the signature received + // in the avahello message. + SchnorrSig &sig = pfrom.m_avalanche_state->sig; + CHashWriter hasher(SER_GETHASH, 0); + hasher << delegation.getId(); + hasher << pfrom.nRemoteHostNonce; + hasher << pfrom.GetLocalNonce(); + hasher << pfrom.nRemoteExtraEntropy; + hasher << pfrom.GetLocalExtraEntropy(); + const uint256 hash = hasher.GetHash(); + + bool success; + success = pubkey.VerifySchnorr(hash, sig); + if (!success) { + Misbehaving(pfrom, 100, "invalid-avalanche-handshake"); + return; + } + + // Add the node the avalanche peers. + success = g_avalanche->addNode(pfrom.GetId(), proof, delegation); + if (success) { + LogPrint(BCLog::NET, "added avalanche node=%d\n", pfrom.GetId()); + } else { + LogPrint(BCLog::NET, "failed to add avalanche node=%d\n", + pfrom.GetId()); + } } // Ignore avalanche requests while importing diff --git a/src/protocol.h b/src/protocol.h --- a/src/protocol.h +++ b/src/protocol.h @@ -310,6 +310,12 @@ * Sent in response to a "avapoll" message. */ extern const char *AVARESPONSE; +/** + * Contains an avalanche::Proof. + * Sent in response to a "getdata" message with inventory type + * MSG_AVALANCHE_PROOF. + */ +extern const char *AVAPROOF; /** * Indicate if the message is used to transmit the content of a block. @@ -495,6 +501,7 @@ MSG_FILTERED_BLOCK = 3, //! Defined in BIP152 MSG_CMPCT_BLOCK = 4, + MSG_AVALANCHE_PROOF = 5, }; /** diff --git a/src/protocol.cpp b/src/protocol.cpp --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -54,6 +54,7 @@ const char *AVAHELLO = "avahello"; const char *AVAPOLL = "avapoll"; const char *AVARESPONSE = "avaresponse"; +const char *AVAPROOF = "avaproof"; bool IsBlockLike(const std::string &strCommand) { return strCommand == NetMsgType::BLOCK ||