Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1001std::copy(std::begin(m_cipher.GetOurPubKey()), std::end(m_cipher.GetOurPubKey()), MakeWritableByteSpan(m_send_buffer).begin());1002std::copy(m_send_garbage.begin(), m_send_garbage.end(), m_send_buffer.begin() + EllSwiftPubKey::size());1003// We cannot wipe m_send_garbage as it will still be used as AAD later in the handshake.1004}1006V2Transport::V2Transport(NodeId nodeid, bool initiating, const CKey& key, std::span<const std::byte> ent32, std::vector<uint8_t> garbage) noexcept1007: m_cipher{key, ent32},1008m_initiating{initiating},1009m_nodeid{nodeid},1010m_v1_fallback{nodeid},1011m_recv_state{initiating ? RecvState::KEY : RecvState::KEY_MAYBE_V1},1012m_send_garbage{std::move(garbage)},1013m_send_state{initiating ? SendState::AWAITING_KEY : SendState::MAYBE_V1}1014{1015Assume(m_send_garbage.size() <= MAX_GARBAGE_LEN);1016// Start sending immediately if we're the initiator of the connection.1017if (initiating) {1018LOCK(m_send_mutex);1019StartSendingHandshake();1020}1021}1023V2Transport::V2Transport(NodeId nodeid, bool initiating) noexcept1024: V2Transport{nodeid, initiating, GenerateRandomKey(),1025MakeByteSpan(GetRandHash()), GenerateRandomGarbage()} {}1027void V2Transport::SetReceiveState(RecvState recv_state) noexcept1028{1029AssertLockHeld(m_recv_mutex);1030// Enforce allowed state transitions.1031switch (m_recv_state) {1032case RecvState::KEY_MAYBE_V1:1033Assume(recv_state == RecvState::KEY || recv_state == RecvState::V1);1034break;1035case RecvState::KEY:1036Assume(recv_state == RecvState::GARB_GARBTERM);1037break;1038case RecvState::GARB_GARBTERM:1039Assume(recv_state == RecvState::VERSION);1040break;1041case RecvState::VERSION:1042Assume(recv_state == RecvState::APP);1043break;1044case RecvState::APP:1045Assume(recv_state == RecvState::APP_READY);1046break;1047case RecvState::APP_READY:1048Assume(recv_state == RecvState::APP);1049break;1050case RecvState::V1:1051Assume(false); // V1 state cannot be left1052break;1053}1054// Change state.1055m_recv_state = recv_state;1056}1058void V2Transport::SetSendState(SendState send_state) noexcept1059{1060AssertLockHeld(m_send_mutex);1061// Enforce allowed state transitions.1062switch (m_send_state) {1063case SendState::MAYBE_V1:1064Assume(send_state == SendState::V1 || send_state == SendState::AWAITING_KEY);1065break;1066case SendState::AWAITING_KEY:1067Assume(send_state == SendState::READY);1068break;1069case SendState::READY:1070case SendState::V1:1071Assume(false); // Final states1072break;1073}1074// Change state.1075m_send_state = send_state;1076}1078bool V2Transport::ReceivedMessageComplete() const noexcept1079{1080AssertLockNotHeld(m_recv_mutex);1081LOCK(m_recv_mutex);1082if (m_recv_state == RecvState::V1) return m_v1_fallback.ReceivedMessageComplete();1084return m_recv_state == RecvState::APP_READY;1085}1087void V2Transport::ProcessReceivedMaybeV1Bytes() noexcept1088{1089AssertLockHeld(m_recv_mutex);1090AssertLockNotHeld(m_send_mutex);1091Assume(m_recv_state == RecvState::KEY_MAYBE_V1);1092// We still have to determine if this is a v1 or v2 connection. The bytes being received could1093// be the beginning of either a v1 packet (network magic + "version\x00\x00\x00\x00\x00"), or1094// of a v2 public key. BIP324 specifies that a mismatch with this 16-byte string should trigger1095// sending of the key.1096std::array<uint8_t, V1_PREFIX_LEN> v1_prefix = {0, 0, 0, 0, 'v', 'e', 'r', 's', 'i', 'o', 'n', 0, 0, 0, 0, 0};1097std::copy(std::begin(Params().MessageStart()), std::end(Params().MessageStart()), v1_prefix.begin());1098Assume(m_recv_buffer.size() <= v1_prefix.size());1099if (!std::equal(m_recv_buffer.begin(), m_recv_buffer.end(), v1_prefix.begin())) {1100// Mismatch with v1 prefix, so we can assume a v2 connection.1101SetReceiveState(RecvState::KEY); // Convert to KEY state, leaving received bytes around.1102// Transition the sender to AWAITING_KEY state and start sending.1103LOCK(m_send_mutex);1104SetSendState(SendState::AWAITING_KEY);1105StartSendingHandshake();1106} else if (m_recv_buffer.size() == v1_prefix.size()) {1107// Full match with the v1 prefix, so fall back to v1 behavior.1108LOCK(m_send_mutex);1109std::span<const uint8_t> feedback{m_recv_buffer};1110// Feed already received bytes to v1 transport. It should always accept these, because it's1111// less than the size of a v1 header, and these are the first bytes fed to m_v1_fallback.1112bool ret = m_v1_fallback.ReceivedBytes(feedback);1113Assume(feedback.empty());1114Assume(ret);1115SetReceiveState(RecvState::V1);1116SetSendState(SendState::V1);1117// Reset v2 transport buffers to save memory.1118ClearShrink(m_recv_buffer);1119ClearShrink(m_send_buffer);1120} else {1121// We have not received enough to distinguish v1 from v2 yet. Wait until more bytes come.1122}1123}1125bool V2Transport::ProcessReceivedKeyBytes() noexcept1126{1127AssertLockHeld(m_recv_mutex);1128AssertLockNotHeld(m_send_mutex);1129Assume(m_recv_state == RecvState::KEY);1130Assume(m_recv_buffer.size() <= EllSwiftPubKey::size());1132// As a special exception, if bytes 4-16 of the key on a responder connection match the1133// corresponding bytes of a V1 version message, but bytes 0-4 don't match the network magic1134// (if they did, we'd have switched to V1 state already), assume this is a peer from1135// another network, and disconnect them. They will almost certainly disconnect us too when1136// they receive our uniformly random key and garbage, but detecting this case specially1137// means we can log it.1138static constexpr std::array<uint8_t, 12> MATCH = {'v', 'e', 'r', 's', 'i', 'o', 'n', 0, 0, 0, 0, 0};1139static constexpr size_t OFFSET = std::tuple_size_v<MessageStartChars>;1140if (!m_initiating && m_recv_buffer.size() >= OFFSET + MATCH.size()) {1141if (std::equal(MATCH.begin(), MATCH.end(), m_recv_buffer.begin() + OFFSET)) {1142LogDebug(BCLog::NET, "V2 transport error: V1 peer with wrong MessageStart %s\n",1143HexStr(std::span(m_recv_buffer).first(OFFSET)));1144return false;1145}1146}1148if (m_recv_buffer.size() == EllSwiftPubKey::size()) {1149// Other side's key has been fully received, and can now be Diffie-Hellman combined with1150// our key to initialize the encryption ciphers.1152// Initialize the ciphers.1153EllSwiftPubKey ellswift(MakeByteSpan(m_recv_buffer));1154LOCK(m_send_mutex);1155m_cipher.Initialize(ellswift, m_initiating);1157// Switch receiver state to GARB_GARBTERM.1158SetReceiveState(RecvState::GARB_GARBTERM);1159m_recv_buffer.clear();1161// Switch sender state to READY.1162SetSendState(SendState::READY);1164// Append the garbage terminator to the send buffer.1165m_send_buffer.resize(m_send_buffer.size() + BIP324Cipher::GARBAGE_TERMINATOR_LEN);1166std::copy(m_cipher.GetSendGarbageTerminator().begin(),1167m_cipher.GetSendGarbageTerminator().end(),1168MakeWritableByteSpan(m_send_buffer).last(BIP324Cipher::GARBAGE_TERMINATOR_LEN).begin());1170// Construct version packet in the send buffer, with the sent garbage data as AAD.1171m_send_buffer.resize(m_send_buffer.size() + BIP324Cipher::EXPANSION + VERSION_CONTENTS.size());1172m_cipher.Encrypt(1173/*contents=*/VERSION_CONTENTS,1174/*aad=*/MakeByteSpan(m_send_garbage),1175/*ignore=*/false,1176/*output=*/MakeWritableByteSpan(m_send_buffer).last(BIP324Cipher::EXPANSION + VERSION_CONTENTS.size()));1177// We no longer need the garbage.1178ClearShrink(m_send_garbage);1179} else {1180// We still have to receive more key bytes.1181}1182return true;1183}1185bool V2Transport::ProcessReceivedGarbageBytes() noexcept1186{1187AssertLockHeld(m_recv_mutex);1188Assume(m_recv_state == RecvState::GARB_GARBTERM);1189Assume(m_recv_buffer.size() <= MAX_GARBAGE_LEN + BIP324Cipher::GARBAGE_TERMINATOR_LEN);1190if (m_recv_buffer.size() >= BIP324Cipher::GARBAGE_TERMINATOR_LEN) {1191if (std::ranges::equal(MakeByteSpan(m_recv_buffer).last(BIP324Cipher::GARBAGE_TERMINATOR_LEN), m_cipher.GetReceiveGarbageTerminator())) {1192// Garbage terminator received. Store garbage to authenticate it as AAD later.1193m_recv_aad = std::move(m_recv_buffer);1194m_recv_aad.resize(m_recv_aad.size() - BIP324Cipher::GARBAGE_TERMINATOR_LEN);1195m_recv_buffer.clear();1196SetReceiveState(RecvState::VERSION);1197} else if (m_recv_buffer.size() == MAX_GARBAGE_LEN + BIP324Cipher::GARBAGE_TERMINATOR_LEN) {1198// We've reached the maximum length for garbage + garbage terminator, and the1199// terminator still does not match. Abort.1200LogDebug(BCLog::NET, "V2 transport error: missing garbage terminator, peer=%d\n", m_nodeid);1201return false;1202} else {1203// We still need to receive more garbage and/or garbage terminator bytes.1204}1205} else {1206// We have less than GARBAGE_TERMINATOR_LEN (16) bytes, so we certainly need to receive1207// more first.1208}1209return true;1210}1212bool V2Transport::ProcessReceivedPacketBytes() noexcept1213{1214AssertLockHeld(m_recv_mutex);1215Assume(m_recv_state == RecvState::VERSION || m_recv_state == RecvState::APP);1217// The maximum permitted contents length for a packet, consisting of:1218// - 0x00 byte: indicating long message type encoding1219// - 12 bytes of message type1220// - payload1221static constexpr size_t MAX_CONTENTS_LEN =12221 + CMessageHeader::MESSAGE_TYPE_SIZE +1223std::min<size_t>(MAX_SIZE, MAX_PROTOCOL_MESSAGE_LENGTH);1225if (m_recv_buffer.size() == BIP324Cipher::LENGTH_LEN) {1226// Length descriptor received.1227m_recv_len = m_cipher.DecryptLength(MakeByteSpan(m_recv_buffer));1228if (m_recv_len > MAX_CONTENTS_LEN) {1229LogDebug(BCLog::NET, "V2 transport error: packet too large (%u bytes), peer=%d\n", m_recv_len, m_nodeid);1230return false;1231}1232} else if (m_recv_buffer.size() > BIP324Cipher::LENGTH_LEN && m_recv_buffer.size() == m_recv_len + BIP324Cipher::EXPANSION) {1233// Ciphertext received, decrypt it into m_recv_decode_buffer.1234// Note that it is impossible to reach this branch without hitting the branch above first,1235// as GetMaxBytesToProcess only allows up to LENGTH_LEN into the buffer before that point.1236m_recv_decode_buffer.resize(m_recv_len);1237bool ignore{false};1238bool ret = m_cipher.Decrypt(1239/*input=*/MakeByteSpan(m_recv_buffer).subspan(BIP324Cipher::LENGTH_LEN),1240/*aad=*/MakeByteSpan(m_recv_aad),1241/*ignore=*/ignore,1242/*contents=*/MakeWritableByteSpan(m_recv_decode_buffer));1243if (!ret) {1244LogDebug(BCLog::NET, "V2 transport error: packet decryption failure (%u bytes), peer=%d\n", m_recv_len, m_nodeid);1245return false;1246}1247// We have decrypted a valid packet with the AAD we expected, so clear the expected AAD.1248ClearShrink(m_recv_aad);1249// Feed the last 4 bytes of the Poly1305 authentication tag (and its timing) into our RNG.1250RandAddEvent(ReadLE32(m_recv_buffer.data() + m_recv_buffer.size() - 4));