Skip to content

Commit c32329c

Browse files
dhruvjonasschnelli
andcommitted
Add BIP324 v2 transport serializer and deserializer
Co-authored-by: Jonas Schnelli <[email protected]>
1 parent 9d9c46f commit c32329c

File tree

4 files changed

+367
-11
lines changed

4 files changed

+367
-11
lines changed

src/net.cpp

+195-4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <scheduler.h>
3030
#include <util/sock.h>
3131
#include <util/strencodings.h>
32+
#include <util/string.h>
3233
#include <util/syscall_sandbox.h>
3334
#include <util/system.h>
3435
#include <util/thread.h>
@@ -65,6 +66,8 @@ static_assert (MAX_BLOCK_RELAY_ONLY_ANCHORS <= static_cast<size_t>(MAX_BLOCK_REL
6566
/** Anchor IP address database file name */
6667
const char* const ANCHORS_DATABASE_FILENAME = "anchors.dat";
6768

69+
static constexpr uint64_t V2_MAX_CONTENTS_LENGTH = 0x01000000 - 1; // 2^24 - 1
70+
6871
// How often to dump addresses to peers.dat
6972
static constexpr std::chrono::minutes DUMP_PEERS_INTERVAL{15};
7073

@@ -110,6 +113,8 @@ const std::string NET_MESSAGE_TYPE_OTHER = "*other*";
110113
static const uint64_t RANDOMIZER_ID_NETGROUP = 0x6c0edd8036ef4036ULL; // SHA256("netgroup")[0:8]
111114
static const uint64_t RANDOMIZER_ID_LOCALHOSTNONCE = 0xd93e69e2bbfa5735ULL; // SHA256("localhostnonce")[0:8]
112115
static const uint64_t RANDOMIZER_ID_ADDRCACHE = 0x1cf2e4ddd306dda9ULL; // SHA256("addrcache")[0:8]
116+
117+
static constexpr uint8_t V2_LONG_MSG_TYPE_LEN = 12; // V2 (BIP324) message type long ids
113118
//
114119
// Global state variables
115120
//
@@ -685,7 +690,14 @@ bool CNode::ReceiveMsgBytes(Span<const uint8_t> msg_bytes, bool& complete)
685690
if (m_deserializer->Complete()) {
686691
// decompose a transport agnostic CNetMessage from the deserializer
687692
bool reject_message{false};
688-
CNetMessage msg = m_deserializer->GetMessage(time, reject_message);
693+
bool disconnect{false};
694+
CNetMessage msg = m_deserializer->GetMessage(time, reject_message, disconnect);
695+
696+
if (disconnect) {
697+
// v2 p2p incorrect MAC tag. Disconnect from peer.
698+
return false;
699+
}
700+
689701
if (reject_message) {
690702
// Message deserialization failed. Drop the message but don't disconnect the peer.
691703
// store the size of the corrupt message
@@ -777,10 +789,12 @@ const uint256& V1TransportDeserializer::GetMessageHash() const
777789
return data_hash;
778790
}
779791

780-
CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds time, bool& reject_message)
792+
CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds time, bool& reject_message, bool& disconnect)
781793
{
782794
// Initialize out parameter
783795
reject_message = false;
796+
disconnect = false;
797+
784798
// decompose a single CNetMessage from the TransportDeserializer
785799
CNetMessage msg(std::move(vRecv));
786800

@@ -802,6 +816,7 @@ CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds
802816
HexStr(Span{hash}.first(CMessageHeader::CHECKSUM_SIZE)),
803817
HexStr(hdr.pchChecksum),
804818
m_node_id);
819+
// TODO: Should we disconnect the v1 peer in this case?
805820
reject_message = true;
806821
} else if (!hdr.IsCommandValid()) {
807822
LogPrint(BCLog::NET, "Header error: Invalid message type (%s, %u bytes), peer=%d\n",
@@ -814,7 +829,7 @@ CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds
814829
return msg;
815830
}
816831

817-
void V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) const
832+
bool V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) const
818833
{
819834
// create dbl-sha256 checksum
820835
uint256 hash = Hash(msg.data);
@@ -826,6 +841,179 @@ void V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vec
826841
// serialize header
827842
header.reserve(CMessageHeader::HEADER_SIZE);
828843
CVectorWriter{SER_NETWORK, INIT_PROTO_VERSION, header, 0, hdr};
844+
return true;
845+
}
846+
847+
int V2TransportDeserializer::readHeader(Span<const uint8_t> pkt_bytes)
848+
{
849+
// copy data to temporary parsing buffer
850+
const size_t remaining = BIP324_LENGTH_FIELD_LEN - m_hdr_pos;
851+
const size_t copy_bytes = std::min<unsigned int>(remaining, pkt_bytes.size());
852+
853+
memcpy(&vRecv[m_hdr_pos], pkt_bytes.data(), copy_bytes);
854+
m_hdr_pos += copy_bytes;
855+
856+
// if we don't have the encrypted length yet, exit
857+
if (m_hdr_pos < BIP324_LENGTH_FIELD_LEN) {
858+
return copy_bytes;
859+
}
860+
861+
// we have the 3 bytes encrypted packet length at this point
862+
std::array<std::byte, BIP324_LENGTH_FIELD_LEN> encrypted_pkt_len;
863+
memcpy(encrypted_pkt_len.data(), vRecv.data(), BIP324_LENGTH_FIELD_LEN);
864+
865+
// the encrypted packet data = bip324 header + contents (message type + message payload)
866+
m_contents_size = m_cipher_suite->DecryptLength(encrypted_pkt_len);
867+
868+
// m_contents_size is the size of the p2p message
869+
if (m_contents_size > V2_MAX_CONTENTS_LENGTH) {
870+
return -1;
871+
}
872+
873+
// switch state to reading message data
874+
m_in_data = true;
875+
876+
return copy_bytes;
877+
}
878+
879+
int V2TransportDeserializer::readData(Span<const uint8_t> pkt_bytes)
880+
{
881+
// Read the BIP324 encrypted packet data.
882+
const size_t remaining = BIP324_HEADER_LEN + m_contents_size + RFC8439_EXPANSION - m_data_pos;
883+
const size_t copy_bytes = std::min<unsigned int>(remaining, pkt_bytes.size());
884+
885+
// extend buffer, respect previous copied encrypted length
886+
if (vRecv.size() < BIP324_LENGTH_FIELD_LEN + m_data_pos + copy_bytes) {
887+
// Allocate up to 256 KiB ahead, but never more than the total message size.
888+
vRecv.resize(BIP324_LENGTH_FIELD_LEN + std::min(BIP324_HEADER_LEN + m_contents_size, m_data_pos + copy_bytes + 256 * 1024) + RFC8439_EXPANSION, std::byte{0x00});
889+
}
890+
891+
memcpy(&vRecv[BIP324_LENGTH_FIELD_LEN + m_data_pos], pkt_bytes.data(), copy_bytes);
892+
m_data_pos += copy_bytes;
893+
894+
return copy_bytes;
895+
}
896+
897+
CNetMessage V2TransportDeserializer::GetMessage(const std::chrono::microseconds time, bool& reject_message, bool& disconnect)
898+
{
899+
const size_t min_contents_size = 1; // BIP324 1-byte message type id is the minimum contents
900+
901+
// Initialize out parameters
902+
reject_message = (vRecv.size() < V2_MIN_PACKET_LENGTH + min_contents_size);
903+
disconnect = false;
904+
905+
// In v2, vRecv contains:
906+
// 3 bytes of encrypted packet length
907+
// 1-byte encrypted bip324 header
908+
// variable length encrypted contents(message type and message payload) and
909+
// mac tag
910+
assert(Complete());
911+
912+
std::string msg_type;
913+
914+
BIP324HeaderFlags flags;
915+
size_t msg_type_size = 1; // at least one byte needed for message type
916+
if (m_cipher_suite->Crypt({},
917+
Span{reinterpret_cast<const std::byte*>(vRecv.data() + BIP324_LENGTH_FIELD_LEN), BIP324_HEADER_LEN + m_contents_size + RFC8439_EXPANSION},
918+
Span{reinterpret_cast<std::byte*>(vRecv.data()), m_contents_size}, flags, false)) {
919+
// MAC check was successful
920+
vRecv.resize(m_contents_size);
921+
reject_message = reject_message || (BIP324HeaderFlags(BIP324_IGNORE & flags) != BIP324_NONE);
922+
923+
if (!reject_message) {
924+
uint8_t msg_type_id = NO_BIP324_SHORT_ID;
925+
try {
926+
vRecv >> msg_type_id;
927+
} catch (const std::ios_base::failure&) {
928+
LogPrint(BCLog::NET, "Failed to read message type, peer=%d\n", m_node_id);
929+
reject_message = true;
930+
}
931+
932+
if (msg_type_id == NO_BIP324_SHORT_ID) {
933+
if (vRecv.size() < V2_LONG_MSG_TYPE_LEN) {
934+
LogPrint(BCLog::NET, "Invalid v2 message type long id, peer=%d\n", m_node_id);
935+
reject_message = true;
936+
}
937+
msg_type.resize(V2_LONG_MSG_TYPE_LEN);
938+
vRecv.read(MakeWritableByteSpan(msg_type));
939+
auto trim_pos = std::find(msg_type.begin(), msg_type.end(), 0);
940+
if (trim_pos != msg_type.end()) {
941+
msg_type.resize(trim_pos - msg_type.begin());
942+
}
943+
msg_type_size += V2_LONG_MSG_TYPE_LEN;
944+
} else {
945+
auto mtype = GetMessageTypeFromShortID(msg_type_id);
946+
if (mtype.has_value()) {
947+
msg_type = mtype.value();
948+
} else {
949+
// unknown-short-id results in a valid but unknown message (will be skipped)
950+
msg_type = "unknown-" + ToString(msg_type_id);
951+
reject_message = true;
952+
}
953+
}
954+
}
955+
} else {
956+
// Invalid mac tag
957+
LogPrint(BCLog::NET, "Invalid v2 mac tag, peer=%d\n", m_node_id);
958+
disconnect = true;
959+
reject_message = true;
960+
}
961+
962+
// we'll always return a CNetMessage (even if decryption fails)
963+
// decompose a single CNetMessage from the TransportDeserializer
964+
CNetMessage msg(std::move(vRecv));
965+
msg.m_type = msg_type;
966+
msg.m_time = time;
967+
968+
if (!reject_message) {
969+
msg.m_message_size = m_contents_size - msg_type_size;
970+
msg.m_raw_message_size = V2_MIN_PACKET_LENGTH + m_contents_size; // raw wire size
971+
}
972+
973+
Reset();
974+
return msg;
975+
}
976+
977+
bool V2TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) const
978+
{
979+
size_t serialized_msg_type_size = 1; // short-IDs are 1 byte
980+
uint8_t short_msg_type = GetShortIDFromMessageType(msg.m_type);
981+
if (short_msg_type == NO_BIP324_SHORT_ID) {
982+
// message type without an assigned short-ID
983+
assert(msg.m_type.size() <= V2_LONG_MSG_TYPE_LEN);
984+
// encode as NO_BIP324_SHORT_ID || 12-byte long id
985+
serialized_msg_type_size += V2_LONG_MSG_TYPE_LEN;
986+
}
987+
988+
std::vector<unsigned char> msg_type_bytes(serialized_msg_type_size, 0);
989+
990+
// append the short-ID or the varstr of the msg type
991+
msg_type_bytes[0] = short_msg_type;
992+
if (short_msg_type == NO_BIP324_SHORT_ID) {
993+
// append ASCII command string
994+
memcpy(msg_type_bytes.data() + 1, msg.m_type.data(), msg.m_type.size());
995+
}
996+
997+
// insert message type directly into the CSerializedNetMsg data buffer (insert at begin)
998+
// TODO: if we refactor the BIP324CipherSuite::Crypt() function to allow separate buffers for
999+
// the message type and payload we could avoid a insert and thus a potential reallocation
1000+
msg.data.insert(msg.data.begin(), msg_type_bytes.begin(), msg_type_bytes.end());
1001+
1002+
auto contents_size = msg.data.size();
1003+
auto encrypted_pkt_size = V2_MIN_PACKET_LENGTH + contents_size;
1004+
// resize the message buffer to make space for the MAC tag
1005+
msg.data.resize(encrypted_pkt_size, 0);
1006+
1007+
BIP324HeaderFlags flags{BIP324_NONE};
1008+
// encrypt the payload, this should always succeed (controlled buffers, don't check the MAC during encrypting)
1009+
auto success = m_cipher_suite->Crypt({},
1010+
Span{reinterpret_cast<const std::byte*>(msg.data.data()), contents_size},
1011+
Span{reinterpret_cast<std::byte*>(msg.data.data()), encrypted_pkt_size},
1012+
flags, true);
1013+
if (!success) {
1014+
LogPrint(BCLog::NET, "error in v2 p2p encryption for message type: %s\n", msg.m_type);
1015+
}
1016+
return success;
8291017
}
8301018

8311019
size_t CConnman::SocketSendData(CNode& node) const
@@ -2832,7 +3020,10 @@ void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg)
28323020

28333021
// make sure we use the appropriate network transport format
28343022
std::vector<unsigned char> serializedHeader;
2835-
pnode->m_serializer->prepareForTransport(msg, serializedHeader);
3023+
if (!pnode->m_serializer->prepareForTransport(msg, serializedHeader)) {
3024+
return;
3025+
}
3026+
28363027
size_t nTotalSize = nMessageSize + serializedHeader.size();
28373028

28383029
size_t nBytesSent = 0;

0 commit comments

Comments
 (0)