Skip to content

Commit bc9283c

Browse files
committed
[test] Add functional test to test early key response behaviour in BIP 324
- A node initiates a v2 connection by sending 64 bytes ellswift - In BIP 324 "The responder waits until one byte is received which does not match the V1_PREFIX (16 bytes consisting of the network magic followed by "version\x00\x00\x00\x00\x00".)" - It's possible that the 64 bytes ellswift sent by an initiator starts with a prefix of V1_PREFIX - Example form of 64 bytes ellswift could be: 4 bytes network magic + 60 bytes which aren't prefixed with remaining V1_PREFIX - We test this behaviour: - when responder receives 4 byte network magic -> no response received by initiator - when first mismatch happens -> response received by initiator
1 parent ffe6a56 commit bc9283c

File tree

2 files changed

+86
-0
lines changed

2 files changed

+86
-0
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2022 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
import random
7+
8+
from test_framework.test_framework import BitcoinTestFramework
9+
from test_framework.crypto.ellswift import ellswift_create
10+
from test_framework.p2p import P2PInterface
11+
from test_framework.v2_p2p import EncryptedP2PState
12+
13+
14+
class TestEncryptedP2PState(EncryptedP2PState):
15+
""" Modify v2 P2P protocol functions for testing that "The responder waits until one byte is received which does
16+
not match the 16 bytes consisting of the network magic followed by "version\x00\x00\x00\x00\x00"." (see BIP 324)
17+
18+
- if `send_net_magic` is True, send first 4 bytes of ellswift (match network magic) else send remaining 60 bytes
19+
- `can_data_be_received` is a variable used to assert if data is received on recvbuf.
20+
- v2 TestNode shouldn't respond back if we send V1_PREFIX and data shouldn't be received on recvbuf.
21+
This state is represented using `can_data_be_received` = False.
22+
- v2 TestNode responds back when mismatch from V1_PREFIX happens and data can be received on recvbuf.
23+
This state is represented using `can_data_be_received` = True.
24+
"""
25+
26+
def __init__(self):
27+
super().__init__(initiating=True, net='regtest')
28+
self.send_net_magic = True
29+
self.can_data_be_received = False
30+
31+
def initiate_v2_handshake(self, garbage_len=random.randrange(4096)):
32+
"""Initiator begins the v2 handshake by sending its ellswift bytes and garbage.
33+
Here, the 64 bytes ellswift is assumed to have it's 4 bytes match network magic bytes. It is sent in 2 phases:
34+
1. when `send_network_magic` = True, send first 4 bytes of ellswift (matches network magic bytes)
35+
2. when `send_network_magic` = False, send remaining 60 bytes of ellswift
36+
"""
37+
if self.send_net_magic:
38+
self.privkey_ours, self.ellswift_ours = ellswift_create()
39+
self.sent_garbage = random.randbytes(garbage_len)
40+
self.send_net_magic = False
41+
return b"\xfa\xbf\xb5\xda"
42+
else:
43+
self.can_data_be_received = True
44+
return self.ellswift_ours[4:] + self.sent_garbage
45+
46+
47+
class PeerEarlyKey(P2PInterface):
48+
"""Custom implementation of P2PInterface which uses modified v2 P2P protocol functions for testing purposes."""
49+
def __init__(self):
50+
super().__init__()
51+
self.v2_state = None
52+
53+
def connection_made(self, transport):
54+
"""64 bytes ellswift is sent in 2 parts during `initial_v2_handshake()`"""
55+
self.v2_state = TestEncryptedP2PState()
56+
super().connection_made(transport)
57+
58+
def data_received(self, t):
59+
# check that data can be received on recvbuf only when mismatch from V1_PREFIX happens (send_net_magic = False)
60+
assert self.v2_state.can_data_be_received and not self.v2_state.send_net_magic
61+
62+
63+
class P2PEarlyKey(BitcoinTestFramework):
64+
def set_test_params(self):
65+
self.num_nodes = 1
66+
self.extra_args = [["-v2transport=1", "-peertimeout=3"]]
67+
68+
def run_test(self):
69+
self.log.info('Sending ellswift bytes in parts to ensure that response from responder is received only when')
70+
self.log.info('ellswift bytes have a mismatch from the 16 bytes(network magic followed by "version\\x00\\x00\\x00\\x00\\x00")')
71+
node0 = self.nodes[0]
72+
self.log.info('Sending first 4 bytes of ellswift which match network magic')
73+
self.log.info('If a response is received, assertion failure would happen in our custom data_received() function')
74+
# send happens in `initiate_v2_handshake()` in `connection_made()`
75+
peer1 = node0.add_p2p_connection(PeerEarlyKey(), wait_for_verack=False, send_version=False, supports_v2_p2p=True)
76+
self.log.info('Sending remaining ellswift and garbage which are different from V1_PREFIX. Since a response is')
77+
self.log.info('expected now, our custom data_received() function wouldn\'t result in assertion failure')
78+
ellswift_and_garbage_data = peer1.v2_state.initiate_v2_handshake()
79+
peer1.send_raw_message(ellswift_and_garbage_data)
80+
peer1.wait_for_disconnect(timeout=5)
81+
self.log.info('successful disconnection when MITM happens in the key exchange phase')
82+
83+
84+
if __name__ == '__main__':
85+
P2PEarlyKey().main()

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@
260260
'p2p_invalid_tx.py --v2transport',
261261
'p2p_v2_transport.py',
262262
'p2p_v2_encrypted.py',
263+
'p2p_v2_earlykeyresponse.py',
263264
'example_test.py',
264265
'wallet_txn_doublespend.py --legacy-wallet',
265266
'wallet_multisig_descriptor_psbt.py --descriptors',

0 commit comments

Comments
 (0)