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.5"""Class for v2 P2P protocol (see BIP 324)"""7import random9from .crypto.bip324_cipher import FSChaCha20Poly130510from .crypto.chacha20 import FSChaCha2011from .crypto.ellswift import ellswift_create, ellswift_ecdh_xonly12from .crypto.hkdf import hkdf_sha25613from .key import TaggedHash14from .messages import MAGIC_BYTES15from .util import assert_equal18CHACHA20POLY1305_EXPANSION = 1619HEADER_LEN = 120IGNORE_BIT_POS = 721LENGTH_FIELD_LEN = 322MAX_GARBAGE_LEN = 409524SHORTID = {251: b"addr",262: b"block",273: b"blocktxn",284: b"cmpctblock",295: b"feefilter",306: b"filteradd",317: b"filterclear",328: b"filterload",339: b"getblocks",3410: b"getblocktxn",3511: b"getdata",3612: b"getheaders",3713: b"headers",3814: b"inv",3915: b"mempool",4016: b"merkleblock",4117: b"notfound",4218: b"ping",4319: b"pong",4420: b"sendcmpct",4521: b"tx",4622: b"getcfilters",4723: b"cfilter",4824: b"getcfheaders",4925: b"cfheaders",5026: b"getcfcheckpt",5127: b"cfcheckpt",5228: b"addrv2",5337: b"feature",54}56# Dictionary which contains short message type ID for the P2P message57MSGTYPE_TO_SHORTID = {msgtype: shortid for shortid, msgtype in SHORTID.items()}60class EncryptedP2PState:61"""A class for managing the state when v2 P2P protocol is used. Performs initial v2 handshake and encrypts/decrypts62P2P messages. P2PConnection uses an object of this class.65Args:66initiating (bool): defines whether the P2PConnection is an initiator or responder.67- initiating = True for inbound connections in the test framework [TestNode <------- P2PConnection]68- initiating = False for outbound connections in the test framework [TestNode -------> P2PConnection]70net (string): chain used (regtest, signet etc..)72Methods:73perform an advanced form of diffie-hellman handshake to instantiate the encrypted transport. before exchanging74any P2P messages, 2 nodes perform this handshake in order to determine a shared secret that is unique to both75of them and use it to derive keys to encrypt/decrypt P2P messages.76- initial v2 handshakes is performed by: (see BIP324 section #overall-handshake-pseudocode)771. initiator using initiate_v2_handshake(), complete_handshake() and authenticate_handshake()782. responder using respond_v2_handshake(), complete_handshake() and authenticate_handshake()79- initialize_v2_transport() sets various BIP324 derived keys and ciphers.81encrypt/decrypt v2 P2P messages using v2_enc_packet() and v2_receive_packet().82"""83def __init__(self, *, initiating, net):84self.initiating = initiating # True if initiator85self.net = net86self.peer = {} # object with various BIP324 derived keys and ciphers87self.privkey_ours = None88self.ellswift_ours = None89self.sent_garbage = b""90self.received_garbage = b""91self.received_prefix = b"" # received ellswift bytes till the first mismatch from 16 bytes v1_prefix92self.tried_v2_handshake = False # True when the initial handshake is over93# stores length of packet contents to detect whether first 3 bytes (which contains length of packet contents)94# has been decrypted. set to -1 if decryption hasn't been done yet.95self.contents_len = -196self.found_garbage_terminator = False97self.transport_version = b''99@staticmethod100def v2_ecdh(priv, ellswift_theirs, ellswift_ours, initiating):101"""Compute BIP324 shared secret.103Returns:104bytes - BIP324 shared secret105"""106ecdh_point_x32 = ellswift_ecdh_xonly(ellswift_theirs, priv)107if initiating:108# Initiating, place our public key encoding first.109return TaggedHash("bip324_ellswift_xonly_ecdh", ellswift_ours + ellswift_theirs + ecdh_point_x32)110else:111# Responding, place their public key encoding first.112return TaggedHash("bip324_ellswift_xonly_ecdh", ellswift_theirs + ellswift_ours + ecdh_point_x32)114def generate_keypair_and_garbage(self, garbage_len=None):115"""Generates ellswift keypair and 4095 bytes garbage at max"""116self.privkey_ours, self.ellswift_ours = ellswift_create()117if garbage_len is None:118garbage_len = random.randrange(MAX_GARBAGE_LEN + 1)119self.sent_garbage = random.randbytes(garbage_len)120return self.ellswift_ours + self.sent_garbage122def initiate_v2_handshake(self):123"""Initiator begins the v2 handshake by sending its ellswift bytes and garbage125Returns:126bytes - bytes to be sent to the peer when starting the v2 handshake as an initiator127"""128return self.generate_keypair_and_garbage()130def respond_v2_handshake(self, response):131"""Responder begins the v2 handshake by sending its ellswift bytes and garbage. However, the responder132sends this after having received at least one byte that mismatches 16-byte v1_prefix.134Returns:1351. int - length of bytes that were consumed so that recvbuf can be updated1362. bytes - bytes to be sent to the peer when starting the v2 handshake as a responder.137- returns b"" if more bytes need to be received before we can respond and start the v2 handshake.138- returns -1 to downgrade the connection to v1 P2P.139"""140v1_prefix = MAGIC_BYTES[self.net] + b'version\x00\x00\x00\x00\x00'141while len(self.received_prefix) < 16:142byte = response.read(1)143# return b"" if we need to receive more bytes144if not byte:145return len(self.received_prefix), b""146self.received_prefix += byte147if self.received_prefix[-1] != v1_prefix[len(self.received_prefix) - 1]:148return len(self.received_prefix), self.generate_keypair_and_garbage()149# return -1 to decide v1 only after all 16 bytes processed150return len(self.received_prefix), -1152def complete_handshake(self, response):153""" Instantiates the encrypted transport and154sends garbage terminator + optional decoy packets + transport version packet.155Done by both initiator and responder.157Returns:1581. int - length of bytes that were consumed. returns 0 if all 64 bytes from ellswift haven't been received yet.1592. bytes - bytes to be sent to the peer when completing the v2 handshake160"""161ellswift_theirs = self.received_prefix + response.read(64 - len(self.received_prefix))162# return b"" if we need to receive more bytes163if len(ellswift_theirs) != 64:164return 0, b""165ecdh_secret = self.v2_ecdh(self.privkey_ours, ellswift_theirs, self.ellswift_ours, self.initiating)166self.initialize_v2_transport(ecdh_secret)167# Send garbage terminator168msg_to_send = self.peer['send_garbage_terminator']169# Optionally send decoy packets after garbage terminator.170aad = self.sent_garbage171for decoy_content_len in [random.randint(1, 100) for _ in range(random.randint(0, 10))]:172msg_to_send += self.v2_enc_packet(decoy_content_len * b'\x00', aad=aad, ignore=True)173aad = b''174# Send version packet.175msg_to_send += self.v2_enc_packet(self.transport_version, aad=aad)176return 64 - len(self.received_prefix), msg_to_send178def authenticate_handshake(self, response):179""" Ensures that the received optional decoy packets and transport version packet are authenticated.180Marks the v2 handshake as complete. Done by both initiator and responder.182Returns:1831. int - length of bytes that were processed so that recvbuf can be updated1842. bool - True if the authentication was successful/more bytes need to be received and False otherwise185"""186processed_length = 0188# Detect garbage terminator in the received bytes189if not self.found_garbage_terminator:190received_garbage = response[:16]191response = response[16:]192processed_length = len(received_garbage)193for i in range(MAX_GARBAGE_LEN + 1):194if received_garbage[-16:] == self.peer['recv_garbage_terminator']:195# Receive, decode, and ignore version packet.196# This includes skipping decoys and authenticating the received garbage.197self.found_garbage_terminator = True198self.received_garbage = received_garbage[:-16]199break200else:201# don't update recvbuf since more bytes need to be received202if len(response) == 0:203return 0, True204received_garbage += response[:1]205processed_length += 1206response = response[1:]207else:208# disconnect since garbage terminator was not seen after 4 KiB of garbage.209return processed_length, False211# Process optional decoy packets and transport version packet212while not self.tried_v2_handshake:213length, contents = self.v2_receive_packet(response, aad=self.received_garbage)214if length == -1:215return processed_length, False216elif length == 0:217return processed_length, True218processed_length += length219self.received_garbage = b""220# decoy packets have contents = None. v2 handshake is complete only when version packet221# (can be empty with contents = b"") with contents != None is received.222if contents is not None:223assert_equal(contents, b"") # currently TestNode sends an empty version packet224self.tried_v2_handshake = True225return processed_length, True226response = response[length:]228def initialize_v2_transport(self, ecdh_secret):229"""Sets the peer object with various BIP324 derived keys and ciphers."""230peer = {}231salt = b'bitcoin_v2_shared_secret' + MAGIC_BYTES[self.net]232for name in ('initiator_L', 'initiator_P', 'responder_L', 'responder_P', 'garbage_terminators', 'session_id'):233peer[name] = hkdf_sha256(salt=salt, ikm=ecdh_secret, info=name.encode('utf-8'), length=32)234if self.initiating:235self.peer['send_L'] = FSChaCha20(peer['initiator_L'])236self.peer['send_P'] = FSChaCha20Poly1305(peer['initiator_P'])237self.peer['send_garbage_terminator'] = peer['garbage_terminators'][:16]238self.peer['recv_L'] = FSChaCha20(peer['responder_L'])239self.peer['recv_P'] = FSChaCha20Poly1305(peer['responder_P'])240self.peer['recv_garbage_terminator'] = peer['garbage_terminators'][16:]241else:242self.peer['send_L'] = FSChaCha20(peer['responder_L'])243self.peer['send_P'] = FSChaCha20Poly1305(peer['responder_P'])244self.peer['send_garbage_terminator'] = peer['garbage_terminators'][16:]245self.peer['recv_L'] = FSChaCha20(peer['initiator_L'])246self.peer['recv_P'] = FSChaCha20Poly1305(peer['initiator_P'])247self.peer['recv_garbage_terminator'] = peer['garbage_terminators'][:16]248self.peer['session_id'] = peer['session_id']250def v2_enc_packet(self, contents, aad=b'', ignore=False):
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.