diff --git a/src/net.h b/src/net.h --- a/src/net.h +++ b/src/net.h @@ -1000,6 +1000,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 @@ -3979,11 +3979,30 @@ 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; + verifier >> pfrom.m_avalanche_state->delegation; + SchnorrSig &sig = pfrom.m_avalanche_state->sig; + verifier >> sig; + return; + } + + if (msg_type == NetMsgType::AVAPROOF) { + if (!pfrom.m_avalanche_state) { + // We don't support the case of a AVAPROOF received before + // the AVAHELLO for now. Ideally we should store all the data + // received, and process it after both messages were received, + // no matter which order they reach us. + return; + } + // Read the proof. avalanche::Proof proof; + vRecv >> proof; + + // Verify the delegation + const avalanche::Delegation &delegation = + pfrom.m_avalanche_state->delegation; avalanche::DelegationState state; CPubKey pubkey; if (!delegation.verify(state, proof, pubkey)) { @@ -3991,8 +4010,20 @@ return; } - SchnorrSig sig; - verifier >> sig; + // Use the delegated pubkey to verify the signature received + // in the avahello message. + const uint256 hash = g_avalanche->buildRemoteSighash(&pfrom); + + if (!pubkey.VerifySchnorr(hash, pfrom.m_avalanche_state->sig)) { + Misbehaving(pfrom, 100, "invalid-avalanche-handshake"); + return; + } + + // TODO: + // - store the proof in a proofpool + // - if IBD is finished, add the node + // - if IBD, add the node later, when we can check its proof + return; } if (msg_type == NetMsgType::AVAPOLL) { diff --git a/test/functional/abc_p2p_avalanche.py b/test/functional/abc_p2p_avalanche.py --- a/test/functional/abc_p2p_avalanche.py +++ b/test/functional/abc_p2p_avalanche.py @@ -3,6 +3,7 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the resolution of forks via avalanche.""" +from io import BytesIO import random from test_framework.avatools import get_stakes @@ -12,10 +13,13 @@ ) from test_framework.mininode import P2PInterface, mininode_lock from test_framework.messages import ( + AvalancheProof, AvalancheResponse, AvalancheVote, CInv, + msg_avahello, msg_avapoll, + msg_avaproof, msg_tcpavaresponse, NODE_AVALANCHE, NODE_NETWORK, @@ -104,6 +108,11 @@ with mininode_lock: return self.avapolls.pop(0) if len(self.avapolls) > 0 else None + def send_avahello(self, hello): + msg = msg_avahello() + msg.hello = hello + self.send_message(msg) + def wait_for_avahello(self, timeout=5): wait_until( lambda: self.avahello is not None, @@ -113,6 +122,11 @@ with mininode_lock: return self.avahello + def send_avaproof(self, proof): + msg = msg_avaproof() + msg.proof = proof + self.send_message(msg) + class AvalancheTest(BitcoinTestFramework): def set_test_params(self): @@ -350,8 +364,7 @@ ]) self.log.info("Test the avahello signature") - quorum = get_quorum() - poll_node = quorum[0] + poll_node = get_node() avahello = poll_node.wait_for_avahello().hello @@ -359,6 +372,21 @@ assert avakey.verify_schnorr( avahello.sig, avahello.get_sighash(poll_node)) + self.log.info("Test banning behavior on invalid avahello signature") + poll_node = get_node() + with self.nodes[0].assert_debug_log( + ['Misbehaving', + '(0 -> 100) BAN THRESHOLD EXCEEDED: invalid-avalanche-handshake']): + # We use the same avahello that the node previously sent to + # another p2p interface. The signature will not correspond to + # the expected message for this peer. + poll_node.send_avahello(avahello) + # The signature is checked when the delegation can be checked, + # after receiving a proof. + proof_obj = AvalancheProof() + proof_obj.deserialize(BytesIO(bytes.fromhex(proof))) + poll_node.send_avaproof(proof_obj) + if __name__ == '__main__': AvalancheTest().main()