diff options
author | fanquake <fanquake@gmail.com> | 2023-09-11 09:29:40 +0100 |
---|---|---|
committer | fanquake <fanquake@gmail.com> | 2023-09-11 09:44:38 +0100 |
commit | 8f7b9eb8711fdb32e8bdb59d2a7462a46c7a8086 (patch) | |
tree | e580722b499779ae14f9dd5e7b6e15d086d4aa68 | |
parent | c5a63ea56f8347139bd84e1669b378ecfb234c3c (diff) | |
parent | 64704386b28ce3a1ab607a946ec729286da8faa6 (diff) |
Merge bitcoin/bitcoin#28433: Follow-up to BIP324 connection support
64704386b28ce3a1ab607a946ec729286da8faa6 doc: fix typos and mistakes in BIP324 code comments (Pieter Wuille)
9bde93df2c84b6d5f333aa56cbd0b28b6ad337b0 net: do not use send buffer to store/cache garbage (Pieter Wuille)
b6934fd03f080d437acb1fd2b665503c3d6de785 net: merge V2Transport constructors, move key gen (Pieter Wuille)
Pull request description:
This addresses a few remaining comments on #28196:
* Deduplicate the `V2Transport` constructors (https://github.com/bitcoin/bitcoin/pull/28196#discussion_r1318573111)
* Do not use the send buffer to store garbage (https://github.com/bitcoin/bitcoin/pull/28196#discussion_r1319134141)
* Fix typo (https://github.com/bitcoin/bitcoin/pull/28196#discussion_r1315179378)
In addition, also fix an incorrect description in `V2Transport::SendState` (it claimed garbage was sent in the `READY` state, but it's in the `AWAITING_KEY` state).
ACKs for top commit:
naumenkogs:
ACK 64704386b28ce3a1ab607a946ec729286da8faa6
theStack:
Code-review ACK 64704386b28ce3a1ab607a946ec729286da8faa6
Tree-SHA512: 4bf6d2fe73c8054502d0b60e9de1722f8b3dd269c2dd6bf67197c3fb6eabcf047b6360cdab3c1fd5504215c2ac4ac2890a022780efc30ff583776242c8112451
-rw-r--r-- | src/bip324.cpp | 7 | ||||
-rw-r--r-- | src/bip324.h | 4 | ||||
-rw-r--r-- | src/net.cpp | 73 | ||||
-rw-r--r-- | src/net.h | 22 | ||||
-rw-r--r-- | src/test/fuzz/p2p_transport_serialization.cpp | 2 | ||||
-rw-r--r-- | src/test/net_tests.cpp | 11 |
6 files changed, 72 insertions, 47 deletions
diff --git a/src/bip324.cpp b/src/bip324.cpp index 314e756829..f579a25193 100644 --- a/src/bip324.cpp +++ b/src/bip324.cpp @@ -22,13 +22,6 @@ #include <iterator> #include <string> -BIP324Cipher::BIP324Cipher() noexcept -{ - m_key.MakeNewKey(true); - uint256 entropy = GetRandHash(); - m_our_pubkey = m_key.EllSwiftCreate(MakeByteSpan(entropy)); -} - BIP324Cipher::BIP324Cipher(const CKey& key, Span<const std::byte> ent32) noexcept : m_key(key) { diff --git a/src/bip324.h b/src/bip324.h index 0238c479c0..28e7c411ea 100644 --- a/src/bip324.h +++ b/src/bip324.h @@ -41,8 +41,8 @@ private: std::array<std::byte, GARBAGE_TERMINATOR_LEN> m_recv_garbage_terminator; public: - /** Initialize a BIP324 cipher with securely generated random keys. */ - BIP324Cipher() noexcept; + /** No default constructor; keys must be provided to create a BIP324Cipher. */ + BIP324Cipher() = delete; /** Initialize a BIP324 cipher with specified key and encoding entropy (testing only). */ BIP324Cipher(const CKey& key, Span<const std::byte> ent32) noexcept; diff --git a/src/net.cpp b/src/net.cpp index 3955005dfa..af2855932d 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -979,36 +979,56 @@ public: const V2MessageMap V2_MESSAGE_MAP; -} // namespace +CKey GenerateRandomKey() noexcept +{ + CKey key; + key.MakeNewKey(/*fCompressed=*/true); + return key; +} -V2Transport::V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in) noexcept : - m_cipher{}, m_initiating{initiating}, m_nodeid{nodeid}, - m_v1_fallback{nodeid, type_in, version_in}, m_recv_type{type_in}, m_recv_version{version_in}, - m_recv_state{initiating ? RecvState::KEY : RecvState::KEY_MAYBE_V1}, - m_send_state{initiating ? SendState::AWAITING_KEY : SendState::MAYBE_V1} +std::vector<uint8_t> GenerateRandomGarbage() noexcept { - // Construct garbage (including its length) using a FastRandomContext. + std::vector<uint8_t> ret; FastRandomContext rng; - size_t garbage_len = rng.randrange(MAX_GARBAGE_LEN + 1); - // Initialize the send buffer with ellswift pubkey + garbage. - m_send_buffer.resize(EllSwiftPubKey::size() + garbage_len); + ret.resize(rng.randrange(V2Transport::MAX_GARBAGE_LEN + 1)); + rng.fillrand(MakeWritableByteSpan(ret)); + return ret; +} + +} // namespace + +void V2Transport::StartSendingHandshake() noexcept +{ + AssertLockHeld(m_send_mutex); + Assume(m_send_state == SendState::AWAITING_KEY); + Assume(m_send_buffer.empty()); + // Initialize the send buffer with ellswift pubkey + provided garbage. + m_send_buffer.resize(EllSwiftPubKey::size() + m_send_garbage.size()); std::copy(std::begin(m_cipher.GetOurPubKey()), std::end(m_cipher.GetOurPubKey()), MakeWritableByteSpan(m_send_buffer).begin()); - rng.fillrand(MakeWritableByteSpan(m_send_buffer).subspan(EllSwiftPubKey::size())); + std::copy(m_send_garbage.begin(), m_send_garbage.end(), m_send_buffer.begin() + EllSwiftPubKey::size()); + // We cannot wipe m_send_garbage as it will still be used to construct the garbage + // authentication packet. } -V2Transport::V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in, const CKey& key, Span<const std::byte> ent32, Span<const uint8_t> garbage) noexcept : +V2Transport::V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in, const CKey& key, Span<const std::byte> ent32, std::vector<uint8_t> garbage) noexcept : m_cipher{key, ent32}, m_initiating{initiating}, m_nodeid{nodeid}, m_v1_fallback{nodeid, type_in, version_in}, m_recv_type{type_in}, m_recv_version{version_in}, m_recv_state{initiating ? RecvState::KEY : RecvState::KEY_MAYBE_V1}, + m_send_garbage{std::move(garbage)}, m_send_state{initiating ? SendState::AWAITING_KEY : SendState::MAYBE_V1} { - assert(garbage.size() <= MAX_GARBAGE_LEN); - // Initialize the send buffer with ellswift pubkey + provided garbage. - m_send_buffer.resize(EllSwiftPubKey::size() + garbage.size()); - std::copy(std::begin(m_cipher.GetOurPubKey()), std::end(m_cipher.GetOurPubKey()), MakeWritableByteSpan(m_send_buffer).begin()); - std::copy(garbage.begin(), garbage.end(), m_send_buffer.begin() + EllSwiftPubKey::size()); + Assume(m_send_garbage.size() <= MAX_GARBAGE_LEN); + // Start sending immediately if we're the initiator of the connection. + if (initiating) { + LOCK(m_send_mutex); + StartSendingHandshake(); + } } +V2Transport::V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in) noexcept : + V2Transport{nodeid, initiating, type_in, version_in, GenerateRandomKey(), + MakeByteSpan(GetRandHash()), GenerateRandomGarbage()} { } + void V2Transport::SetReceiveState(RecvState recv_state) noexcept { AssertLockHeld(m_recv_mutex); @@ -1087,9 +1107,10 @@ void V2Transport::ProcessReceivedMaybeV1Bytes() noexcept if (!std::equal(m_recv_buffer.begin(), m_recv_buffer.end(), v1_prefix.begin())) { // Mismatch with v1 prefix, so we can assume a v2 connection. SetReceiveState(RecvState::KEY); // Convert to KEY state, leaving received bytes around. - // Transition the sender to AWAITING_KEY state (if not already). + // Transition the sender to AWAITING_KEY state and start sending. LOCK(m_send_mutex); SetSendState(SendState::AWAITING_KEY); + StartSendingHandshake(); } else if (m_recv_buffer.size() == v1_prefix.size()) { // Full match with the v1 prefix, so fall back to v1 behavior. LOCK(m_send_mutex); @@ -1149,7 +1170,6 @@ bool V2Transport::ProcessReceivedKeyBytes() noexcept SetSendState(SendState::READY); // Append the garbage terminator to the send buffer. - size_t garbage_len = m_send_buffer.size() - EllSwiftPubKey::size(); m_send_buffer.resize(m_send_buffer.size() + BIP324Cipher::GARBAGE_TERMINATOR_LEN); std::copy(m_cipher.GetSendGarbageTerminator().begin(), m_cipher.GetSendGarbageTerminator().end(), @@ -1160,9 +1180,12 @@ bool V2Transport::ProcessReceivedKeyBytes() noexcept m_send_buffer.resize(m_send_buffer.size() + BIP324Cipher::EXPANSION); m_cipher.Encrypt( /*contents=*/{}, - /*aad=*/MakeByteSpan(m_send_buffer).subspan(EllSwiftPubKey::size(), garbage_len), + /*aad=*/MakeByteSpan(m_send_garbage), /*ignore=*/false, /*output=*/MakeWritableByteSpan(m_send_buffer).last(BIP324Cipher::EXPANSION)); + // We no longer need the garbage. + m_send_garbage.clear(); + m_send_garbage.shrink_to_fit(); // Construct version packet in the send buffer. m_send_buffer.resize(m_send_buffer.size() + BIP324Cipher::EXPANSION + VERSION_CONTENTS.size()); @@ -1532,9 +1555,7 @@ Transport::BytesToSend V2Transport::GetBytesToSend(bool have_next_message) const LOCK(m_send_mutex); if (m_send_state == SendState::V1) return m_v1_fallback.GetBytesToSend(have_next_message); - // We do not send anything in MAYBE_V1 state (as we don't know if the peer is v1 or v2), - // despite there being data in the send buffer in that state. - if (m_send_state == SendState::MAYBE_V1) return {{}, false, m_send_type}; + if (m_send_state == SendState::MAYBE_V1) Assume(m_send_buffer.empty()); Assume(m_send_pos <= m_send_buffer.size()); return { Span{m_send_buffer}.subspan(m_send_pos), @@ -1553,10 +1574,8 @@ void V2Transport::MarkBytesSent(size_t bytes_sent) noexcept m_send_pos += bytes_sent; Assume(m_send_pos <= m_send_buffer.size()); - // Only wipe the buffer when everything is sent in the READY state. In the AWAITING_KEY state - // we still need the garbage that's in the send buffer to construct the garbage authentication - // packet. - if (m_send_state == SendState::READY && m_send_pos == m_send_buffer.size()) { + // Wipe the buffer when everything is sent. + if (m_send_pos == m_send_buffer.size()) { m_send_pos = 0; m_send_buffer = {}; } @@ -540,25 +540,25 @@ private: enum class SendState : uint8_t { /** (Responder only) Not sending until v1 or v2 is detected. * - * This is the initial state for responders. The send buffer contains the public key to - * send, but nothing is sent in this state yet. When the receiver determines whether this + * This is the initial state for responders. The send buffer is empty. + * When the receiver determines whether this * is a V1 or V2 connection, the sender state becomes AWAITING_KEY (for v2) or V1 (for v1). */ MAYBE_V1, /** Waiting for the other side's public key. * - * This is the initial state for initiators. The public key is sent out. When the receiver - * receives the other side's public key and transitions to GARB_GARBTERM, the sender state - * becomes READY. */ + * This is the initial state for initiators. The public key and garbage is sent out. When + * the receiver receives the other side's public key and transitions to GARB_GARBTERM, the + * sender state becomes READY. */ AWAITING_KEY, /** Normal sending state. * * In this state, the ciphers are initialized, so packets can be sent. When this state is - * entered, the garbage, garbage terminator, garbage authentication packet, and version - * packet are appended to the send buffer (in addition to the key which may still be - * there). In this state a message can be provided if the send buffer is empty. */ + * entered, the garbage terminator, garbage authentication packet, and version + * packet are appended to the send buffer (in addition to the key and garbage which may + * still be there). In this state a message can be provided if the send buffer is empty. */ READY, /** This transport is using v1 fallback. @@ -601,6 +601,8 @@ private: std::vector<uint8_t> m_send_buffer GUARDED_BY(m_send_mutex); /** How many bytes from the send buffer have been sent so far. */ uint32_t m_send_pos GUARDED_BY(m_send_mutex) {0}; + /** The garbage sent, or to be sent (MAYBE_V1 and AWAITING_KEY state only). */ + std::vector<uint8_t> m_send_garbage GUARDED_BY(m_send_mutex); /** Type of the message being sent. */ std::string m_send_type GUARDED_BY(m_send_mutex); /** Current sender state. */ @@ -614,6 +616,8 @@ private: static std::optional<std::string> GetMessageType(Span<const uint8_t>& contents) noexcept; /** Determine how many received bytes can be processed in one go (not allowed in V1 state). */ size_t GetMaxBytesToProcess() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex); + /** Put our public key + garbage in the send buffer. */ + void StartSendingHandshake() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_send_mutex); /** Process bytes in m_recv_buffer, while in KEY_MAYBE_V1 state. */ void ProcessReceivedMaybeV1Bytes() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex, !m_send_mutex); /** Process bytes in m_recv_buffer, while in KEY state. */ @@ -636,7 +640,7 @@ public: V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in) noexcept; /** Construct a V2 transport with specified keys and garbage (test use only). */ - V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in, const CKey& key, Span<const std::byte> ent32, Span<const uint8_t> garbage) noexcept; + V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in, const CKey& key, Span<const std::byte> ent32, std::vector<uint8_t> garbage) noexcept; // Receive side functions. bool ReceivedMessageComplete() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex); diff --git a/src/test/fuzz/p2p_transport_serialization.cpp b/src/test/fuzz/p2p_transport_serialization.cpp index 6e3ef2a707..88d6e96eac 100644 --- a/src/test/fuzz/p2p_transport_serialization.cpp +++ b/src/test/fuzz/p2p_transport_serialization.cpp @@ -366,7 +366,7 @@ std::unique_ptr<Transport> MakeV2Transport(NodeId nodeid, bool initiator, RNG& r .Write(garb.data(), garb.size()) .Finalize(UCharCast(ent.data())); - return std::make_unique<V2Transport>(nodeid, initiator, SER_NETWORK, INIT_PROTO_VERSION, key, ent, garb); + return std::make_unique<V2Transport>(nodeid, initiator, SER_NETWORK, INIT_PROTO_VERSION, key, ent, std::move(garb)); } } // namespace diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 900e311d22..3eb7bdec62 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -1008,12 +1008,20 @@ BOOST_AUTO_TEST_CASE(advertise_local_address) namespace { +CKey GenerateRandomTestKey() noexcept +{ + CKey key; + uint256 key_data = InsecureRand256(); + key.Set(key_data.begin(), key_data.end(), true); + return key; +} + /** A class for scenario-based tests of V2Transport * * Each V2TransportTester encapsulates a V2Transport (the one being tested), and can be told to * interact with it. To do so, it also encapsulates a BIP324Cipher to act as the other side. A * second V2Transport is not used, as doing so would not permit scenarios that involve sending - * invalid data, or ones scenarios using BIP324 features that are not implemented on the sending + * invalid data, or ones using BIP324 features that are not implemented on the sending * side (like decoy packets). */ class V2TransportTester @@ -1031,6 +1039,7 @@ public: /** Construct a tester object. test_initiator: whether the tested transport is initiator. */ V2TransportTester(bool test_initiator) : m_transport(0, test_initiator, SER_NETWORK, INIT_PROTO_VERSION), + m_cipher{GenerateRandomTestKey(), MakeByteSpan(InsecureRand256())}, m_test_initiator(test_initiator) {} /** Data type returned by Interact: |