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.
1#!/usr/bin/env python32# Copyright (c) 2022-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.6import random7import time8from enum import Enum10from test_framework.messages import MAGIC_BYTES11from test_framework.p2p import P2PInterface12from test_framework.test_framework import BitcoinTestFramework13from test_framework.util import random_bitflip14from test_framework.v2_p2p import (15EncryptedP2PState,16MAX_GARBAGE_LEN,17)20class TestType(Enum):21""" Scenarios to be tested:231. EARLY_KEY_RESPONSE - The responder needs to wait until one byte is received which does not match the 16 bytes24consisting of network magic followed by "version\x00\x00\x00\x00\x00" before sending out its ellswift + garbage bytes252. EXCESS_GARBAGE - Disconnection happens when > MAX_GARBAGE_LEN bytes garbage is sent263. WRONG_GARBAGE_TERMINATOR - Disconnection happens when incorrect garbage terminator is sent274. WRONG_GARBAGE - Disconnection happens when garbage bytes that is sent is different from what the peer receives285. SEND_NO_AAD - Disconnection happens when AAD of first encrypted packet after the garbage terminator is not filled296. SEND_NON_EMPTY_VERSION_PACKET - non-empty version packet is simply ignored30"""31EARLY_KEY_RESPONSE = 032EXCESS_GARBAGE = 133WRONG_GARBAGE_TERMINATOR = 234WRONG_GARBAGE = 335SEND_NO_AAD = 436SEND_NON_EMPTY_VERSION_PACKET = 539class EarlyKeyResponseState(EncryptedP2PState):40""" Modify v2 P2P protocol functions for testing EARLY_KEY_RESPONSE scenario"""41def __init__(self, initiating, net):42super().__init__(initiating=initiating, net=net)43self.can_data_be_received = False # variable used to assert if data is received on recvbuf.45def initiate_v2_handshake(self):46"""Send ellswift and garbage bytes in 2 parts when TestType = (EARLY_KEY_RESPONSE)"""47self.generate_keypair_and_garbage()48return b""51class ExcessGarbageState(EncryptedP2PState):52"""Generate > MAX_GARBAGE_LEN garbage bytes"""53def generate_keypair_and_garbage(self):54garbage_len = MAX_GARBAGE_LEN + random.randrange(1, MAX_GARBAGE_LEN + 1)55return super().generate_keypair_and_garbage(garbage_len)58class WrongGarbageTerminatorState(EncryptedP2PState):59"""Add option for sending wrong garbage terminator"""60def generate_keypair_and_garbage(self):61garbage_len = random.randrange(MAX_GARBAGE_LEN//2)62return super().generate_keypair_and_garbage(garbage_len)64def complete_handshake(self, response):65length, handshake_bytes = super().complete_handshake(response)66# first 16 bytes returned by complete_handshake() is the garbage terminator67wrong_garbage_terminator = random_bitflip(handshake_bytes[:16])68return length, wrong_garbage_terminator + handshake_bytes[16:]71class WrongGarbageState(EncryptedP2PState):72"""Generate tampered garbage bytes"""73def generate_keypair_and_garbage(self):74garbage_len = random.randrange(1, MAX_GARBAGE_LEN)75ellswift_garbage_bytes = super().generate_keypair_and_garbage(garbage_len)76# assume that garbage bytes sent to TestNode were tampered with77return ellswift_garbage_bytes[:64] + random_bitflip(ellswift_garbage_bytes[64:])80class NoAADState(EncryptedP2PState):81"""Add option for not filling first encrypted packet after garbage terminator with AAD"""82def generate_keypair_and_garbage(self):83garbage_len = random.randrange(1, MAX_GARBAGE_LEN)84return super().generate_keypair_and_garbage(garbage_len)86def complete_handshake(self, response):87self.sent_garbage = b'' # do not authenticate the garbage which is sent88return super().complete_handshake(response)91class NonEmptyVersionPacketState(EncryptedP2PState):92""""Add option for sending non-empty transport version packet."""93def complete_handshake(self, response):94self.transport_version = random.randbytes(5)95return super().complete_handshake(response)98class MisbehavingV2Peer(P2PInterface):99"""Custom implementation of P2PInterface which uses modified v2 P2P protocol functions for testing purposes."""100def __init__(self, test_type):101super().__init__()102self.test_type = test_type104def connection_made(self, transport):105if self.test_type == TestType.EARLY_KEY_RESPONSE:106self.v2_state = EarlyKeyResponseState(initiating=True, net='regtest')107elif self.test_type == TestType.EXCESS_GARBAGE:108self.v2_state = ExcessGarbageState(initiating=True, net='regtest')109elif self.test_type == TestType.WRONG_GARBAGE_TERMINATOR:110self.v2_state = WrongGarbageTerminatorState(initiating=True, net='regtest')111elif self.test_type == TestType.WRONG_GARBAGE:112self.v2_state = WrongGarbageState(initiating=True, net='regtest')113elif self.test_type == TestType.SEND_NO_AAD:114self.v2_state = NoAADState(initiating=True, net='regtest')115elif TestType.SEND_NON_EMPTY_VERSION_PACKET:116self.v2_state = NonEmptyVersionPacketState(initiating=True, net='regtest')117super().connection_made(transport)119def data_received(self, t):120if self.test_type == TestType.EARLY_KEY_RESPONSE:121# check that data can be received on recvbuf only when mismatch from V1_PREFIX happens122assert self.v2_state.can_data_be_received123else:124super().data_received(t)127class EncryptedP2PMisbehaving(BitcoinTestFramework):128def set_test_params(self):129self.num_nodes = 1130self.extra_args = [["-v2transport=1", "-peertimeout=3"]]132def run_test(self):133self.test_earlykeyresponse()134self.test_v2disconnection()136def test_earlykeyresponse(self):137self.log.info('Sending ellswift bytes in parts to ensure that response from responder is received only when')138self.log.info('ellswift bytes have a mismatch from the 16 bytes(network magic followed by "version\\x00\\x00\\x00\\x00\\x00")')139node0 = self.nodes[0]140node0.setmocktime(int(time.time()))141self.log.info('Sending first 4 bytes of ellswift which match network magic')142self.log.info('If a response is received, assertion failure would happen in our custom data_received() function')143with node0.wait_for_new_peer():144peer1 = node0.add_p2p_connection(MisbehavingV2Peer(TestType.EARLY_KEY_RESPONSE), wait_for_verack=False, send_version=False, supports_v2_p2p=True, wait_for_v2_handshake=False)145peer1.send_raw_message(MAGIC_BYTES['regtest'])146self.log.info('Sending remaining ellswift and garbage which are different from V1_PREFIX. Since a response is')147self.log.info('expected now, our custom data_received() function wouldn\'t result in assertion failure')148peer1.v2_state.can_data_be_received = True149self.wait_until(lambda: peer1.v2_state.ellswift_ours)150peer1.send_raw_message(peer1.v2_state.ellswift_ours[4:] + peer1.v2_state.sent_garbage)151# Ensure that the bytes sent after 4 bytes network magic are actually received.152self.wait_until(lambda: node0.getpeerinfo()[-1]["bytesrecv"] > 4)153self.wait_until(lambda: node0.getpeerinfo()[-1]["bytessent"] > 0)154with node0.assert_debug_log(['V2 handshake timeout, disconnecting peer=0']):155node0.bumpmocktime(4) # `InactivityCheck()` triggers now156peer1.wait_for_disconnect(timeout=1)157self.log.info('successful disconnection since modified ellswift was sent as response')159def test_v2disconnection(self):160# test v2 disconnection scenarios161node0 = self.nodes[0]162expected_debug_message = [163[], # EARLY_KEY_RESPONSE164["V2 transport error: missing garbage terminator"], # EXCESS_GARBAGE165["V2 handshake timeout, disconnecting peer"], # WRONG_GARBAGE_TERMINATOR166["V2 transport error: packet decryption failure"], # WRONG_GARBAGE167["V2 transport error: packet decryption failure"], # SEND_NO_AAD168[], # SEND_NON_EMPTY_VERSION_PACKET169]170for test_type in TestType:171if test_type == TestType.EARLY_KEY_RESPONSE:172continue173elif test_type == TestType.SEND_NON_EMPTY_VERSION_PACKET:174node0.add_p2p_connection(MisbehavingV2Peer(test_type), wait_for_verack=True, send_version=True, supports_v2_p2p=True)175self.log.info(f"No disconnection for {test_type.name}")176else:177with node0.assert_debug_log(expected_debug_message[test_type.value], timeout=5):178node0.setmocktime(int(time.time()))179peer1 = node0.add_p2p_connection(MisbehavingV2Peer(test_type), wait_for_verack=False, send_version=False, supports_v2_p2p=True, expect_success=False)180# Make a passing connection for more robust disconnection checking.181peer2 = node0.add_p2p_connection(P2PInterface())182assert peer2.is_connected183node0.bumpmocktime(4) # `InactivityCheck()` triggers now184peer1.wait_for_disconnect()185self.log.info(f"Expected disconnection for {test_type.name}")188if __name__ == '__main__':189EncryptedP2PMisbehaving(__file__).main()
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.