-
-
Save buzzkillb/e50823e14839062e96542775594f0741 to your computer and use it in GitHub Desktop.
How to establish a connection to the bitcoin manually. Peer discovery + connecting and sending version message
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Import requests and regex library | |
import requests | |
import re | |
def get_external_ip(): | |
# Make a request to checkip.dyndns.org as proposed | |
# in https://en.bitcoin.it/wiki/Satoshi_Client_Node_Discovery#DNS_Addresses | |
response = requests.get('http://checkip.dyndns.org').text | |
# Filter the response with a regex for an IPv4 address | |
ip = re.search("(?:[0-9]{1,3}\.){3}[0-9]{1,3}", response).group() | |
return ip | |
external_ip = get_external_ip() | |
print(external_ip) | |
# Import socket and time library | |
import socket | |
import time | |
def get_node_addresses(): | |
# The list of seeds as hardcoded in a Bitcoin client | |
# view https://en.bitcoin.it/wiki/Satoshi_Client_Node_Discovery#DNS_Addresses | |
dns_seeds = [ | |
("seed.bitcoin.sipa.be", 8333), | |
("dnsseed.bluematt.me", 8333), | |
("dnsseed.bitcoin.dashjr.org", 8333), | |
("seed.bitcoinstats.com", 8333), | |
("seed.bitnodes.io", 8333), | |
("bitseed.xf2.org", 8333), | |
] | |
# The list where we store our found peers | |
found_peers = [] | |
try: | |
# Loop our seed list | |
for (ip_address, port) in dns_seeds: | |
index = 0 | |
# Connect to a dns address and get the A records | |
for info in socket.getaddrinfo(ip_address, port, | |
socket.AF_INET, socket.SOCK_STREAM, | |
socket.IPPROTO_TCP): | |
# The IP address and port is at index [4][0] | |
# for example: ('13.250.46.106', 8333) | |
found_peers.append((info[4][0], info[4][1])) | |
except Exception: | |
return found_peers | |
peers = get_node_addresses() | |
print(peers) | |
import struct | |
import random | |
import hashlib | |
# Create a streaming socket | |
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
# Connect to the first responding peer from our dns list | |
def connect(peer_index): | |
try: | |
print("Trying to connect to ", peers[peer_index]) | |
err = sock.connect(peers[peer_index]) | |
return peer_index | |
except Exception: | |
# Sidenote: Recursive call to test the next peer | |
# You would it not do like this in a real world, but it is for educational purposes only | |
return connect(peer_index+1) | |
peer_index = connect(0) | |
def create_version_message(): | |
# Encode all values to the right binary representation on https://bitcoin.org/en/developer-reference#version | |
# And https://docs.python.org/3/library/struct.html#format-characters | |
# The current protocol version, look it up under https://bitcoin.org/en/developer-reference#protocol-versions | |
version = struct.pack("i", 70015) | |
# Services that we support, can be either full-node (1) or not full-node (0) | |
services = struct.pack("Q", 0) | |
# The current timestamp | |
timestamp = struct.pack("q", int(time.time())) | |
# Services that receiver supports | |
add_recv_services = struct.pack("Q", 0) | |
# The receiver's IP, we got it from the DNS example above | |
add_recv_ip = struct.pack(">16s", bytes(peers[peer_index][0], 'utf-8')) | |
# The receiver's port (Bitcoin default is 8333) | |
add_recv_port = struct.pack(">H", 8333) | |
# Should be identical to services, was added later by the protocol | |
add_trans_services = struct.pack("Q", 0) | |
# Our ip or 127.0.0.1 | |
add_trans_ip = struct.pack(">16s", bytes("127.0.0.1", 'utf-8')) | |
# Our port | |
add_trans_port = struct.pack(">H", 8333) | |
# A nonce to detect connections to ourself | |
# If we receive the same nonce that we sent, we want to connect to oursel | |
nonce = struct.pack("Q", random.getrandbits(64)) | |
# Can be a user agent like Satoshi:0.15.1, we leave it empty | |
user_agent_bytes = struct.pack("B", 0) | |
# The block starting height, you can find the latest on http://blockchain.info/ | |
starting_height = struct.pack("i", 525453) | |
# We do not relay data and thus want to prevent to get tx messages | |
relay = struct.pack("?", False) | |
# Let's combine everything to our payload | |
payload = version + services + timestamp + add_recv_services + add_recv_ip + add_recv_port + \ | |
add_trans_services + add_trans_ip + add_trans_port + nonce + user_agent_bytes + starting_height + relay | |
# To meet the protocol specifications, we also have to create a header | |
# The general header format is described here https://en.bitcoin.it/wiki/Protocol_documentation#Message_structure | |
# The magic bytes, indicate the initiating network (Mainnet or Testned) | |
# The known values can be found here https://en.bitcoin.it/wiki/Protocol_documentation#Common_structures | |
magic = bytes.fromhex("F9BEB4D9") | |
# The command we want to send e.g. version message | |
# This must be null padded to reach 12 bytes in total (version = 7 Bytes + 5 zero bytes) | |
command = b"version" + 5 * b"\00" | |
# The payload length | |
length = struct.pack("I", len(payload)) | |
# The checksum, combuted as described in https://en.bitcoin.it/wiki/Protocol_documentation#Message_structure | |
checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4] | |
# Build up the message | |
return magic + command + length + checksum + payload | |
# Send out our version message | |
sock.send(create_version_message()) | |
def encode_received_message(recv_message): | |
# Encode the magic number | |
recv_magic = recv_message[:4].hex() | |
# Encode the command (should be version) | |
recv_command = recv_message[4:16] | |
# Encode the payload length | |
recv_length = struct.unpack("I", recv_message[16:20]) | |
# Encode the checksum | |
recv_checksum = recv_message[20:24] | |
# Encode the payload (the rest) | |
recv_payload = recv_message[24:] | |
# Encode the version of the other peer | |
recv_version = struct.unpack("i", recv_payload[:4]) | |
return (recv_magic, recv_command, recv_length, recv_checksum, recv_payload, recv_version) | |
time.sleep(1) | |
# Receive the message | |
encoded_values = encode_received_message(sock.recv(8192)) | |
print("Version: ", encoded_values[-1]) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment