diff --git a/src/avalanche/peermanager.h b/src/avalanche/peermanager.h --- a/src/avalanche/peermanager.h +++ b/src/avalanche/peermanager.h @@ -79,6 +79,18 @@ struct next_request_time {}; +struct AvaHelloSigData { + uint256 sigHash; + SchnorrSig signature; + + AvaHelloSigData(uint256 hash, SchnorrSig sig) + : sigHash(hash), signature(sig) {} + + bool verify(const CPubKey pubkey) const { + return pubkey.VerifySchnorr(sigHash, signature); + } +}; + class PeerManager { std::vector slots; uint64_t slotCount = 0; @@ -128,7 +140,8 @@ * Node API. */ bool addNode(NodeId nodeid, const Proof &proof, - const Delegation &delegation); + const Delegation &delegation, + std::optional sigData); bool removeNode(NodeId nodeid); bool forNode(NodeId nodeid, std::function func) const; diff --git a/src/avalanche/peermanager.cpp b/src/avalanche/peermanager.cpp --- a/src/avalanche/peermanager.cpp +++ b/src/avalanche/peermanager.cpp @@ -14,7 +14,8 @@ namespace avalanche { bool PeerManager::addNode(NodeId nodeid, const Proof &proof, - const Delegation &delegation) { + const Delegation &delegation, + std::optional sigData) { auto it = fetchOrCreatePeer(proof); if (it == peers.end()) { return false; @@ -28,6 +29,11 @@ return false; } + // Check AVAHELLO signature if applicable. + if (sigData && !sigData->verify(pubkey)) { + return false; + } + auto nit = nodes.find(nodeid); if (nit == nodes.end()) { if (!nodes.emplace(nodeid, peerid, std::move(pubkey)).second) { diff --git a/src/avalanche/processor.h b/src/avalanche/processor.h --- a/src/avalanche/processor.h +++ b/src/avalanche/processor.h @@ -289,8 +289,12 @@ bool registerVotes(NodeId nodeid, const Response &response, std::vector &updates); + // Add node with RPC bool addNode(NodeId nodeid, const Proof &proof, const Delegation &delegation); + // Add node with peer discovery + bool addNode(CNode *pfrom, const Proof &proof, const Delegation &delegation, + const SchnorrSig avaHelloSig); bool forNode(NodeId nodeid, std::function func) const; CPubKey getSessionPubKey() const; diff --git a/src/avalanche/processor.cpp b/src/avalanche/processor.cpp --- a/src/avalanche/processor.cpp +++ b/src/avalanche/processor.cpp @@ -445,7 +445,16 @@ bool Processor::addNode(NodeId nodeid, const Proof &proof, const Delegation &delegation) { LOCK(cs_peerManager); - return peerManager->addNode(nodeid, proof, delegation); + return peerManager->addNode(nodeid, proof, delegation, std::nullopt); +} + +bool Processor::addNode(CNode *pfrom, const Proof &proof, + const Delegation &delegation, + const SchnorrSig avaHelloSig) { + LOCK(cs_peerManager); + auto sigData = std::make_optional( + buildRemoteSighash(pfrom), avaHelloSig); + return peerManager->addNode(pfrom->GetId(), proof, delegation, sigData); } bool Processor::forNode(NodeId nodeid, diff --git a/src/avalanche/test/peermanager_tests.cpp b/src/avalanche/test/peermanager_tests.cpp --- a/src/avalanche/test/peermanager_tests.cpp +++ b/src/avalanche/test/peermanager_tests.cpp @@ -164,13 +164,13 @@ // One peer, we always return it. Proof proof0 = buildRandomProof(MIN_VALID_PROOF_SCORE); Delegation dg0 = DelegationBuilder(proof0).build(); - pm.addNode(node0, proof0, dg0); + pm.addNode(node0, proof0, dg0, std::nullopt); BOOST_CHECK_EQUAL(pm.selectNode(), node0); // Two peers, verify ratio. Proof proof1 = buildRandomProof(2 * MIN_VALID_PROOF_SCORE); Delegation dg1 = DelegationBuilder(proof1).build(); - pm.addNode(node1, proof1, dg1); + pm.addNode(node1, proof1, dg1, std::nullopt); std::unordered_map results = {}; for (int i = 0; i < 10000; i++) { @@ -184,7 +184,7 @@ // Three peers, verify ratio. Proof proof2 = buildRandomProof(MIN_VALID_PROOF_SCORE); Delegation dg2 = DelegationBuilder(proof2).build(); - pm.addNode(node2, proof2, dg2); + pm.addNode(node2, proof2, dg2, std::nullopt); results.clear(); for (int i = 0; i < 10000; i++) { @@ -206,8 +206,8 @@ for (int i = 0; i < 4; i++) { Proof p = buildRandomProof(100); peerids[i] = pm.getPeerId(p); - BOOST_CHECK( - pm.addNode(InsecureRand32(), p, DelegationBuilder(p).build())); + BOOST_CHECK(pm.addNode(InsecureRand32(), p, + DelegationBuilder(p).build(), std::nullopt)); } BOOST_CHECK_EQUAL(pm.getSlotCount(), 400); @@ -239,8 +239,8 @@ for (int i = 0; i < 4; i++) { Proof p = buildRandomProof(100); peerids[i + 4] = pm.getPeerId(p); - BOOST_CHECK( - pm.addNode(InsecureRand32(), p, DelegationBuilder(p).build())); + BOOST_CHECK(pm.addNode(InsecureRand32(), p, + DelegationBuilder(p).build(), std::nullopt)); } BOOST_CHECK_EQUAL(pm.getSlotCount(), 700); @@ -282,8 +282,8 @@ for (int i = 0; i < 4; i++) { Proof p = buildRandomProof(100); peerids[i] = pm.getPeerId(p); - BOOST_CHECK( - pm.addNode(InsecureRand32(), p, DelegationBuilder(p).build())); + BOOST_CHECK(pm.addNode(InsecureRand32(), p, + DelegationBuilder(p).build(), std::nullopt)); } // Remove all peers. @@ -314,7 +314,7 @@ // Add 4 nodes. for (int i = 0; i < 4; i++) { - BOOST_CHECK(pm.addNode(i, proof, dg)); + BOOST_CHECK(pm.addNode(i, proof, dg, std::nullopt)); } for (int i = 0; i < 100; i++) { @@ -349,7 +349,7 @@ // as chances of being picked are 1 in 10 million. Proof altproof = buildRandomProof(MIN_VALID_PROOF_SCORE); Delegation altdg = DelegationBuilder(altproof).build(); - BOOST_CHECK(pm.addNode(3, altproof, altdg)); + BOOST_CHECK(pm.addNode(3, altproof, altdg, std::nullopt)); int node3selected = 0; for (int i = 0; i < 100; i++) { @@ -438,4 +438,37 @@ NO_PEER); } +BOOST_AUTO_TEST_CASE(addnode_with_signature) { + CKey proofMaster; + proofMaster.MakeNewKey(true); + + // Create one peer. + Proof proof = buildRandomProof(1000, proofMaster.GetPubKey()); + Delegation dg = DelegationBuilder(proof).build(); + + CHashWriter ss(SER_GETHASH, 0); + ss << "very message"; + const uint256 sighash = ss.GetHash(); + SchnorrSig signature; + proofMaster.SignSchnorr(sighash, signature); + + { + // Add a node using correct sighash & signature + auto sigData = std::make_optional(sighash, signature); + avalanche::PeerManager pm; + BOOST_CHECK(pm.addNode(0, proof, dg, sigData)); + } + + { + // Fail to add node using mismatching sighash & signature + CHashWriter ss2(SER_GETHASH, 0); + ss2 << "much message"; + const uint256 wrongSighash = ss.GetHash(); + auto sigData = + std::make_optional(wrongSighash, signature); + avalanche::PeerManager pm; + BOOST_CHECK(!pm.addNode(0, proof, dg, sigData)); + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/avalanche/test/processor_tests.cpp b/src/avalanche/test/processor_tests.cpp --- a/src/avalanche/test/processor_tests.cpp +++ b/src/avalanche/test/processor_tests.cpp @@ -141,7 +141,7 @@ std::array nodes; for (CNode *&n : nodes) { n = ConnectNode(NODE_AVALANCHE); - BOOST_CHECK(pm.addNode(n->GetId(), proof, dg)); + BOOST_CHECK(pm.addNode(n->GetId(), proof, dg, std::nullopt)); } return nodes; @@ -750,7 +750,7 @@ std::array nodes; for (auto &n : nodes) { n = ConnectNode(NODE_AVALANCHE); - BOOST_CHECK(pm.addNode(n->GetId(), proof, dg)); + BOOST_CHECK(pm.addNode(n->GetId(), proof, dg, std::nullopt)); } // Add a block to poll