mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-05 05:14:51 +02:00
Merge bitcoin/bitcoin#29431: test/BIP324: disconnection scenarios during v2 handshake
c9dacd958dtest: Check that non empty version packet is ignored and no disconnection happens (stratospher)997cc00b95test: Check that disconnection happens when AAD isn't filled (stratospher)b5e6238fdbtest: Check that disconnection happens when garbage sent/received are different (stratospher)ad1482d5a2test: Check that disconnection happens when wrong garbage terminator is sent (stratospher)e351576862test: Check that disconnection happens when >4095 garbage bytes is sent (stratospher)e075fd131dtest: Introduce test types and modify v2 handshake function accordingly (stratospher)7d07daa623log: Add V2 handshake timeout (stratospher)d4a1da8543test: Make global TRANSPORT_VERSION variable an instance variable (stratospher)c642b08c4etest: Log when the garbage is actually sent to transport layer (stratospher)86cca2cba2test: Support disconnect waiting for add_p2p_connection (stratospher)bf9669af9ctest: Rename early key response test and move random_bitflip to util (stratospher) Pull request description: Add tests for the following v2 handshake scenarios: 1. Disconnection happens when > `MAX_GARBAGE_LEN` bytes garbage is sent 2. Disconnection happens when incorrect garbage terminator is sent 3. Disconnection happens when garbage bytes are tampered with 4. Disconnection happens when AAD of first encrypted packet after the garbage terminator is not filled 5. bitcoind ignores non-empty version packet and no disconnection happens All these tests require a modified v2 P2P class (different from `EncryptedP2PState` used in `v2_p2p.py`) to implement our custom handshake behaviour based on different scenarios and have been kept in a single test file (`test/functional/p2p_v2_misbehaving.py`). Shifted the test in `test/functional/p2p_v2_earlykeyresponse.py` which is of the same pattern to this file too. ACKs for top commit: achow101: ACKc9dacd958dmzumsande: ACKc9dacd958dtheStack: Code-review ACKc9dacd958dTree-SHA512: 90df81f0c7f4ecf0a47762d290a618ded92cde9f83d3ef3cc70e1b005ecb16125ec39a9d80ce95f99e695d29abd63443240cb5490aa57c5bc8fa2e52149a0672
This commit is contained in:
@@ -14,6 +14,7 @@ import random
|
||||
import unittest
|
||||
|
||||
from test_framework.crypto import secp256k1
|
||||
from test_framework.util import random_bitflip
|
||||
|
||||
# Point with no known discrete log.
|
||||
H_POINT = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"
|
||||
@@ -292,11 +293,6 @@ def sign_schnorr(key, msg, aux=None, flip_p=False, flip_r=False):
|
||||
class TestFrameworkKey(unittest.TestCase):
|
||||
def test_ecdsa_and_schnorr(self):
|
||||
"""Test the Python ECDSA and Schnorr implementations."""
|
||||
def random_bitflip(sig):
|
||||
sig = list(sig)
|
||||
sig[random.randrange(len(sig))] ^= (1 << (random.randrange(8)))
|
||||
return bytes(sig)
|
||||
|
||||
byte_arrays = [generate_privkey() for _ in range(3)] + [v.to_bytes(32, 'big') for v in [0, ORDER - 1, ORDER, 2**256 - 1]]
|
||||
keys = {}
|
||||
for privkey_bytes in byte_arrays: # build array of key/pubkey pairs
|
||||
|
||||
@@ -223,6 +223,7 @@ class P2PConnection(asyncio.Protocol):
|
||||
# send the initial handshake immediately
|
||||
if self.supports_v2_p2p and self.v2_state.initiating and not self.v2_state.tried_v2_handshake:
|
||||
send_handshake_bytes = self.v2_state.initiate_v2_handshake()
|
||||
logger.debug(f"sending {len(self.v2_state.sent_garbage)} bytes of garbage data")
|
||||
self.send_raw_message(send_handshake_bytes)
|
||||
# for v1 outbound connections, send version message immediately after opening
|
||||
# (for v2 outbound connections, send it after the initial v2 handshake)
|
||||
@@ -262,6 +263,7 @@ class P2PConnection(asyncio.Protocol):
|
||||
self.v2_state = None
|
||||
return
|
||||
elif send_handshake_bytes:
|
||||
logger.debug(f"sending {len(self.v2_state.sent_garbage)} bytes of garbage data")
|
||||
self.send_raw_message(send_handshake_bytes)
|
||||
elif send_handshake_bytes == b"":
|
||||
return # only after send_handshake_bytes are sent can `complete_handshake()` be done
|
||||
|
||||
@@ -666,7 +666,7 @@ class TestNode():
|
||||
assert_msg += "with expected error " + expected_msg
|
||||
self._raise_assertion_error(assert_msg)
|
||||
|
||||
def add_p2p_connection(self, p2p_conn, *, wait_for_verack=True, send_version=True, supports_v2_p2p=None, wait_for_v2_handshake=True, **kwargs):
|
||||
def add_p2p_connection(self, p2p_conn, *, wait_for_verack=True, send_version=True, supports_v2_p2p=None, wait_for_v2_handshake=True, expect_success=True, **kwargs):
|
||||
"""Add an inbound p2p connection to the node.
|
||||
|
||||
This method adds the p2p connection to the self.p2ps list and also
|
||||
@@ -686,7 +686,6 @@ class TestNode():
|
||||
if supports_v2_p2p is None:
|
||||
supports_v2_p2p = self.use_v2transport
|
||||
|
||||
|
||||
p2p_conn.p2p_connected_to_node = True
|
||||
if self.use_v2transport:
|
||||
kwargs['services'] = kwargs.get('services', P2P_SERVICES) | NODE_P2P_V2
|
||||
@@ -694,6 +693,8 @@ class TestNode():
|
||||
p2p_conn.peer_connect(**kwargs, send_version=send_version, net=self.chain, timeout_factor=self.timeout_factor, supports_v2_p2p=supports_v2_p2p)()
|
||||
|
||||
self.p2ps.append(p2p_conn)
|
||||
if not expect_success:
|
||||
return p2p_conn
|
||||
p2p_conn.wait_until(lambda: p2p_conn.is_connected, check_connected=False)
|
||||
if supports_v2_p2p and wait_for_v2_handshake:
|
||||
p2p_conn.wait_until(lambda: p2p_conn.v2_state.tried_v2_handshake)
|
||||
|
||||
@@ -14,6 +14,7 @@ import logging
|
||||
import os
|
||||
import pathlib
|
||||
import platform
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
|
||||
@@ -247,6 +248,12 @@ def ceildiv(a, b):
|
||||
return -(-a // b)
|
||||
|
||||
|
||||
def random_bitflip(data):
|
||||
data = list(data)
|
||||
data[random.randrange(len(data))] ^= (1 << (random.randrange(8)))
|
||||
return bytes(data)
|
||||
|
||||
|
||||
def get_fee(tx_size, feerate_btc_kvb):
|
||||
"""Calculate the fee in BTC given a feerate is BTC/kvB. Reflects CFeeRate::GetFee"""
|
||||
feerate_sat_kvb = int(feerate_btc_kvb * Decimal(1e8)) # Fee in sat/kvb as an int to avoid float precision errors
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Class for v2 P2P protocol (see BIP 324)"""
|
||||
|
||||
import logging
|
||||
import random
|
||||
|
||||
from .crypto.bip324_cipher import FSChaCha20Poly1305
|
||||
@@ -14,14 +13,12 @@ from .crypto.hkdf import hkdf_sha256
|
||||
from .key import TaggedHash
|
||||
from .messages import MAGIC_BYTES
|
||||
|
||||
logger = logging.getLogger("TestFramework.v2_p2p")
|
||||
|
||||
CHACHA20POLY1305_EXPANSION = 16
|
||||
HEADER_LEN = 1
|
||||
IGNORE_BIT_POS = 7
|
||||
LENGTH_FIELD_LEN = 3
|
||||
MAX_GARBAGE_LEN = 4095
|
||||
TRANSPORT_VERSION = b''
|
||||
|
||||
SHORTID = {
|
||||
1: b"addr",
|
||||
@@ -95,6 +92,7 @@ class EncryptedP2PState:
|
||||
# has been decrypted. set to -1 if decryption hasn't been done yet.
|
||||
self.contents_len = -1
|
||||
self.found_garbage_terminator = False
|
||||
self.transport_version = b''
|
||||
|
||||
@staticmethod
|
||||
def v2_ecdh(priv, ellswift_theirs, ellswift_ours, initiating):
|
||||
@@ -111,12 +109,12 @@ class EncryptedP2PState:
|
||||
# Responding, place their public key encoding first.
|
||||
return TaggedHash("bip324_ellswift_xonly_ecdh", ellswift_theirs + ellswift_ours + ecdh_point_x32)
|
||||
|
||||
def generate_keypair_and_garbage(self):
|
||||
def generate_keypair_and_garbage(self, garbage_len=None):
|
||||
"""Generates ellswift keypair and 4095 bytes garbage at max"""
|
||||
self.privkey_ours, self.ellswift_ours = ellswift_create()
|
||||
garbage_len = random.randrange(MAX_GARBAGE_LEN + 1)
|
||||
if garbage_len is None:
|
||||
garbage_len = random.randrange(MAX_GARBAGE_LEN + 1)
|
||||
self.sent_garbage = random.randbytes(garbage_len)
|
||||
logger.debug(f"sending {garbage_len} bytes of garbage data")
|
||||
return self.ellswift_ours + self.sent_garbage
|
||||
|
||||
def initiate_v2_handshake(self):
|
||||
@@ -172,7 +170,7 @@ class EncryptedP2PState:
|
||||
msg_to_send += self.v2_enc_packet(decoy_content_len * b'\x00', aad=aad, ignore=True)
|
||||
aad = b''
|
||||
# Send version packet.
|
||||
msg_to_send += self.v2_enc_packet(TRANSPORT_VERSION, aad=aad)
|
||||
msg_to_send += self.v2_enc_packet(self.transport_version, aad=aad)
|
||||
return 64 - len(self.received_prefix), msg_to_send
|
||||
|
||||
def authenticate_handshake(self, response):
|
||||
|
||||
Reference in New Issue
Block a user