From 47f51a92fe50fc587725af3b23d0a99bc30d4faf Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 13 Jan 2016 01:14:00 +0100 Subject: [PATCH 1/5] libp2p: implement operator== for NodeIPEndpoint --- libp2p/Common.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libp2p/Common.h b/libp2p/Common.h index 6d0239499..7e7c4c2bc 100644 --- a/libp2p/Common.h +++ b/libp2p/Common.h @@ -194,6 +194,13 @@ class NodeIPEndpoint operator bool() const { return !address.is_unspecified() && udpPort > 0 && tcpPort > 0; } bool isAllowed() const { return NodeIPEndpoint::test_allowLocal ? !address.is_unspecified() : isPublicAddress(address); } + + bool operator==(NodeIPEndpoint const& _cmp) const { + return address == _cmp.address && udpPort == _cmp.udpPort && tcpPort == _cmp.tcpPort; + } + bool operator!=(NodeIPEndpoint const& _cmp) const { + return !operator==(_cmp); + } void streamRLP(RLPStream& _s, RLPAppend _append = StreamList) const; void interpretRLP(RLP const& _r); From 12246f67ad150381086ad29882b94d41f5cb09f4 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 14 Jan 2016 13:24:19 +0100 Subject: [PATCH 2/5] libp2p: add alternative Host constructor that allows setting the alias --- libp2p/Host.cpp | 11 ++++++++--- libp2p/Host.h | 8 ++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index abfde91aa..6dda552f5 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -98,20 +98,25 @@ bytes ReputationManager::data(Session const& _s, std::string const& _sub) const return bytes(); } -Host::Host(std::string const& _clientVersion, NetworkPreferences const& _n, bytesConstRef _restoreNetwork): +Host::Host(string const& _clientVersion, KeyPair const& _alias, NetworkPreferences const& _n): Worker("p2p", 0), - m_restoreNetwork(_restoreNetwork.toBytes()), m_clientVersion(_clientVersion), m_netPrefs(_n), m_ifAddresses(Network::getInterfaceAddresses()), m_ioService(2), m_tcp4Acceptor(m_ioService), - m_alias(networkAlias(_restoreNetwork)), + m_alias(_alias), m_lastPing(chrono::steady_clock::time_point::min()) { clog(NetNote) << "Id:" << id(); } +Host::Host(string const& _clientVersion, NetworkPreferences const& _n, bytesConstRef _restoreNetwork): + Host(_clientVersion, networkAlias(_restoreNetwork), _n) +{ + m_restoreNetwork = _restoreNetwork.toBytes(); +} + Host::~Host() { stop(); diff --git a/libp2p/Host.h b/libp2p/Host.h index d1bf50ec0..0539b1e0b 100644 --- a/libp2p/Host.h +++ b/libp2p/Host.h @@ -142,6 +142,14 @@ class Host: public Worker bytesConstRef _restoreNetwork = bytesConstRef() ); + /// Alternative constructor that allows providing the node key directly + /// without restoring the network. + Host( + std::string const& _clientVersion, + KeyPair const& _alias, + NetworkPreferences const& _n = NetworkPreferences() + ); + /// Will block on network process events. virtual ~Host(); From 6c689ff9831811650af2876ae5f2f22abfe6d824 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 12 Feb 2016 00:28:37 +0100 Subject: [PATCH 3/5] libdevcrypto: add support for ECIES authenticated data --- libdevcrypto/Common.cpp | 14 ++++++++++++-- libdevcrypto/Common.h | 8 ++++++++ libdevcrypto/CryptoPP.cpp | 13 +++++++++++++ libdevcrypto/CryptoPP.h | 11 ++++++++++- test/libdevcrypto/crypto.cpp | 21 +++++++++++++++++++++ 5 files changed, 64 insertions(+), 3 deletions(-) diff --git a/libdevcrypto/Common.cpp b/libdevcrypto/Common.cpp index d6d128c7c..63b0a2232 100644 --- a/libdevcrypto/Common.cpp +++ b/libdevcrypto/Common.cpp @@ -124,16 +124,26 @@ bool dev::decrypt(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext) } void dev::encryptECIES(Public const& _k, bytesConstRef _plain, bytes& o_cipher) +{ + encryptECIES(_k, bytesConstRef(), _plain, o_cipher); +} + +void dev::encryptECIES(Public const& _k, bytesConstRef _sharedMacData, bytesConstRef _plain, bytes& o_cipher) { bytes io = _plain.toBytes(); - Secp256k1PP::get()->encryptECIES(_k, io); + Secp256k1PP::get()->encryptECIES(_k, _sharedMacData, io); o_cipher = std::move(io); } bool dev::decryptECIES(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext) +{ + return decryptECIES(_k, bytesConstRef(), _cipher, o_plaintext); +} + +bool dev::decryptECIES(Secret const& _k, bytesConstRef _sharedMacData, bytesConstRef _cipher, bytes& o_plaintext) { bytes io = _cipher.toBytes(); - if (!Secp256k1PP::get()->decryptECIES(_k, io)) + if (!Secp256k1PP::get()->decryptECIES(_k, _sharedMacData, io)) return false; o_plaintext = std::move(io); return true; diff --git a/libdevcrypto/Common.h b/libdevcrypto/Common.h index 22460e940..1e2f4061b 100644 --- a/libdevcrypto/Common.h +++ b/libdevcrypto/Common.h @@ -105,9 +105,17 @@ bool decryptSym(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext); /// Encrypt payload using ECIES standard with AES128-CTR. void encryptECIES(Public const& _k, bytesConstRef _plain, bytes& o_cipher); +/// Encrypt payload using ECIES standard with AES128-CTR. +/// @a _sharedMacData is shared authenticated data. +void encryptECIES(Public const& _k, bytesConstRef _sharedMacData, bytesConstRef _plain, bytes& o_cipher); + /// Decrypt payload using ECIES standard with AES128-CTR. bool decryptECIES(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext); +/// Decrypt payload using ECIES standard with AES128-CTR. +/// @a _sharedMacData is shared authenticated data. +bool decryptECIES(Secret const& _k, bytesConstRef _sharedMacData, bytesConstRef _cipher, bytes& o_plaintext); + /// Encrypts payload with random IV/ctr using AES128-CTR. std::pair encryptSymNoAuth(SecureFixedHash<16> const& _k, bytesConstRef _plain); diff --git a/libdevcrypto/CryptoPP.cpp b/libdevcrypto/CryptoPP.cpp index d927420d5..9299fc2ca 100644 --- a/libdevcrypto/CryptoPP.cpp +++ b/libdevcrypto/CryptoPP.cpp @@ -66,6 +66,11 @@ bytes Secp256k1PP::eciesKDF(Secret const& _z, bytes _s1, unsigned kdByteLen) } void Secp256k1PP::encryptECIES(Public const& _k, bytes& io_cipher) +{ + encryptECIES(_k, bytesConstRef(), io_cipher); +} + +void Secp256k1PP::encryptECIES(Public const& _k, bytesConstRef _sharedMacData, bytes& io_cipher) { // interop w/go ecies implementation auto r = KeyPair::create(); @@ -93,6 +98,7 @@ void Secp256k1PP::encryptECIES(Public const& _k, bytes& io_cipher) CryptoPP::HMAC hmacctx(mKey.data(), mKey.size()); bytesConstRef cipherWithIV = bytesRef(&msg).cropped(1 + Public::size, h128::size + cipherText.size()); hmacctx.Update(cipherWithIV.data(), cipherWithIV.size()); + hmacctx.Update(_sharedMacData.data(), _sharedMacData.size()); hmacctx.Final(msg.data() + 1 + Public::size + cipherWithIV.size()); io_cipher.resize(msg.size()); @@ -101,6 +107,12 @@ void Secp256k1PP::encryptECIES(Public const& _k, bytes& io_cipher) bool Secp256k1PP::decryptECIES(Secret const& _k, bytes& io_text) { + return decryptECIES(_k, bytesConstRef(), io_text); +} + +bool Secp256k1PP::decryptECIES(Secret const& _k, bytesConstRef _sharedMacData, bytes& io_text) +{ + // interop w/go ecies implementation // io_cipher[0] must be 2, 3, or 4, else invalidpublickey @@ -133,6 +145,7 @@ bool Secp256k1PP::decryptECIES(Secret const& _k, bytes& io_text) // verify tag CryptoPP::HMAC hmacctx(mKey.data(), mKey.size()); hmacctx.Update(cipherWithIV.data(), cipherWithIV.size()); + hmacctx.Update(_sharedMacData.data(), _sharedMacData.size()); h256 mac; hmacctx.Final(mac.data()); for (unsigned i = 0; i < h256::size; i++) diff --git a/libdevcrypto/CryptoPP.h b/libdevcrypto/CryptoPP.h index bc3d0a95f..b6b973863 100644 --- a/libdevcrypto/CryptoPP.h +++ b/libdevcrypto/CryptoPP.h @@ -63,6 +63,9 @@ inline ECP::Point publicToPoint(Public const& _p) { Integer x(_p.data(), 32); In inline Integer secretToExponent(Secret const& _s) { return std::move(Integer(_s.data(), Secret::size)); } +/// Amount of bytes added when encrypting with encryptECIES. +static const unsigned c_eciesOverhead = 113; + /** * CryptoPP secp256k1 algorithms. * @todo Collect ECIES methods into class. @@ -82,10 +85,16 @@ class Secp256k1PP /// Encrypts text (replace input). (ECIES w/AES128-CTR-SHA256) void encryptECIES(Public const& _k, bytes& io_cipher); - + + /// Encrypts text (replace input). (ECIES w/AES128-CTR-SHA256) + void encryptECIES(Public const& _k, bytesConstRef _sharedMacData, bytes& io_cipher); + /// Decrypts text (replace input). (ECIES w/AES128-CTR-SHA256) bool decryptECIES(Secret const& _k, bytes& io_text); + /// Decrypts text (replace input). (ECIES w/AES128-CTR-SHA256) + bool decryptECIES(Secret const& _k, bytesConstRef _sharedMacData, bytes& io_text); + /// Key derivation function used by encryptECIES and decryptECIES. bytes eciesKDF(Secret const& _z, bytes _s1, unsigned kdBitLen = 256); diff --git a/test/libdevcrypto/crypto.cpp b/test/libdevcrypto/crypto.cpp index d49dff381..073a4e449 100644 --- a/test/libdevcrypto/crypto.cpp +++ b/test/libdevcrypto/crypto.cpp @@ -332,6 +332,27 @@ BOOST_AUTO_TEST_CASE(ecies_standard) BOOST_REQUIRE(bytesConstRef(&b).cropped(0, original.size()).toBytes() == asBytes(original)); } +BOOST_AUTO_TEST_CASE(ecies_sharedMacData) +{ + KeyPair k = KeyPair::create(); + + string message("Now is the time for all good persons to come to the aid of humanity."); + string original = message; + bytes b = asBytes(message); + + bytesConstRef shared("shared MAC data"); + bytesConstRef wrongShared("wrong shared MAC data"); + + s_secp256k1->encryptECIES(k.pub(), shared, b); + BOOST_REQUIRE(b != asBytes(original)); + BOOST_REQUIRE(b.size() > 0 && b[0] == 0x04); + + BOOST_REQUIRE(!s_secp256k1->decryptECIES(k.sec(), wrongShared, b)); + + s_secp256k1->decryptECIES(k.sec(), shared, b); + BOOST_REQUIRE(bytesConstRef(&b).cropped(0, original.size()).toBytes() == asBytes(original)); +} + BOOST_AUTO_TEST_CASE(ecies_eckeypair) { KeyPair k = KeyPair::create(); From 71e18b42650a055dbffa0d62746bb4a3e2bdb5bd Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 18 Jan 2016 15:14:34 +0100 Subject: [PATCH 4/5] libp2p: implement EIP-8 support for discovery This commit implements relaxed packet decoding as required by EIP-8. It also removes support for discovery v3 ping packets. --- libp2p/NodeTable.cpp | 188 ++++++++++++++++-------------------------- libp2p/NodeTable.h | 152 ++++++++++++++++++++++------------ libp2p/UDP.h | 11 +-- test/libp2p/eip-8.cpp | 117 ++++++++++++++++++++++++++ test/libp2p/net.cpp | 19 +---- 5 files changed, 293 insertions(+), 194 deletions(-) create mode 100644 test/libp2p/eip-8.cpp diff --git a/libp2p/NodeTable.cpp b/libp2p/NodeTable.cpp index d185847b4..58e6302b9 100644 --- a/libp2p/NodeTable.cpp +++ b/libp2p/NodeTable.cpp @@ -396,49 +396,27 @@ NodeTable::NodeBucket& NodeTable::bucket_UNSAFE(NodeEntry const* _n) void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytesConstRef _packet) { - // h256 + Signature + type + RLP (smallest possible packet is empty neighbours packet which is 3 bytes) - if (_packet.size() < h256::size + Signature::size + 1 + 3) - { - clog(NodeTableTriviaSummary) << "Invalid message size from " << _from.address().to_string() << ":" << _from.port(); - return; - } - - bytesConstRef hashedBytes(_packet.cropped(h256::size, _packet.size() - h256::size)); - h256 hashSigned(sha3(hashedBytes)); - if (!_packet.cropped(0, h256::size).contentsEqual(hashSigned.asBytes())) - { - clog(NodeTableTriviaSummary) << "Invalid message hash from " << _from.address().to_string() << ":" << _from.port(); - return; - } - - bytesConstRef signedBytes(hashedBytes.cropped(Signature::size, hashedBytes.size() - Signature::size)); - - // todo: verify sig via known-nodeid and MDC - - bytesConstRef sigBytes(_packet.cropped(h256::size, Signature::size)); - Public nodeid(dev::recover(*(Signature const*)sigBytes.data(), sha3(signedBytes))); - if (!nodeid) - { - clog(NodeTableTriviaSummary) << "Invalid message signature from " << _from.address().to_string() << ":" << _from.port(); - return; - } - - unsigned packetType = signedBytes[0]; - bytesConstRef rlpBytes(_packet.cropped(h256::size + Signature::size + 1)); try { - RLP rlp(rlpBytes); - switch (packetType) + unique_ptr packet = DiscoveryDatagram::interpretUDP(_from, _packet); + if (!packet) + return; + if (packet->isExpired()) + { + clog(NodeTableWarn) << "Invalid packet (timestamp in the past) from " << _from.address().to_string() << ":" << _from.port(); + return; + } + + switch (packet->packetType()) { case Pong::type: { - Pong in = Pong::fromBytesConstRef(_from, rlpBytes); - + auto in = dynamic_cast(*packet); // whenever a pong is received, check if it's in m_evictions bool found = false; EvictionTimeout evictionEntry; DEV_GUARDED(x_evictions) for (auto it = m_evictions.begin(); it != m_evictions.end(); ++it) - if (it->first.first == nodeid && it->first.second > std::chrono::steady_clock::now()) + if (it->first.first == in.sourceid && it->first.second > std::chrono::steady_clock::now()) { found = true; evictionEntry = *it; @@ -455,7 +433,7 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes else { // if not, check if it's known/pending or a pubk discovery ping - if (auto n = nodeEntry(nodeid)) + if (auto n = nodeEntry(in.sourceid)) n->pending = false; else { @@ -465,8 +443,8 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes return; // unsolicited pong; don't note node as active m_pubkDiscoverPings.erase(_from.address()); } - if (!haveNode(nodeid)) - addNode(Node(nodeid, NodeIPEndpoint(_from.address(), _from.port(), _from.port()))); + if (!haveNode(in.sourceid)) + addNode(Node(in.sourceid, NodeIPEndpoint(_from.address(), _from.port(), _from.port()))); } } @@ -478,31 +456,30 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes m_node.endpoint.udpPort = in.destination.udpPort; } - clog(NodeTableConnect) << "PONG from " << nodeid << _from; + clog(NodeTableConnect) << "PONG from " << in.sourceid << _from; break; } case Neighbours::type: { + auto in = dynamic_cast(*packet); bool expected = false; auto now = chrono::steady_clock::now(); DEV_GUARDED(x_findNodeTimeout) m_findNodeTimeout.remove_if([&](NodeIdTimePoint const& t) { - if (t.first == nodeid && now - t.second < c_reqTimeout) + if (t.first == in.sourceid && now - t.second < c_reqTimeout) expected = true; - else if (t.first == nodeid) + else if (t.first == in.sourceid) return true; return false; }); - if (!expected) { clog(NetConnect) << "Dropping unsolicited neighbours packet from " << _from.address(); break; } - - Neighbours in = Neighbours::fromBytesConstRef(_from, rlpBytes); + for (auto n: in.neighbours) addNode(Node(n.node, n.endpoint)); break; @@ -510,13 +487,7 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes case FindNode::type: { - FindNode in = FindNode::fromBytesConstRef(_from, rlpBytes); - if (RLPXDatagramFace::secondsSinceEpoch() > in.ts) - { - clog(NodeTableTriviaSummary) << "Received expired FindNode from " << _from.address().to_string() << ":" << _from.port(); - return; - } - + auto in = dynamic_cast(*packet); vector> nearest = nearestNodeEntries(in.target); static unsigned const nlimit = (m_socketPointer->maxDatagramSize - 109) / 90; for (unsigned offset = 0; offset < nearest.size(); offset += nlimit) @@ -532,42 +503,24 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes case PingNode::type: { - PingNode in = PingNode::fromBytesConstRef(_from, rlpBytes); - if (in.version < dev::p2p::c_protocolVersion) - { - if (in.version == 3) - { - compat::Pong p(in.source); - p.echo = sha3(rlpBytes); - p.sign(m_secret); - m_socketPointer->send(p); - } - else - return; - } - - if (RLPXDatagramFace::secondsSinceEpoch() > in.ts) - { - clog(NodeTableTriviaSummary) << "Received expired PingNode from " << _from.address().to_string() << ":" << _from.port(); - return; - } - + auto in = dynamic_cast(*packet); in.source.address = _from.address(); in.source.udpPort = _from.port(); - addNode(Node(nodeid, in.source)); + addNode(Node(in.sourceid, in.source)); + Pong p(in.source); - p.echo = sha3(rlpBytes); + p.echo = sha3(in.echo); p.sign(m_secret); m_socketPointer->send(p); break; } - - default: - clog(NodeTableWarn) << "Invalid message, " << hex << packetType << ", received from " << _from.address().to_string() << ":" << dec << _from.port(); - return; } - noteActiveNode(nodeid, _from); + noteActiveNode(packet->sourceid, _from); + } + catch (std::exception const& _e) + { + clog(NodeTableWarn) << "Exception processing message from " << _from.address().to_string() << ":" << _from.port() << ": " << _e.what(); } catch (...) { @@ -624,47 +577,50 @@ void NodeTable::doDiscovery() }); } -void PingNode::streamRLP(RLPStream& _s) const -{ - _s.appendList(4); - _s << dev::p2p::c_protocolVersion; - source.streamRLP(_s); - destination.streamRLP(_s); - _s << ts; -} - -void PingNode::interpretRLP(bytesConstRef _bytes) +unique_ptr DiscoveryDatagram::interpretUDP(bi::udp::endpoint const& _from, bytesConstRef _packet) { - RLP r(_bytes); - if (r.itemCountStrict() == 4 && r[0].isInt() && r[0].toInt(RLP::Strict) == dev::p2p::c_protocolVersion) + unique_ptr decoded; + // h256 + Signature + type + RLP (smallest possible packet is empty neighbours packet which is 3 bytes) + if (_packet.size() < h256::size + Signature::size + 1 + 3) { - version = dev::p2p::c_protocolVersion; - source.interpretRLP(r[1]); - destination.interpretRLP(r[2]); - ts = r[3].toInt(RLP::Strict); + clog(NodeTableWarn) << "Invalid packet (too small) from " << _from.address().to_string() << ":" << _from.port(); + return decoded; } - else - version = r[0].toInt(RLP::Strict); -} - -void Pong::streamRLP(RLPStream& _s) const -{ - _s.appendList(3); - destination.streamRLP(_s); - _s << echo << ts; -} - -void Pong::interpretRLP(bytesConstRef _bytes) -{ - RLP r(_bytes); - destination.interpretRLP(r[0]); - echo = (h256)r[1]; - ts = r[2].toInt(); -} + bytesConstRef hashedBytes(_packet.cropped(h256::size, _packet.size() - h256::size)); + bytesConstRef signedBytes(hashedBytes.cropped(Signature::size, hashedBytes.size() - Signature::size)); + bytesConstRef signatureBytes(_packet.cropped(h256::size, Signature::size)); + bytesConstRef bodyBytes(_packet.cropped(h256::size + Signature::size + 1)); -void compat::Pong::interpretRLP(bytesConstRef _bytes) -{ - RLP r(_bytes); - echo = (h256)r[0]; - ts = r[1].toInt(); + h256 echo(sha3(hashedBytes)); + if (!_packet.cropped(0, h256::size).contentsEqual(echo.asBytes())) + { + clog(NodeTableWarn) << "Invalid packet (bad hash) from " << _from.address().to_string() << ":" << _from.port(); + return decoded; + } + Public sourceid(dev::recover(*(Signature const*)signatureBytes.data(), sha3(signedBytes))); + if (!sourceid) + { + clog(NodeTableWarn) << "Invalid packet (bad signature) from " << _from.address().to_string() << ":" << _from.port(); + return decoded; + } + switch (signedBytes[0]) + { + case PingNode::type: + decoded.reset(new PingNode(_from, sourceid, echo)); + break; + case Pong::type: + decoded.reset(new Pong(_from, sourceid, echo)); + break; + case FindNode::type: + decoded.reset(new FindNode(_from, sourceid, echo)); + break; + case Neighbours::type: + decoded.reset(new Neighbours(_from, sourceid, echo)); + break; + default: + clog(NodeTableWarn) << "Invalid packet (unknown packet type) from " << _from.address().to_string() << ":" << _from.port(); + return decoded; + } + decoded->interpretRLP(bodyBytes); + return decoded; } diff --git a/libp2p/NodeTable.h b/libp2p/NodeTable.h index ffce243f2..183ac984c 100644 --- a/libp2p/NodeTable.h +++ b/libp2p/NodeTable.h @@ -276,7 +276,26 @@ inline std::ostream& operator<<(std::ostream& _out, NodeTable const& _nodeTable) return _out; } -struct InvalidRLP: public Exception {}; +struct DiscoveryDatagram: public RLPXDatagramFace +{ + /// Constructor used for sending. + DiscoveryDatagram(bi::udp::endpoint const& _to): RLPXDatagramFace(_to), ts(futureFromEpoch(std::chrono::seconds(60))) {} + + /// Constructor used for parsing inbound packets. + DiscoveryDatagram(bi::udp::endpoint const& _from, NodeID const& _fromid, h256 const& _echo): RLPXDatagramFace(_from), sourceid(_fromid), echo(_echo) {} + + // These two are set for inbound packets only. + NodeID sourceid; // sender public key (from signature) + h256 echo; // hash of encoded packet, for reply tracking + + // All discovery packets carry a timestamp, which must be greater + // than the current local time. This prevents replay attacks. + uint32_t ts = 0; + bool isExpired() const { return secondsSinceEpoch() > ts; } + + /// Decodes UDP packets. + static std::unique_ptr interpretUDP(bi::udp::endpoint const& _from, bytesConstRef _packet); +}; /** * Ping packet: Sent to check if node is alive. @@ -287,41 +306,64 @@ struct InvalidRLP: public Exception {}; * If the pinged node doesn't respond, then it is removed and the new * node is inserted. */ -struct PingNode: RLPXDatagram +struct PingNode: DiscoveryDatagram { - /// Constructor used for sending PingNode. - PingNode(NodeIPEndpoint _src, NodeIPEndpoint _dest): RLPXDatagram(_dest), source(_src), destination(_dest), ts(futureFromEpoch(std::chrono::seconds(60))) {} - - /// Constructor used to create empty PingNode for parsing inbound packets. - PingNode(bi::udp::endpoint _ep): RLPXDatagram(_ep), source(UnspecifiedNodeIPEndpoint), destination(UnspecifiedNodeIPEndpoint) {} + using DiscoveryDatagram::DiscoveryDatagram; + PingNode(NodeIPEndpoint const& _src, NodeIPEndpoint const& _dest): DiscoveryDatagram(_dest), source(_src), destination(_dest) {} + PingNode(bi::udp::endpoint const& _from, NodeID const& _fromid, h256 const& _echo): DiscoveryDatagram(_from, _fromid, _echo) {} static const uint8_t type = 1; + uint8_t packetType() const { return type; } unsigned version = 0; NodeIPEndpoint source; NodeIPEndpoint destination; - uint32_t ts = 0; - void streamRLP(RLPStream& _s) const override; - void interpretRLP(bytesConstRef _bytes) override; + void streamRLP(RLPStream& _s) const + { + _s.appendList(4); + _s << dev::p2p::c_protocolVersion; + source.streamRLP(_s); + destination.streamRLP(_s); + _s << ts; + } + void interpretRLP(bytesConstRef _bytes) + { + RLP r(_bytes, RLP::AllowNonCanon|RLP::ThrowOnFail); + version = r[0].toInt(); + source.interpretRLP(r[1]); + destination.interpretRLP(r[2]); + ts = r[3].toInt(); + } }; /** * Pong packet: Sent in response to ping */ -struct Pong: RLPXDatagram +struct Pong: DiscoveryDatagram { - Pong(bi::udp::endpoint const& _ep): RLPXDatagram(_ep), destination(UnspecifiedNodeIPEndpoint) {} - Pong(NodeIPEndpoint const& _dest): RLPXDatagram((bi::udp::endpoint)_dest), destination(_dest), ts(futureFromEpoch(std::chrono::seconds(60))) {} + Pong(NodeIPEndpoint const& _dest): DiscoveryDatagram((bi::udp::endpoint)_dest), destination(_dest) {} + Pong(bi::udp::endpoint const& _from, NodeID const& _fromid, h256 const& _echo): DiscoveryDatagram(_from, _fromid, _echo) {} static const uint8_t type = 2; + uint8_t packetType() const { return type; } NodeIPEndpoint destination; - h256 echo; ///< MCD of PingNode - uint32_t ts = 0; - void streamRLP(RLPStream& _s) const; - void interpretRLP(bytesConstRef _bytes); + void streamRLP(RLPStream& _s) const + { + _s.appendList(3); + destination.streamRLP(_s); + _s << echo; + _s << ts; + } + void interpretRLP(bytesConstRef _bytes) + { + RLP r(_bytes, RLP::AllowNonCanon|RLP::ThrowOnFail); + destination.interpretRLP(r[0]); + echo = (h256)r[1]; + ts = r[2].toInt(); + } }; /** @@ -336,25 +378,42 @@ struct Pong: RLPXDatagram * target: NodeID of node. The responding node will send back nodes closest to the target. * */ -struct FindNode: RLPXDatagram +struct FindNode: DiscoveryDatagram { - FindNode(bi::udp::endpoint _ep): RLPXDatagram(_ep) {} - FindNode(bi::udp::endpoint _ep, NodeID _target): RLPXDatagram(_ep), target(_target), ts(futureFromEpoch(std::chrono::seconds(60))) {} + FindNode(bi::udp::endpoint _to, h512 _target): DiscoveryDatagram(_to), target(_target) {} + FindNode(bi::udp::endpoint const& _from, NodeID const& _fromid, h256 const& _echo): DiscoveryDatagram(_from, _fromid, _echo) {} static const uint8_t type = 3; + uint8_t packetType() const { return type; } h512 target; - uint32_t ts = 0; - void streamRLP(RLPStream& _s) const { _s.appendList(2); _s << target << ts; } - void interpretRLP(bytesConstRef _bytes) { RLP r(_bytes); target = r[0].toHash(); ts = r[1].toInt(); } + void streamRLP(RLPStream& _s) const + { + _s.appendList(2); _s << target << ts; + } + void interpretRLP(bytesConstRef _bytes) + { + RLP r(_bytes, RLP::AllowNonCanon|RLP::ThrowOnFail); + target = r[0].toHash(); + ts = r[1].toInt(); + } }; /** * Node Packet: One or more node packets are sent in response to FindNode. */ -struct Neighbours: RLPXDatagram +struct Neighbours: DiscoveryDatagram { + Neighbours(bi::udp::endpoint _to, std::vector> const& _nearest, unsigned _offset = 0, unsigned _limit = 0): DiscoveryDatagram(_to) + { + auto limit = _limit ? std::min(_nearest.size(), (size_t)(_offset + _limit)) : _nearest.size(); + for (auto i = _offset; i < limit; i++) + neighbours.push_back(Neighbour(*_nearest[i])); + } + Neighbours(bi::udp::endpoint const& _to): DiscoveryDatagram(_to) {} + Neighbours(bi::udp::endpoint const& _from, NodeID const& _fromid, h256 const& _echo): DiscoveryDatagram(_from, _fromid, _echo) {} + struct Neighbour { Neighbour(Node const& _node): endpoint(_node.endpoint), node(_node.id) {} @@ -364,38 +423,27 @@ struct Neighbours: RLPXDatagram void streamRLP(RLPStream& _s) const { _s.appendList(4); endpoint.streamRLP(_s, NodeIPEndpoint::StreamInline); _s << node; } }; - Neighbours(bi::udp::endpoint _ep): RLPXDatagram(_ep), ts(secondsSinceEpoch()) {} - Neighbours(bi::udp::endpoint _to, std::vector> const& _nearest, unsigned _offset = 0, unsigned _limit = 0): RLPXDatagram(_to), ts(futureFromEpoch(std::chrono::seconds(60))) - { - auto limit = _limit ? std::min(_nearest.size(), (size_t)(_offset + _limit)) : _nearest.size(); - for (auto i = _offset; i < limit; i++) - neighbours.push_back(Neighbour(*_nearest[i])); - } - static const uint8_t type = 4; + uint8_t packetType() const { return type; } + std::vector neighbours; - uint32_t ts = 0; - void streamRLP(RLPStream& _s) const { _s.appendList(2); _s.appendList(neighbours.size()); for (auto& n: neighbours) n.streamRLP(_s); _s << ts; } - void interpretRLP(bytesConstRef _bytes) { RLP r(_bytes); for (auto n: r[0]) neighbours.push_back(Neighbour(n)); ts = r[1].toInt(); } -}; - -namespace compat -{ - /** - * Pong packet [compatability]: Sent in response to ping - */ - struct Pong: RLPXDatagram + void streamRLP(RLPStream& _s) const { - Pong(bi::udp::endpoint const& _ep): RLPXDatagram(_ep) {} - Pong(NodeIPEndpoint const& _dest): RLPXDatagram((bi::udp::endpoint)_dest), ts(futureFromEpoch(std::chrono::seconds(60))) {} - static const uint8_t type = 2; - h256 echo; - uint32_t ts = 0; - void streamRLP(RLPStream& _s) const { _s.appendList(2); _s << echo << ts; } - void interpretRLP(bytesConstRef _bytes); - }; -} + _s.appendList(2); + _s.appendList(neighbours.size()); + for (auto const& n: neighbours) + n.streamRLP(_s); + _s << ts; + } + void interpretRLP(bytesConstRef _bytes) + { + RLP r(_bytes, RLP::AllowNonCanon|RLP::ThrowOnFail); + for (auto const& n: r[0]) + neighbours.emplace_back(n); + ts = r[1].toInt(); + } +}; struct NodeTableWarn: public LogChannel { static const char* name(); static const int verbosity = 0; }; struct NodeTableNote: public LogChannel { static const char* name(); static const int verbosity = 1; }; diff --git a/libp2p/UDP.h b/libp2p/UDP.h index e345ce07f..af464d0ea 100644 --- a/libp2p/UDP.h +++ b/libp2p/UDP.h @@ -69,22 +69,15 @@ struct RLPXDatagramFace: public UDPDatagram static uint32_t secondsSinceEpoch() { return static_cast(std::chrono::duration_cast((std::chrono::system_clock::now()).time_since_epoch()).count()); } static Public authenticate(bytesConstRef _sig, bytesConstRef _rlp); - virtual uint8_t packetType() = 0; RLPXDatagramFace(bi::udp::endpoint const& _ep): UDPDatagram(_ep) {} + virtual h256 sign(Secret const& _from); + virtual uint8_t packetType() const = 0; virtual void streamRLP(RLPStream&) const = 0; virtual void interpretRLP(bytesConstRef _bytes) = 0; }; -template -struct RLPXDatagram: public RLPXDatagramFace -{ - RLPXDatagram(bi::udp::endpoint const& _ep): RLPXDatagramFace(_ep) {} - static T fromBytesConstRef(bi::udp::endpoint const& _ep, bytesConstRef _bytes) { try { T t(_ep); t.interpretRLP(_bytes); return t; } catch(...) { return T{_ep}; } } - uint8_t packetType() { return T::type; } -}; - /** * @brief Interface which UDPSocket will implement. */ diff --git a/test/libp2p/eip-8.cpp b/test/libp2p/eip-8.cpp new file mode 100644 index 000000000..f9e75fe89 --- /dev/null +++ b/test/libp2p/eip-8.cpp @@ -0,0 +1,117 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file discovery.cpp + * @author Felix Lange + * @date 2016 + * Forward-compatibility tests checking EIP-8 compliance. + */ + +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::p2p; + +BOOST_AUTO_TEST_SUITE(discovery) + +BOOST_AUTO_TEST_CASE(test_discovery_eip8) +{ + bi::udp::endpoint ep; + bytes packet; + + packet = fromHex( + "e9614ccfd9fc3e74360018522d30e1419a143407ffcce748de3e22116b7e8dc92ff74788c0b6663a" + "aa3d67d641936511c8f8d6ad8698b820a7cf9e1be7155e9a241f556658c55428ec0563514365799a" + "4be2be5a685a80971ddcfa80cb422cdd0101ec04cb847f000001820cfa8215a8d790000000000000" + "000000000000000000018208ae820d058443b9a3550102" + ); + auto ping1 = dynamic_cast(*DiscoveryDatagram::interpretUDP(ep, bytesConstRef(&packet))); + BOOST_CHECK_EQUAL(ping1.version, 4); + BOOST_CHECK_EQUAL(ping1.ts, 1136239445); + BOOST_CHECK_EQUAL(ping1.source, NodeIPEndpoint(bi::address::from_string("127.0.0.1"), 3322, 5544)); + BOOST_CHECK_EQUAL(ping1.destination, NodeIPEndpoint(bi::address::from_string("::1"), 2222, 3333)); + + packet = fromHex( + "577be4349c4dd26768081f58de4c6f375a7a22f3f7adda654d1428637412c3d7fe917cadc56d4e5e" + "7ffae1dbe3efffb9849feb71b262de37977e7c7a44e677295680e9e38ab26bee2fcbae207fba3ff3" + "d74069a50b902a82c9903ed37cc993c50001f83e82022bd79020010db83c4d001500000000abcdef" + "12820cfa8215a8d79020010db885a308d313198a2e037073488208ae82823a8443b9a355c5010203" + "040531b9019afde696e582a78fa8d95ea13ce3297d4afb8ba6433e4154caa5ac6431af1b80ba7602" + "3fa4090c408f6b4bc3701562c031041d4702971d102c9ab7fa5eed4cd6bab8f7af956f7d565ee191" + "7084a95398b6a21eac920fe3dd1345ec0a7ef39367ee69ddf092cbfe5b93e5e568ebc491983c09c7" + "6d922dc3" + ); + auto ping2 = dynamic_cast(*DiscoveryDatagram::interpretUDP(ep, bytesConstRef(&packet))); + BOOST_CHECK_EQUAL(ping2.version, 555); + BOOST_CHECK_EQUAL(ping2.source, NodeIPEndpoint(bi::address::from_string("2001:db8:3c4d:15::abcd:ef12"), 3322, 5544)); + BOOST_CHECK_EQUAL(ping2.destination, NodeIPEndpoint(bi::address::from_string("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338)); + BOOST_CHECK_EQUAL(ping2.ts, 1136239445); + + packet = fromHex( + "09b2428d83348d27cdf7064ad9024f526cebc19e4958f0fdad87c15eb598dd61d08423e0bf66b206" + "9869e1724125f820d851c136684082774f870e614d95a2855d000f05d1648b2d5945470bc187c2d2" + "216fbe870f43ed0909009882e176a46b0102f846d79020010db885a308d313198a2e037073488208" + "ae82823aa0fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c9548443b9" + "a355c6010203c2040506a0c969a58f6f9095004c0177a6b47f451530cab38966a25cca5cb58f0555" + "42124e" + ); + auto pong = dynamic_cast(*DiscoveryDatagram::interpretUDP(ep, bytesConstRef(&packet))); + BOOST_CHECK_EQUAL(pong.destination, NodeIPEndpoint(bi::address::from_string("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338)); + BOOST_CHECK_EQUAL(pong.echo, h256("fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c954")); + BOOST_CHECK_EQUAL(pong.ts, 1136239445); + + packet = fromHex( + "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91" + "831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe" + "04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d" + "115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be0081290476" + "7bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260a" + "dd7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396" + ); + auto findnode = dynamic_cast(*DiscoveryDatagram::interpretUDP(ep, bytesConstRef(&packet))); + BOOST_CHECK_EQUAL(findnode.target, Public("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f")); + BOOST_CHECK_EQUAL(findnode.ts, 1136239445); + + packet = fromHex( + "c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8" + "d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1" + "b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db84031" + "55e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa8291" + "15d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422" + "cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e82" + "9f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05" + "820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2" + "d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d3" + "13198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811" + "197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73" + "8443b9a355010203b525a138aa34383fec3d2719a0" + ); + auto neighbours = dynamic_cast(*DiscoveryDatagram::interpretUDP(ep, bytesConstRef(&packet))); + BOOST_REQUIRE_EQUAL(neighbours.neighbours.size(), 4); + BOOST_CHECK_EQUAL(neighbours.neighbours[0].endpoint, NodeIPEndpoint(bi::address::from_string("99.33.22.55"), 4444, 4445)); + BOOST_CHECK_EQUAL(neighbours.neighbours[0].node, Public("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32")); + BOOST_CHECK_EQUAL(neighbours.neighbours[1].endpoint, NodeIPEndpoint(bi::address::from_string("1.2.3.4"), 1, 1)); + BOOST_CHECK_EQUAL(neighbours.neighbours[1].node, Public("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db")); + BOOST_CHECK_EQUAL(neighbours.neighbours[2].endpoint, NodeIPEndpoint(bi::address::from_string("2001:db8:3c4d:15::abcd:ef12"), 3333, 3333)); + BOOST_CHECK_EQUAL(neighbours.neighbours[2].node, Public("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac")); + BOOST_CHECK_EQUAL(neighbours.neighbours[3].endpoint, NodeIPEndpoint(bi::address::from_string("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 999, 1000)); + BOOST_CHECK_EQUAL(neighbours.neighbours[3].node, Public("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73")); + BOOST_CHECK_EQUAL(neighbours.ts, 1136239445); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libp2p/net.cpp b/test/libp2p/net.cpp index fba65d62e..e5735ab20 100644 --- a/test/libp2p/net.cpp +++ b/test/libp2p/net.cpp @@ -223,20 +223,6 @@ BOOST_AUTO_TEST_CASE(isIPAddressType) BOOST_REQUIRE(!isLocalHostAddress(privateAddress10)); } -BOOST_AUTO_TEST_CASE(v2PingNodePacket) -{ - if (test::Options::get().nonetwork) - return; - - // test old versino of pingNode packet w/new - RLPStream s; - s.appendList(3); s << "1.1.1.1" << 30303 << std::chrono::duration_cast((std::chrono::system_clock::now() + chrono::seconds(60)).time_since_epoch()).count(); - - PingNode p((bi::udp::endpoint())); - BOOST_REQUIRE_NO_THROW(p = PingNode::fromBytesConstRef(bi::udp::endpoint(), bytesConstRef(&s.out()))); - BOOST_REQUIRE(p.version == 0); -} - BOOST_AUTO_TEST_CASE(neighboursPacketLength) { if (test::Options::get().nonetwork) @@ -284,10 +270,9 @@ BOOST_AUTO_TEST_CASE(neighboursPacket) out.sign(k.sec()); bytesConstRef packet(out.data.data(), out.data.size()); - bytesConstRef rlpBytes(packet.cropped(h256::size + Signature::size + 1)); - Neighbours in = Neighbours::fromBytesConstRef(to, rlpBytes); + auto in = DiscoveryDatagram::interpretUDP(to, packet); int count = 0; - for (auto n: in.neighbours) + for (auto n: dynamic_cast(*in).neighbours) { BOOST_REQUIRE_EQUAL(testNodes[count].second, n.endpoint.udpPort); BOOST_REQUIRE_EQUAL(testNodes[count].first.pub(), n.node); From 4dff018eb6e29f386891a6645c39e6c776954434 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 13 Jan 2016 22:21:25 +0100 Subject: [PATCH 5/5] libp2p: implement EIP-8 RLPx handshake encoding --- libp2p/RLPxHandshake.cpp | 139 ++++++++++++++++++++---- libp2p/RLPxHandshake.h | 50 ++++++--- test/libp2p/eip-8.cpp | 229 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 378 insertions(+), 40 deletions(-) diff --git a/libp2p/RLPxHandshake.cpp b/libp2p/RLPxHandshake.cpp index 4c553110b..053d3c0cc 100644 --- a/libp2p/RLPxHandshake.cpp +++ b/libp2p/RLPxHandshake.cpp @@ -26,6 +26,7 @@ using namespace std; using namespace dev; using namespace dev::p2p; +using namespace dev::crypto; using namespace CryptoPP; void RLPXHandshake::writeAuth() @@ -64,6 +65,31 @@ void RLPXHandshake::writeAck() m_nonce.ref().copyTo(nonce); m_ack[m_ack.size() - 1] = 0x0; encryptECIES(m_remote, &m_ack, m_ackCipher); + + auto self(shared_from_this()); + ba::async_write(m_socket->ref(), ba::buffer(m_ackCipher), [this, self](boost::system::error_code ec, std::size_t) + { + transition(ec); + }); +} + +void RLPXHandshake::writeAckEIP8() +{ + clog(NetP2PConnect) << "p2p.connect.ingress sending EIP-8 ack to " << m_socket->remoteEndpoint(); + + RLPStream rlp; + rlp.appendList(3) + << m_ecdhe.pubkey() + << m_nonce + << c_rlpxVersion; + m_ack = rlp.out(); + int padAmount(rand()%100 + 100); + m_ack.resize(m_ack.size() + padAmount, 0); + + bytes prefix(2); + toBigEndian(m_ack.size() + c_eciesOverhead, prefix); + encryptECIES(m_remote, bytesConstRef(&prefix), &m_ack, m_ackCipher); + m_ackCipher.insert(m_ackCipher.begin(), prefix.begin(), prefix.end()); auto self(shared_from_this()); ba::async_write(m_socket->ref(), ba::buffer(m_ackCipher), [this, self](boost::system::error_code ec, std::size_t) @@ -72,9 +98,19 @@ void RLPXHandshake::writeAck() }); } +void RLPXHandshake::setAuthValues(Signature const& _sig, Public const& _remotePubk, h256 const& _remoteNonce, uint64_t _remoteVersion) +{ + _remotePubk.ref().copyTo(m_remote.ref()); + _remoteNonce.ref().copyTo(m_remoteNonce.ref()); + m_remoteVersion = _remoteVersion; + Secret sharedSecret; + crypto::ecdh::agree(m_host->m_alias.sec(), _remotePubk, sharedSecret); + m_remoteEphemeral = recover(_sig, sharedSecret.makeInsecure() ^ _remoteNonce); +} + void RLPXHandshake::readAuth() { - clog(NetP2PConnect) << "p2p.connect.ingress recving auth from " << m_socket->remoteEndpoint(); + clog(NetP2PConnect) << "p2p.connect.ingress receiving auth from " << m_socket->remoteEndpoint(); m_authCipher.resize(307); auto self(shared_from_this()); ba::async_read(m_socket->ref(), ba::buffer(m_authCipher, 307), [this, self](boost::system::error_code ec, std::size_t) @@ -83,25 +119,46 @@ void RLPXHandshake::readAuth() transition(ec); else if (decryptECIES(m_host->m_alias.sec(), bytesConstRef(&m_authCipher), m_auth)) { - bytesConstRef sig(&m_auth[0], Signature::size); - bytesConstRef hepubk(&m_auth[Signature::size], h256::size); - bytesConstRef pubk(&m_auth[Signature::size + h256::size], Public::size); - bytesConstRef nonce(&m_auth[Signature::size + h256::size + Public::size], h256::size); - pubk.copyTo(m_remote.ref()); - nonce.copyTo(m_remoteNonce.ref()); - - Secret sharedSecret; - crypto::ecdh::agree(m_host->m_alias.sec(), m_remote, sharedSecret); - m_remoteEphemeral = recover(*(Signature*)sig.data(), sharedSecret.makeInsecure() ^ m_remoteNonce); + bytesConstRef data(&m_auth); + Signature sig(data.cropped(0, Signature::size)); + Public pubk(data.cropped(Signature::size + h256::size, Public::size)); + h256 nonce(data.cropped(Signature::size + h256::size + Public::size, h256::size)); + setAuthValues(sig, pubk, nonce, 4); + transition(); + } + else + readAuthEIP8(); + }); +} - if (sha3(m_remoteEphemeral) != *(h256*)hepubk.data()) - clog(NetP2PConnect) << "p2p.connect.ingress auth failed (invalid: hash mismatch) for" << m_socket->remoteEndpoint(); - +void RLPXHandshake::readAuthEIP8() +{ + assert(m_authCipher.size() == 307); + uint16_t size(m_authCipher[0]<<8 | m_authCipher[1]); + clog(NetP2PConnect) << "p2p.connect.ingress receiving " << size << "bytes EIP-8 auth from " << m_socket->remoteEndpoint(); + m_authCipher.resize((size_t)size + 2); + auto rest = ba::buffer(ba::buffer(m_authCipher) + 307); + auto self(shared_from_this()); + ba::async_read(m_socket->ref(), rest, [this, self](boost::system::error_code ec, std::size_t) + { + bytesConstRef ct(&m_authCipher); + if (ec) + transition(ec); + else if (decryptECIES(m_host->m_alias.sec(), ct.cropped(0, 2), ct.cropped(2), m_auth)) + { + RLP rlp(m_auth, RLP::ThrowOnFail | RLP::FailIfTooSmall); + setAuthValues( + rlp[0].toHash(), + rlp[1].toHash(), + rlp[2].toHash(), + rlp[3].toInt() + ); + m_nextState = AckAuthEIP8; transition(); } else { - clog(NetP2PConnect) << "p2p.connect.ingress recving auth decrypt failed for" << m_socket->remoteEndpoint(); + clog(NetP2PConnect) << "p2p.connect.ingress auth decrypt failed for" << m_socket->remoteEndpoint(); m_nextState = Error; transition(); } @@ -110,7 +167,7 @@ void RLPXHandshake::readAuth() void RLPXHandshake::readAck() { - clog(NetP2PConnect) << "p2p.connect.egress recving ack from " << m_socket->remoteEndpoint(); + clog(NetP2PConnect) << "p2p.connect.egress receiving ack from " << m_socket->remoteEndpoint(); m_ackCipher.resize(210); auto self(shared_from_this()); ba::async_read(m_socket->ref(), ba::buffer(m_ackCipher, 210), [this, self](boost::system::error_code ec, std::size_t) @@ -121,29 +178,61 @@ void RLPXHandshake::readAck() { bytesConstRef(&m_ack).cropped(0, Public::size).copyTo(m_remoteEphemeral.ref()); bytesConstRef(&m_ack).cropped(Public::size, h256::size).copyTo(m_remoteNonce.ref()); + m_remoteVersion = 4; transition(); } else + readAckEIP8(); + }); +} + +void RLPXHandshake::readAckEIP8() +{ + assert(m_ackCipher.size() == 210); + uint16_t size(m_ackCipher[0]<<8 | m_ackCipher[1]); + clog(NetP2PConnect) << "p2p.connect.egress receiving " << size << "bytes EIP-8 ack from " << m_socket->remoteEndpoint(); + m_ackCipher.resize((size_t)size + 2); + auto rest = ba::buffer(ba::buffer(m_ackCipher) + 210); + auto self(shared_from_this()); + ba::async_read(m_socket->ref(), rest, [this, self](boost::system::error_code ec, std::size_t) + { + bytesConstRef ct(&m_ackCipher); + if (ec) + transition(ec); + else if (decryptECIES(m_host->m_alias.sec(), ct.cropped(0, 2), ct.cropped(2), m_ack)) { - clog(NetP2PConnect) << "p2p.connect.egress recving ack decrypt failed for " << m_socket->remoteEndpoint(); + RLP rlp(m_ack, RLP::ThrowOnFail | RLP::FailIfTooSmall); + m_remoteEphemeral = rlp[0].toHash(); + m_remoteNonce = rlp[1].toHash(); + m_remoteVersion = rlp[2].toInt(); + transition(); + } + else + { + clog(NetP2PConnect) << "p2p.connect.egress ack decrypt failed for " << m_socket->remoteEndpoint(); m_nextState = Error; transition(); } }); } -void RLPXHandshake::error() +void RLPXHandshake::cancel() { + m_cancel = true; m_idleTimer.cancel(); - + m_socket->close(); + m_io.reset(); +} + +void RLPXHandshake::error() +{ auto connected = m_socket->isConnected(); if (connected && !m_socket->remoteEndpoint().address().is_unspecified()) clog(NetP2PConnect) << "Disconnecting " << m_socket->remoteEndpoint() << " (Handshake Failed)"; else clog(NetP2PConnect) << "Handshake Failed (Connection reset by peer)"; - m_socket->close(); - m_io.reset(); + cancel(); } void RLPXHandshake::transition(boost::system::error_code _ech) @@ -186,6 +275,14 @@ void RLPXHandshake::transition(boost::system::error_code _ech) else writeAck(); } + else if (m_nextState == AckAuthEIP8) + { + m_nextState = WriteHello; + if (m_originated) + readAck(); + else + writeAckEIP8(); + } else if (m_nextState == WriteHello) { m_nextState = ReadHello; diff --git a/libp2p/RLPxHandshake.h b/libp2p/RLPxHandshake.h index 337b259ce..d0695a6bb 100644 --- a/libp2p/RLPxHandshake.h +++ b/libp2p/RLPxHandshake.h @@ -36,6 +36,8 @@ namespace dev namespace p2p { +static const unsigned c_rlpxVersion = 4; + /** * @brief Setup inbound or outbound connection for communication over RLPXFrameCoder. * RLPx Spec: https://github.com/ethereum/devp2p/blob/master/rlpx.md#encrypted-handshake @@ -49,18 +51,7 @@ namespace p2p class RLPXHandshake: public std::enable_shared_from_this { friend class RLPXFrameCoder; - - /// Sequential states of handshake - enum State - { - Error = -1, - New, - AckAuth, - WriteHello, - ReadHello, - StartSession - }; - + public: /// Setup incoming connection. RLPXHandshake(Host* _host, std::shared_ptr const& _socket): m_host(_host), m_originated(false), m_socket(_socket), m_idleTimer(m_socket->ref().get_io_service()) { crypto::Nonce::get().ref().copyTo(m_nonce.ref()); } @@ -73,27 +64,51 @@ class RLPXHandshake: public std::enable_shared_from_this /// Start handshake. void start() { transition(); } - /// Cancels handshake preventing - void cancel() { m_cancel = true; m_socket->close(); } - + /// Aborts the handshake. + void cancel(); + protected: + /// Sequential states of handshake + enum State + { + Error = -1, + New, + AckAuth, + AckAuthEIP8, + WriteHello, + ReadHello, + StartSession + }; + /// Write Auth message to socket and transitions to AckAuth. void writeAuth(); - + /// Reads Auth message from socket and transitions to AckAuth. void readAuth(); + + /// Continues reading Auth message in EIP-8 format and transitions to AckAuthEIP8. + void readAuthEIP8(); + + /// Derives ephemeral secret from signature and sets members after Auth has been decrypted. + void setAuthValues(Signature const& sig, Public const& remotePubk, h256 const& remoteNonce, uint64_t remoteVersion); /// Write Ack message to socket and transitions to WriteHello. void writeAck(); + + /// Write Ack message in EIP-8 format to socket and transitions to WriteHello. + void writeAckEIP8(); /// Reads Auth message from socket and transitions to WriteHello. void readAck(); + + /// Continues reading Ack message in EIP-8 format and transitions to WriteHello. + void readAckEIP8(); /// Closes connection and ends transitions. void error(); /// Performs transition for m_nextState. - void transition(boost::system::error_code _ech = boost::system::error_code()); + virtual void transition(boost::system::error_code _ech = boost::system::error_code()); /// Timeout for remote to respond to transition events. Enforced by m_idleTimer and refreshed by transition(). boost::posix_time::milliseconds const c_timeout = boost::posix_time::milliseconds(1800); @@ -120,6 +135,7 @@ class RLPXHandshake: public std::enable_shared_from_this Public m_remoteEphemeral; ///< Remote ephemeral public key. h256 m_remoteNonce; ///< Nonce generated by remote host for handshake. + uint64_t m_remoteVersion; /// Used to read and write RLPx encrypted frames for last step of handshake authentication. /// Passed onto Host which will take ownership. diff --git a/test/libp2p/eip-8.cpp b/test/libp2p/eip-8.cpp index f9e75fe89..cdeef3f1d 100644 --- a/test/libp2p/eip-8.cpp +++ b/test/libp2p/eip-8.cpp @@ -22,14 +22,17 @@ #include #include +#include +#include +#include using namespace std; using namespace dev; using namespace dev::p2p; -BOOST_AUTO_TEST_SUITE(discovery) +BOOST_AUTO_TEST_SUITE(eip8) -BOOST_AUTO_TEST_CASE(test_discovery_eip8) +BOOST_AUTO_TEST_CASE(test_discovery_packets) { bi::udp::endpoint ep; bytes packet; @@ -114,4 +117,226 @@ BOOST_AUTO_TEST_CASE(test_discovery_eip8) BOOST_CHECK_EQUAL(neighbours.ts, 1136239445); } +class TestHandshake: public RLPXHandshake +{ +public: + TestHandshake(Host* _host, std::shared_ptr const& _socket): + RLPXHandshake(_host, _socket) {}; + TestHandshake(Host* _host, std::shared_ptr const& _socket, NodeID _remote): + RLPXHandshake(_host, _socket, _remote) {}; + + /// Creates a handshake attached to a Host with the given alias, + /// then runs it through the key establishment, supplying packet + /// as the input on the socket. + /// + /// If remoteID is supplied, the handshake runs in initiator mode. + static shared_ptr runWithInput(Secret _hostAlias, bytes _packet, NodeID _remoteID = NodeID()); + + /// transition is overridden to stop after key establishment. + virtual void transition(boost::system::error_code _ec); + + /// Reports whether we made it through the key establishment without error. + bool completedKeyEstablishment() { return m_nextState == ReadHello; } + + /// Checks whether Auth-related members match the values in the EIP-8 test vectors. + void checkAuthValuesEIP8(uint64_t _expectedRemoteVersion); + + /// Checks whether Ack-related members match the values in the EIP-8 test vectors. + void checkAckValuesEIP8(uint64_t _expectedRemoteVersion); +}; + +// This test checks that pre-EIP-8 'plain' format is still accepted. +BOOST_AUTO_TEST_CASE(test_handshake_plain_auth) +{ + Secret keyB("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"); + bytes auth(fromHex( + "048ca79ad18e4b0659fab4853fe5bc58eb83992980f4c9cc147d2aa31532efd29a3d3dc6a3d89eaf" + "913150cfc777ce0ce4af2758bf4810235f6e6ceccfee1acc6b22c005e9e3a49d6448610a58e98744" + "ba3ac0399e82692d67c1f58849050b3024e21a52c9d3b01d871ff5f210817912773e610443a9ef14" + "2e91cdba0bd77b5fdf0769b05671fc35f83d83e4d3b0b000c6b2a1b1bba89e0fc51bf4e460df3105" + "c444f14be226458940d6061c296350937ffd5e3acaceeaaefd3c6f74be8e23e0f45163cc7ebd7622" + "0f0128410fd05250273156d548a414444ae2f7dea4dfca2d43c057adb701a715bf59f6fb66b2d1d2" + "0f2c703f851cbf5ac47396d9ca65b6260bd141ac4d53e2de585a73d1750780db4c9ee4cd4d225173" + "a4592ee77e2bd94d0be3691f3b406f9bba9b591fc63facc016bfa8" + )); + shared_ptr h = TestHandshake::runWithInput(keyB, auth); + BOOST_REQUIRE(h->completedKeyEstablishment()); + h->checkAuthValuesEIP8(4); +} + +BOOST_AUTO_TEST_CASE(test_handshake_eip8_auth1) +{ + Secret keyB("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"); + bytes auth(fromHex( + "01b304ab7578555167be8154d5cc456f567d5ba302662433674222360f08d5f1534499d3678b513b" + "0fca474f3a514b18e75683032eb63fccb16c156dc6eb2c0b1593f0d84ac74f6e475f1b8d56116b84" + "9634a8c458705bf83a626ea0384d4d7341aae591fae42ce6bd5c850bfe0b999a694a49bbbaf3ef6c" + "da61110601d3b4c02ab6c30437257a6e0117792631a4b47c1d52fc0f8f89caadeb7d02770bf999cc" + "147d2df3b62e1ffb2c9d8c125a3984865356266bca11ce7d3a688663a51d82defaa8aad69da39ab6" + "d5470e81ec5f2a7a47fb865ff7cca21516f9299a07b1bc63ba56c7a1a892112841ca44b6e0034dee" + "70c9adabc15d76a54f443593fafdc3b27af8059703f88928e199cb122362a4b35f62386da7caad09" + "c001edaeb5f8a06d2b26fb6cb93c52a9fca51853b68193916982358fe1e5369e249875bb8d0d0ec3" + "6f917bc5e1eafd5896d46bd61ff23f1a863a8a8dcd54c7b109b771c8e61ec9c8908c733c0263440e" + "2aa067241aaa433f0bb053c7b31a838504b148f570c0ad62837129e547678c5190341e4f1693956c" + "3bf7678318e2d5b5340c9e488eefea198576344afbdf66db5f51204a6961a63ce072c8926c" + )); + shared_ptr h = TestHandshake::runWithInput(keyB, auth); + BOOST_REQUIRE(h->completedKeyEstablishment()); + h->checkAuthValuesEIP8(4); +} + +BOOST_AUTO_TEST_CASE(test_handshake_eip8_auth2) +{ + Secret keyB("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"); + bytes auth(fromHex( + "01b8044c6c312173685d1edd268aa95e1d495474c6959bcdd10067ba4c9013df9e40ff45f5bfd6f7" + "2471f93a91b493f8e00abc4b80f682973de715d77ba3a005a242eb859f9a211d93a347fa64b597bf" + "280a6b88e26299cf263b01b8dfdb712278464fd1c25840b995e84d367d743f66c0e54a586725b7bb" + "f12acca27170ae3283c1073adda4b6d79f27656993aefccf16e0d0409fe07db2dc398a1b7e8ee93b" + "cd181485fd332f381d6a050fba4c7641a5112ac1b0b61168d20f01b479e19adf7fdbfa0905f63352" + "bfc7e23cf3357657455119d879c78d3cf8c8c06375f3f7d4861aa02a122467e069acaf513025ff19" + "6641f6d2810ce493f51bee9c966b15c5043505350392b57645385a18c78f14669cc4d960446c1757" + "1b7c5d725021babbcd786957f3d17089c084907bda22c2b2675b4378b114c601d858802a55345a15" + "116bc61da4193996187ed70d16730e9ae6b3bb8787ebcaea1871d850997ddc08b4f4ea668fbf3740" + "7ac044b55be0908ecb94d4ed172ece66fd31bfdadf2b97a8bc690163ee11f5b575a4b44e36e2bfb2" + "f0fce91676fd64c7773bac6a003f481fddd0bae0a1f31aa27504e2a533af4cef3b623f4791b2cca6" + "d490" + )); + shared_ptr h = TestHandshake::runWithInput(keyB, auth); + BOOST_REQUIRE(h->completedKeyEstablishment()); + h->checkAuthValuesEIP8(56); +} + +BOOST_AUTO_TEST_CASE(test_handshake_eip8_ack_plain) +{ + Secret keyA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee"); + bytes ack(fromHex( + "049f8abcfa9c0dc65b982e98af921bc0ba6e4243169348a236abe9df5f93aa69d99cadddaa387662" + "b0ff2c08e9006d5a11a278b1b3331e5aaabf0a32f01281b6f4ede0e09a2d5f585b26513cb794d963" + "5a57563921c04a9090b4f14ee42be1a5461049af4ea7a7f49bf4c97a352d39c8d02ee4acc416388c" + "1c66cec761d2bc1c72da6ba143477f049c9d2dde846c252c111b904f630ac98e51609b3b1f58168d" + "dca6505b7196532e5f85b259a20c45e1979491683fee108e9660edbf38f3add489ae73e3dda2c71b" + "d1497113d5c755e942d1" + )); + NodeID initiatorPubk("fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877"); + shared_ptr h = TestHandshake::runWithInput(keyA, ack, initiatorPubk); + BOOST_REQUIRE(h->completedKeyEstablishment()); + h->checkAckValuesEIP8(4); +} + +BOOST_AUTO_TEST_CASE(test_handshake_eip8_ack1) +{ + Secret keyA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee"); + bytes ack(fromHex( + "01ea0451958701280a56482929d3b0757da8f7fbe5286784beead59d95089c217c9b917788989470" + "b0e330cc6e4fb383c0340ed85fab836ec9fb8a49672712aeabbdfd1e837c1ff4cace34311cd7f4de" + "05d59279e3524ab26ef753a0095637ac88f2b499b9914b5f64e143eae548a1066e14cd2f4bd7f814" + "c4652f11b254f8a2d0191e2f5546fae6055694aed14d906df79ad3b407d94692694e259191cde171" + "ad542fc588fa2b7333313d82a9f887332f1dfc36cea03f831cb9a23fea05b33deb999e85489e645f" + "6aab1872475d488d7bd6c7c120caf28dbfc5d6833888155ed69d34dbdc39c1f299be1057810f34fb" + "e754d021bfca14dc989753d61c413d261934e1a9c67ee060a25eefb54e81a4d14baff922180c395d" + "3f998d70f46f6b58306f969627ae364497e73fc27f6d17ae45a413d322cb8814276be6ddd13b885b" + "201b943213656cde498fa0e9ddc8e0b8f8a53824fbd82254f3e2c17e8eaea009c38b4aa0a3f306e8" + "797db43c25d68e86f262e564086f59a2fc60511c42abfb3057c247a8a8fe4fb3ccbadde17514b7ac" + "8000cdb6a912778426260c47f38919a91f25f4b5ffb455d6aaaf150f7e5529c100ce62d6d92826a7" + "1778d809bdf60232ae21ce8a437eca8223f45ac37f6487452ce626f549b3b5fdee26afd2072e4bc7" + "5833c2464c805246155289f4" + )); + NodeID initiatorPubk("fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877"); + shared_ptr h = TestHandshake::runWithInput(keyA, ack, initiatorPubk); + BOOST_REQUIRE(h->completedKeyEstablishment()); + h->checkAckValuesEIP8(4); +} + +BOOST_AUTO_TEST_CASE(test_handshake_eip8_ack2) +{ + Secret keyA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee"); + bytes ack(fromHex( + "01f004076e58aae772bb101ab1a8e64e01ee96e64857ce82b1113817c6cdd52c09d26f7b90981cd7" + "ae835aeac72e1573b8a0225dd56d157a010846d888dac7464baf53f2ad4e3d584531fa203658fab0" + "3a06c9fd5e35737e417bc28c1cbf5e5dfc666de7090f69c3b29754725f84f75382891c561040ea1d" + "dc0d8f381ed1b9d0d4ad2a0ec021421d847820d6fa0ba66eaf58175f1b235e851c7e2124069fbc20" + "2888ddb3ac4d56bcbd1b9b7eab59e78f2e2d400905050f4a92dec1c4bdf797b3fc9b2f8e84a482f3" + "d800386186712dae00d5c386ec9387a5e9c9a1aca5a573ca91082c7d68421f388e79127a5177d4f8" + "590237364fd348c9611fa39f78dcdceee3f390f07991b7b47e1daa3ebcb6ccc9607811cb17ce51f1" + "c8c2c5098dbdd28fca547b3f58c01a424ac05f869f49c6a34672ea2cbbc558428aa1fe48bbfd6115" + "8b1b735a65d99f21e70dbc020bfdface9f724a0d1fb5895db971cc81aa7608baa0920abb0a565c9c" + "436e2fd13323428296c86385f2384e408a31e104670df0791d93e743a3a5194ee6b076fb6323ca59" + "3011b7348c16cf58f66b9633906ba54a2ee803187344b394f75dd2e663a57b956cb830dd7a908d4f" + "39a2336a61ef9fda549180d4ccde21514d117b6c6fd07a9102b5efe710a32af4eeacae2cb3b1dec0" + "35b9593b48b9d3ca4c13d245d5f04169b0b1" + )); + NodeID initiatorPubk("fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877"); + shared_ptr h = TestHandshake::runWithInput(keyA, ack, initiatorPubk); + BOOST_REQUIRE(h->completedKeyEstablishment()); + h->checkAckValuesEIP8(57); +} + +void TestHandshake::checkAuthValuesEIP8(uint64_t _expectedRemoteVersion) +{ + BOOST_CHECK_EQUAL(m_remote, Public("fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877")); + BOOST_CHECK_EQUAL(m_remoteNonce, h256("7e968bba13b6c50e2c4cd7f241cc0d64d1ac25c7f5952df231ac6a2bda8ee5d6")); + BOOST_CHECK_EQUAL(m_remoteEphemeral, Public("654d1044b69c577a44e5f01a1209523adb4026e70c62d1c13a067acabc09d2667a49821a0ad4b634554d330a15a58fe61f8a8e0544b310c6de7b0c8da7528a8d")); + BOOST_CHECK_EQUAL(m_remoteVersion, _expectedRemoteVersion); +} + +void TestHandshake::checkAckValuesEIP8(uint64_t _expectedRemoteVersion) +{ + BOOST_CHECK_EQUAL(m_remoteNonce, h256("559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd")); + BOOST_CHECK_EQUAL(m_remoteEphemeral, Public("b6d82fa3409da933dbf9cb0140c5dde89f4e64aec88d476af648880f4a10e1e49fe35ef3e69e93dd300b4797765a747c6384a6ecf5db9c2690398607a86181e4")); + BOOST_CHECK_EQUAL(m_remoteVersion, _expectedRemoteVersion); +} + +#define throwErrorCode(what, error) \ + { if (error) BOOST_THROW_EXCEPTION(Exception(what + _ec.message())); } + +shared_ptr TestHandshake::runWithInput(Secret _hostAlias, bytes _packet, NodeID _remoteID) +{ + // Spawn a listener which sends the packet to any client. + ba::io_service io; + bi::tcp::acceptor acceptor(io); + bi::tcp::endpoint endpoint(bi::address::from_string("127.0.0.1"), 0); + acceptor.open(endpoint.protocol()); + acceptor.bind(endpoint); + acceptor.listen(); + auto server = std::make_shared(io); + acceptor.async_accept(server->ref(), [_packet,server](boost::system::error_code const& _ec) + { + throwErrorCode("accept error: ", _ec); + ba::async_write(server->ref(), ba::buffer(_packet), [](const boost::system::error_code& _ec, std::size_t) + { + throwErrorCode("write error: ", _ec); + }); + }); + + // Spawn a client to execute the handshake. + auto host = make_shared("peer name", KeyPair(_hostAlias)); + auto client = make_shared(io); + shared_ptr handshake; + if (_remoteID == NodeID()) + handshake.reset(new TestHandshake(host.get(), client)); + else + handshake.reset(new TestHandshake(host.get(), client, _remoteID)); + + client->ref().async_connect(acceptor.local_endpoint(), [handshake](boost::system::error_code const& _ec) + { + throwErrorCode("connect error: ", _ec); + handshake->start(); + }); + + // Run all I/O to completion and return the finished handshake. + io.run(); + return handshake; +} + +void TestHandshake::transition(boost::system::error_code _ec) +{ + if (!_ec && m_nextState == ReadHello) + { + cancel(); + return; + } + RLPXHandshake::transition(_ec); +} + BOOST_AUTO_TEST_SUITE_END()