email at this domain
yancy at irc.libera.chat
This site is Lynx (web browser) friendly (mostly).
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
gspZr68=
=YIfr
-----END PGP PUBLIC KEY BLOCK-----
02/26/26
As of late, I've been debugging a Bitcoin P2P application. In order to better understand the
communication that's happening under the hood, I resorted to running a tcpdump which captures
packets between said application and Bitcoin core. Note that my experimentation thus far is
using the V1 protocol which is un-encrypted.
To initiate the packet capture with tcpdump:
sudo tcpdump -i `interface` -w `file` host `host ip`
where:
`host ip` is an instance of Bitcoin core
`interface` is my wireless nic wlp3s0
`file` is the location to save the packet capture (pcap file), like /tmp/dump
Then, to read the pcap file: tcpdump -qns 0 -X -r `file`
A Bitcoin P2P application begins communication with a handshake. This handshake specifies the
first message is a Version Message sent by the client. The Version Message is defined by the
Protocol documentation. According to the docs, all message structures, including version begin
with some header information (see Message Structure in proto docs above).
The Message structure memory layout is as follows:
magic: 4 bytes
command: 12 bytes
length: 4 bytes
checksum: 4 bytes
payload: variable size
In the case of Version, the "command" will be version in ASCII: 7665 7273 696f 6e00 0000 0000.
So, back to the packet capture, to find where the communication between said application and
Bitcoin core starts, I find the first occurrence of that ASCII. On my capture, that occurs on
the 6th packet midway in. The next 4 bytes directly following the command is the length of
the payload or message. For my capture, this looks like `6200 0000`. A quote from the
Protocol docs is "Almost all integers are encoded in little endian", so when we see something
like `6200 0000`, this tells me that the payload is 98 bytes long. The remaining 4 bytes are
the checksum which are un-important for now.
b247 e642 - no clue what this is - 4 bytes
7665 7273 696f 6e00 0000 0000 - this is the word version in hex - 12 bytes
6200 0000 - this is the length of the payload (body) which is 98 bytes - 4 bytes
ae4a f87f - this is the checksum - 4 bytes
As a side note, the 4 bytes directly proceeding the command should be the network magic.
However, `b247 e642` are the bytes directly proceeding version, and that does not translate
to any know "magic values" in the docs.
Continuing on to the payload, AKA message body, which is of variable length, however in this
case is 98 bytes.
The payload, which is of type Version Message is as follows:
version: 4 bytes
services: 8 bytes
timestamp: 8 bytes
receive address: 26 bytes
from address: 26 bytes
nonce: 8 bytes
user agent: variable size
start height: 4 bytes
realy: 1 bytes
I find the next 4 bytes directly following checksum to be `7f11 0100` which translates to
70015 (little endian).
The payload continues as follows:
7f11 0100 - this is the protocol version, which in decimal is 70015 (little endian) - 4 bytes
0000 0000 0000 0000 - services - 8 bytes
a83f 9c69 0000 0000 - timestamp - 8 bytes
0000 0000 0000 0000 0000 0000 0000 0000 0000 ffff 7f00 0001 d431 - address rcv - 26 bytes
0000 0000 0000 0000 0000 0000 0000 0000 0000 ffff 7f00 0001 d431 - address send - 26 bytes
0000 0000 0000 0000 - nonce - 4 bytes
0c2f 6c6f 7265 3a30 2e30 2e31 2f - user agent - ?
0000 0000 - start height - 4 bytes
00 - relay - 1
User agent is a field to transmit the name of the software, whether it be Bitcoin Core or some
other client. Technically the field is of variable length, however since we know the length
of the payload, 98 bytes, and all other fields are of known size, we can also know the length
of user agent with some simple arithmetic: 98 - 4 - 8 - 8 - 26 - 26 - 8 - 4 - 1. Also notice
that the sending and the receiving address are the same. This is no doubt because this is
the first communication, so there is only really a sending address. It's curious though that
the receiving address would not simply be empty.
The response from Bitcoin core will be the version message, so I look for the next occurrence
of "version" in ASCII 7665 7273 696f 6e00 0000 0000. Using the version hex as an anchor, I look at
the proceeding and following bytes which reveal the following:
magic: fabf b5da
command: 7665 7273 696f 6e00 0000 0000
length: 6700 0000
checksum 4f01 cf12
payload: ?
This part threw me for a loop though. I was expecting to find the payload directly following
checksum, which is how the first version message by the initiating client appeared in the pcap
file. However, for whatever reason, bitcoin core sends the header and the message body in
two separate packets. There must be some bytes that go into the beginning of the header
which causes the pcap file to have a payload _which does not directly follow_ the checksum.
Looking halfway down the packet that follows Bitcoin Cores first packet response, I find:
version: 8011 0100 - protocol version 70016
services: 090c 0000
timestamp: 0000 0000
addr recv: 533f 9c69 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
addr from: 0000 0000 0000 0000 090c 0000 0000 0000 0000 0000 0000 0000 0000
nonce: 0000 0000
user agent: 0000 0000 43dc 33f0 6041 cf80 112f 5361 746f 7368 693a 3330 2e39 392e 302f
start height: 0000 0000
relay: 01
03/07/26
Continuing on from last post on 02/26, in which I detailed the start of the handshake
between two peers. After the version message is first sent from initiating client,
the receiving peer, a core node, responds sending back it's version message. The core node
now knows both the sender and it's own capable protocol version, so it computes the minimum
of the two versions. In the trace above, my initial request set protocol version `70015`
and the receiver, in this case a core node, responded with `70016`. Therefore, on the core
node side, the session protocol will be computed as `70015`.
In response to the version message, the core node sends a `verack` message, a header only
message without a body. You might say, a disembodied message.
fabf b5da - network magic
7665 7261 636b 0000 0000 0000 - verack command
0000 0000 - note the length is zero signifying no body
5df6 e0e2 - checksum
The initiating client will do the same in response to the version message and set the
session protocol for communication going forward.
L -> R: Send version message with the local peer's version
R -> L: Send version message back
R -> L: Send verack message
R: Sets version to the minimum of the 2 versions
L -> R: Send verack message after receiving version message from R
L: Sets version to the minimum of the 2 versions
[0]
Now that we know the session protocol version `70015` AKA:
`ProtocolVersion::INVALID_CB_NO_BAN_VERSION` in rust-bitcoin land [1], we know what
messages to see next in our trace.
The next message in the trace is a `sendcmpct` which is sent from core to the initiating
client.
Here is the `sendcmpct` header:
fabf b5da - magic
7365 6e64 636d 7063 7400 0000 - verack cmd
0900 0000 - length 9
e92f 5ef8 - checksum
Tcpdump shows the size of the contained data in the next packet as `9` which matches what
we just saw in the header (length 9).
Body (payload):
00 - mode (low-bandwidth)
02 0000 0000 0000 00 - pch command
Further discussion of `sendcmpct` can be found here [2].
The very next message received from core is a ping command. Ping [3] is used in protocol
versions greater than `60000`, and since we negotiated a session version of `70015`,
needing to respond to ping is no surprise.
Ping Header:
fabf b5da - magic
7069 6e67 0000 0000 0000 0000 - ping command
0800 0000 - length
667e 2273 - checksum
Ping Body:
432f 5f84 912b c7de - nonce
The client which initiated the communication in the first place with core, ought now respond
with a Pong message using the corresponding nonce.
0. version handshake