From 0aecda76ae1709c1ec088a4cae7eb123a8bb0001 Mon Sep 17 00:00:00 2001 From: rustaceanrob Date: Fri, 5 Sep 2025 16:09:36 +0100 Subject: [PATCH] protocol: Remove `bitcoin` for `secp256k1` To help with type safety a bit, I have public callsites use an `impl Borrow<[u8; 4]>`, which the `p2p::Magic` type implements. This way one can pass magic variables directly. In the handshake this just gets dereferenced anyway, but it still feels more ergonomic than forcing a `to_bytes` on the caller IMO. I keep the `secp256k1` the same, but maybe it can be bumped. I forget if there are problems when attempting to build conflicting versions or not. Concat usage was dropped in favor of explicit array handling. This was probably being iron'd out by the compiler before, but this patch introduces Borrow which broke no-std environment support. Co-authored-by: Nick Johnson --- protocol/Cargo.toml | 6 +- protocol/benches/cipher_session.rs | 7 +- protocol/examples/bufreader.rs | 8 +- protocol/fuzz/fuzz_targets/receive_garbage.rs | 10 +- protocol/fuzz/fuzz_targets/receive_key.rs | 7 +- protocol/src/futures.rs | 28 +- protocol/src/handshake.rs | 91 +++-- protocol/src/io.rs | 33 +- protocol/src/lib.rs | 103 +++--- protocol/src/serde.rs | 314 ------------------ protocol/tests/round_trips.rs | 69 ++-- traffic/src/futures.rs | 28 +- traffic/src/io.rs | 15 +- traffic/src/lib.rs | 8 +- traffic/tests/bitcoin_integration.rs | 4 +- 15 files changed, 205 insertions(+), 526 deletions(-) delete mode 100644 protocol/src/serde.rs diff --git a/protocol/Cargo.toml b/protocol/Cargo.toml index 846947c..bc80a6b 100644 --- a/protocol/Cargo.toml +++ b/protocol/Cargo.toml @@ -13,13 +13,13 @@ rust-version = "1.63.0" default = ["std"] # High-level wrappers using tokio traits - may affect MSRV requirements. tokio = ["std", "dep:tokio"] -std = ["bitcoin/rand-std", "bitcoin_hashes/std", "chacha20-poly1305/std"] +std = ["secp256k1/rand-std", "bitcoin_hashes/std", "chacha20-poly1305/std"] [dependencies] # The tokio feature may increase the MSRV beyond 1.63.0 # depending on which version of tokio is selected by the caller. tokio = { version = "1", default-features = false, optional = true, features = ["io-util"] } -bitcoin = { version = "0.32.4", default-features = false } +secp256k1 = { version = "0.29.0", default-features = false } # Depending on hashes directly for HKDF, can drop this and # use the re-exported version in bitcoin > 0.32.*. bitcoin_hashes = { version =">=0.15.0, <0.17.0", default-features = false } @@ -28,6 +28,8 @@ chacha20-poly1305 = { version = "0.1.1", default-features = false } [dev-dependencies] # bitcoind version 26.0 includes support for BIP-324's V2 protocol, but it is disabled by default. bitcoind = { package = "corepc-node", version = "0.7.1", default-features = false, features = ["26_0","download"] } +bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin", rev = "16cc257c3695dea0e7301a5fa9cab44b8ed60598" } +p2p = { package = "bitcoin-p2p-messages", git = "https://github.com/rust-bitcoin/rust-bitcoin", rev = "16cc257c3695dea0e7301a5fa9cab44b8ed60598" } hex = { package = "hex-conservative", version = "0.2.0" } tokio = { version = "1", features = ["io-util", "net", "rt-multi-thread", "macros"] } diff --git a/protocol/benches/cipher_session.rs b/protocol/benches/cipher_session.rs index f0f3be3..8bc5617 100644 --- a/protocol/benches/cipher_session.rs +++ b/protocol/benches/cipher_session.rs @@ -5,21 +5,22 @@ extern crate test; use bip324::{ - CipherSession, GarbageResult, Handshake, InboundCipher, Initialized, Network, OutboundCipher, + CipherSession, GarbageResult, Handshake, InboundCipher, Initialized, OutboundCipher, PacketType, ReceivedKey, Role, VersionResult, NUM_LENGTH_BYTES, }; +use p2p::Magic; use test::{black_box, Bencher}; fn create_cipher_session_pair() -> (CipherSession, CipherSession) { // Send Alice's key. - let alice_handshake = Handshake::::new(Network::Bitcoin, Role::Initiator).unwrap(); + let alice_handshake = Handshake::::new(Magic::BITCOIN, Role::Initiator).unwrap(); let mut alice_key_buffer = vec![0u8; Handshake::::send_key_len(None)]; let alice_handshake = alice_handshake .send_key(None, &mut alice_key_buffer) .unwrap(); // Send Bob's key - let bob_handshake = Handshake::::new(Network::Bitcoin, Role::Responder).unwrap(); + let bob_handshake = Handshake::::new(Magic::BITCOIN, Role::Responder).unwrap(); let mut bob_key_buffer = vec![0u8; Handshake::::send_key_len(None)]; let bob_handshake = bob_handshake.send_key(None, &mut bob_key_buffer).unwrap(); diff --git a/protocol/examples/bufreader.rs b/protocol/examples/bufreader.rs index 20d3299..d5a46b7 100644 --- a/protocol/examples/bufreader.rs +++ b/protocol/examples/bufreader.rs @@ -20,7 +20,7 @@ use bip324::futures::Protocol; use bip324::io::Payload; -use bip324::{Network, Role}; +use bip324::Role; use std::fmt; use std::time::{Duration, Instant}; use tokio::io::BufReader; @@ -127,7 +127,7 @@ async fn start_server( let (reader, writer) = stream.into_split(); let mut protocol = Protocol::new( - Network::Bitcoin, + p2p::Magic::BITCOIN, Role::Responder, None, None, @@ -180,7 +180,7 @@ impl Client { Client::Buffered => { let buffered_reader = BufReader::new(reader); let mut protocol = Protocol::new( - Network::Bitcoin, + p2p::Magic::BITCOIN, Role::Initiator, None, None, @@ -196,7 +196,7 @@ impl Client { } Client::NonBuffered => { let mut protocol = Protocol::new( - Network::Bitcoin, + p2p::Magic::BITCOIN, Role::Initiator, None, None, diff --git a/protocol/fuzz/fuzz_targets/receive_garbage.rs b/protocol/fuzz/fuzz_targets/receive_garbage.rs index 7aaff86..4b83037 100644 --- a/protocol/fuzz/fuzz_targets/receive_garbage.rs +++ b/protocol/fuzz/fuzz_targets/receive_garbage.rs @@ -3,10 +3,12 @@ //! Fuzz test for the receive_garbage function. #![no_main] -use bip324::{Handshake, Initialized, Network, ReceivedKey, Role}; +use bip324::{Handshake, Initialized, ReceivedKey, Role}; use libfuzzer_sys::fuzz_target; use rand::SeedableRng; +const MAGIC: [u8; 4] = [0xF9, 0xBE, 0xB4, 0xD9]; + fuzz_target!(|data: &[u8]| { // Cap input size to avoid wasting time on obviously invalid large inputs // The protocol limit is 4095 garbage bytes + 16 terminator bytes = 4111 total @@ -22,15 +24,13 @@ fuzz_target!(|data: &[u8]| { // Set up a valid handshake in the SentVersion state let initiator = - Handshake::::new_with_rng(Network::Bitcoin, Role::Initiator, &mut rng, &secp) - .unwrap(); + Handshake::::new_with_rng(MAGIC, Role::Initiator, &mut rng, &secp).unwrap(); let mut initiator_key = vec![0u8; Handshake::::send_key_len(None)]; let initiator = initiator.send_key(None, &mut initiator_key).unwrap(); let mut rng2 = rand::rngs::StdRng::from_seed([43u8; 32]); let responder = - Handshake::::new_with_rng(Network::Bitcoin, Role::Responder, &mut rng2, &secp) - .unwrap(); + Handshake::::new_with_rng(MAGIC, Role::Responder, &mut rng2, &secp).unwrap(); let mut responder_key = vec![0u8; Handshake::::send_key_len(None)]; let responder = responder.send_key(None, &mut responder_key).unwrap(); diff --git a/protocol/fuzz/fuzz_targets/receive_key.rs b/protocol/fuzz/fuzz_targets/receive_key.rs index 8ae2dd1..367c73f 100644 --- a/protocol/fuzz/fuzz_targets/receive_key.rs +++ b/protocol/fuzz/fuzz_targets/receive_key.rs @@ -5,10 +5,12 @@ //! This focused test fuzzes the elliptic curve point validation and ECDH logic. #![no_main] -use bip324::{Handshake, Initialized, Network, Role}; +use bip324::{Handshake, Initialized, Role}; use libfuzzer_sys::fuzz_target; use rand::SeedableRng; +const MAGIC: [u8; 4] = [0xF9, 0xBE, 0xB4, 0xD9]; + fuzz_target!(|data: &[u8]| { // We need exactly 64 bytes for an ElligatorSwift key. // This gives the fuzzer a clear signal about the expected input size. @@ -24,8 +26,7 @@ fuzz_target!(|data: &[u8]| { // Set up a handshake in the SentKey state. let handshake = - Handshake::::new_with_rng(Network::Bitcoin, Role::Initiator, &mut rng, &secp) - .unwrap(); + Handshake::::new_with_rng(MAGIC, Role::Initiator, &mut rng, &secp).unwrap(); let mut key_buffer = vec![0u8; Handshake::::send_key_len(None)]; let handshake = handshake.send_key(None, &mut key_buffer).unwrap(); diff --git a/protocol/src/futures.rs b/protocol/src/futures.rs index 7c75bac..b8d6526 100644 --- a/protocol/src/futures.rs +++ b/protocol/src/futures.rs @@ -15,7 +15,7 @@ //! ```no_run //! use bip324::futures::Protocol; //! use bip324::io::Payload; -//! use bip324::{Network, Role}; +//! use bip324::Role; //! use tokio::net::TcpStream; //! use tokio::io::BufReader; //! @@ -28,9 +28,12 @@ //! let (reader, writer) = stream.into_split(); //! let reader = BufReader::new(reader); //! +//! // Bitcoin mainnet magic bytes +//! let magic = [0xF9, 0xBE, 0xB4, 0xD9]; +//! //! // Establish BIP-324 encrypted connection //! let mut protocol = Protocol::new( -//! Network::Bitcoin, +//! magic, //! Role::Initiator, //! None, // no garbage bytes //! None, // no decoy packets @@ -46,12 +49,12 @@ //! # } //! ``` +use core::borrow::Borrow; use std::pin::Pin; use std::task::{Context, Poll}; use std::vec; use std::vec::Vec; -use bitcoin::Network; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use crate::{ @@ -146,7 +149,7 @@ impl AsyncRead for ProtocolSessionReader { /// /// * `Io` - Includes a flag for if the remote probably only understands the V1 protocol. pub async fn handshake( - network: Network, + magic: impl Borrow<[u8; 4]>, role: Role, garbage: Option>, decoys: Option>>, @@ -166,7 +169,7 @@ where .map(|vecs| vecs.iter().map(Vec::as_slice).collect()); let decoys_ref = decoy_refs.as_deref(); - let handshake = Handshake::::new(network, role)?; + let handshake = Handshake::::new(magic, role)?; // Send local public key and optional garbage. let key_buffer_len = Handshake::::send_key_len(garbage_ref); @@ -284,7 +287,7 @@ where /// /// * `Io` - Includes a flag for if the remote probably only understands the V1 protocol. pub async fn new( - network: Network, + magic: impl Borrow<[u8; 4]>, role: Role, garbage: Option>, decoys: Option>>, @@ -292,7 +295,7 @@ where mut writer: W, ) -> Result, ProtocolError> { let (inbound_cipher, outbound_cipher, session_reader) = - handshake(network, role, garbage, decoys, reader, &mut writer).await?; + handshake(magic, role, garbage, decoys, reader, &mut writer).await?; Ok(Protocol { reader: ProtocolReader { @@ -480,7 +483,8 @@ where #[cfg(test)] mod tests { use super::*; - use bitcoin::Network; + + const MAGIC: [u8; 4] = [0xF9, 0xBE, 0xB4, 0xD9]; #[tokio::test] async fn test_async_handshake_functions() { @@ -493,7 +497,7 @@ mod tests { let local_handshake = tokio::spawn(async move { handshake( - Network::Bitcoin, + MAGIC, Role::Initiator, Some(b"local garbage".to_vec()), Some(vec![b"local decoy".to_vec()]), @@ -505,7 +509,7 @@ mod tests { let remote_handshake = tokio::spawn(async move { handshake( - Network::Bitcoin, + MAGIC, Role::Responder, Some(b"remote garbage".to_vec()), Some(vec![b"remote decoy 1".to_vec(), b"remote decoy 2".to_vec()]), @@ -532,7 +536,7 @@ mod tests { let local_handshake = tokio::spawn(async move { handshake( - Network::Bitcoin, + MAGIC, Role::Initiator, None, None, @@ -545,7 +549,7 @@ mod tests { let remote_handshake = tokio::spawn(async move { let large_decoy = vec![0u8; MAX_PACKET_SIZE_FOR_ALLOCATION + 1]; handshake( - Network::Bitcoin, + MAGIC, Role::Responder, None, Some(vec![large_decoy]), diff --git a/protocol/src/handshake.rs b/protocol/src/handshake.rs index 1591d44..2bcc693 100644 --- a/protocol/src/handshake.rs +++ b/protocol/src/handshake.rs @@ -7,14 +7,11 @@ //! 3. **Decoy Packets**: Optional decoy packets can be sent to further obscure traffic patterns. //! 4. **Version Authentication**: Version packets are exchanged to negotiate the protocol version for the channel. //! 5. **Session Establishment**: The secure communication channel is ready for message exchange. +use core::borrow::Borrow; -use bitcoin::{ - key::Secp256k1, - secp256k1::{ - ellswift::{ElligatorSwift, ElligatorSwiftParty}, - PublicKey, SecretKey, Signing, - }, - Network, +use secp256k1::{ + ellswift::{ElligatorSwift, ElligatorSwiftParty}, + PublicKey, Secp256k1, SecretKey, Signing, }; use crate::{ @@ -91,8 +88,8 @@ pub enum VersionResult<'a> { /// 4. `SentVersion` - After sending local garbage terminator and version packet. /// 5. Complete - After receiving and authenticating remote's garbage, garbage terminator, decoy packets, and version packet. pub struct Handshake { - /// Bitcoin network both peers are operating on. - network: Network, + /// The network magic to use. + magic: [u8; 4], /// Local role in the handshake, initiator or responder. role: Role, /// State-specific data. @@ -102,8 +99,8 @@ pub struct Handshake { // Methods available in all states. impl Handshake { /// Get the network this handshake is operating on. - pub fn network(&self) -> Network { - self.network + pub fn magic(&self) -> &[u8; 4] { + &self.magic } /// Get the local role in the handshake. @@ -115,15 +112,15 @@ impl Handshake { impl Handshake { /// Initialize a V2 transport handshake with a remote peer. #[cfg(feature = "std")] - pub fn new(network: Network, role: Role) -> Result { - let mut rng = bitcoin::secp256k1::rand::thread_rng(); + pub fn new(magic: impl Borrow<[u8; 4]>, role: Role) -> Result { + let mut rng = secp256k1::rand::thread_rng(); let curve = Secp256k1::signing_only(); - Self::new_with_rng(network, role, &mut rng, &curve) + Self::new_with_rng(magic, role, &mut rng, &curve) } /// Initialize a V2 transport handshake with remote peer using supplied RNG and secp context. pub fn new_with_rng( - network: Network, + magic: impl Borrow<[u8; 4]>, role: Role, rng: &mut impl FillBytes, curve: &Secp256k1, @@ -141,7 +138,7 @@ impl Handshake { }; Ok(Handshake { - network, + magic: *magic.borrow(), role, state: Initialized { point }, }) @@ -198,7 +195,7 @@ impl Handshake { } Ok(Handshake { - network: self.network, + magic: self.magic, role: self.role, state: SentKey { point: self.state.point, @@ -235,13 +232,11 @@ impl<'a> Handshake> { let their_ellswift = ElligatorSwift::from_array(their_key); // Check for V1 protocol magic bytes - if self.network.magic() - == bitcoin::p2p::Magic::from_bytes( - their_key[..4] - .try_into() - .expect("64 byte array to have 4 byte prefix"), - ) - { + let their_magic: [u8; 4] = their_key[..4] + .try_into() + .expect("64 byte array to have 4 byte prefix"); + + if self.magic == their_magic { return Err(Error::V1Protocol); } @@ -266,11 +261,11 @@ impl<'a> Handshake> { responder_ellswift, secret, party, - self.network, + self.magic, )?; Ok(Handshake { - network: self.network, + magic: self.magic, role: self.role, state: ReceivedKey { session_keys, @@ -373,7 +368,7 @@ impl<'a> Handshake> { )?; Ok(Handshake { - network: self.network, + magic: self.magic, role: self.role, state: SentVersion { cipher, @@ -412,9 +407,9 @@ impl Handshake { /// /// ```rust /// use bip324::{Handshake, GarbageResult, SentVersion}; - /// # use bip324::{Role, Network}; + /// # use bip324::Role; /// # fn example() -> Result<(), Box> { - /// # let mut handshake = Handshake::new(Network::Bitcoin, Role::Initiator)?; + /// # let mut handshake = Handshake::new(p2p::Magic::BITCOIN, Role::Initiator)?; /// # // ... complete handshake to SentVersion state ... /// # let handshake: Handshake = todo!(); /// @@ -445,7 +440,7 @@ impl Handshake { Ok((garbage, _ciphertext)) => { let consumed_bytes = garbage.len() + NUM_GARBAGE_TERMINTOR_BYTES; let handshake = Handshake { - network: self.network, + magic: self.magic, role: self.role, state: ReceivedGarbage { cipher: self.state.cipher, @@ -532,9 +527,9 @@ impl<'a> Handshake> { /// /// ```rust /// use bip324::{Handshake, VersionResult, ReceivedGarbage, NUM_LENGTH_BYTES}; - /// # use bip324::{Role, Network}; + /// # use bip324::Role; /// # fn example() -> Result<(), Box> { - /// # let mut handshake = Handshake::new(Network::Bitcoin, Role::Initiator)?; + /// # let mut handshake = Handshake::new(p2p::Magic::BITCOIN, Role::Initiator)?; /// # // ... complete handshake to ReceivedGarbage state ... /// # let mut handshake: Handshake = todo!(); /// # let encrypted_data: &[u8] = todo!(); @@ -594,6 +589,8 @@ mod tests { use super::*; + const MAGIC: [u8; 4] = [0xF9, 0xBE, 0xB4, 0xD9]; + // Test that the handshake completes successfully with garbage and decoy packets // from both parties. This is a comprehensive integration test of the full protocol. #[test] @@ -601,8 +598,7 @@ mod tests { let initiator_garbage = vec![1u8, 2u8, 3u8]; let responder_garbage = vec![4u8, 5u8]; - let init_handshake = - Handshake::::new(Network::Bitcoin, Role::Initiator).unwrap(); + let init_handshake = Handshake::::new(MAGIC, Role::Initiator).unwrap(); // Send initiator key + garbage. let mut init_buffer = @@ -611,8 +607,7 @@ mod tests { .send_key(Some(&initiator_garbage), &mut init_buffer) .unwrap(); - let resp_handshake = - Handshake::::new(Network::Bitcoin, Role::Responder).unwrap(); + let resp_handshake = Handshake::::new(MAGIC, Role::Responder).unwrap(); // Send responder key + garbage. let mut resp_buffer = @@ -740,21 +735,21 @@ mod tests { fn test_handshake_send_key() { // Test with valid garbage length let valid_garbage = vec![0u8; MAX_NUM_GARBAGE_BYTES]; - let handshake = Handshake::::new(Network::Bitcoin, Role::Initiator).unwrap(); + let handshake = Handshake::::new(MAGIC, Role::Initiator).unwrap(); let mut buffer = vec![0u8; NUM_ELLIGATOR_SWIFT_BYTES + MAX_NUM_GARBAGE_BYTES]; let result = handshake.send_key(Some(&valid_garbage), &mut buffer); assert!(result.is_ok()); // Test with garbage length exceeding MAX_NUM_GARBAGE_BYTES let too_much_garbage = vec![0u8; MAX_NUM_GARBAGE_BYTES + 1]; - let handshake = Handshake::::new(Network::Bitcoin, Role::Initiator).unwrap(); + let handshake = Handshake::::new(MAGIC, Role::Initiator).unwrap(); let result = handshake.send_key(Some(&too_much_garbage), &mut buffer); assert!(matches!(result, Err(Error::TooMuchGarbage))); // Test too small of buffer let buffer_size = NUM_ELLIGATOR_SWIFT_BYTES + valid_garbage.len() - 1; let mut too_small_buffer = vec![0u8; buffer_size]; - let handshake = Handshake::::new(Network::Bitcoin, Role::Initiator).unwrap(); + let handshake = Handshake::::new(MAGIC, Role::Initiator).unwrap(); let result = handshake.send_key(Some(&valid_garbage), &mut too_small_buffer); assert!( matches!(result, Err(Error::BufferTooSmall { required_bytes }) if required_bytes == NUM_ELLIGATOR_SWIFT_BYTES + valid_garbage.len()), @@ -762,7 +757,7 @@ mod tests { ); // Test with no garbage - let handshake = Handshake::::new(Network::Bitcoin, Role::Initiator).unwrap(); + let handshake = Handshake::::new(MAGIC, Role::Initiator).unwrap(); let result = handshake.send_key(None, &mut buffer); assert!(result.is_ok()); } @@ -772,10 +767,8 @@ mod tests { // to pull and do some buffer mangament. #[test] fn test_handshake_receive_garbage_buffer() { - let init_handshake = - Handshake::::new(Network::Bitcoin, Role::Initiator).unwrap(); - let resp_handshake = - Handshake::::new(Network::Bitcoin, Role::Responder).unwrap(); + let init_handshake = Handshake::::new(MAGIC, Role::Initiator).unwrap(); + let resp_handshake = Handshake::::new(MAGIC, Role::Responder).unwrap(); let mut init_buffer = vec![0u8; NUM_ELLIGATOR_SWIFT_BYTES]; let init_handshake = init_handshake.send_key(None, &mut init_buffer).unwrap(); @@ -855,7 +848,7 @@ mod tests { #[test] fn test_handshake_split_garbage() { // Create a handshake and bring it to the SentVersion state to test split_garbage. - let handshake = Handshake::::new(Network::Bitcoin, Role::Initiator).unwrap(); + let handshake = Handshake::::new(MAGIC, Role::Initiator).unwrap(); let mut buffer = vec![0u8; NUM_ELLIGATOR_SWIFT_BYTES]; let handshake = handshake.send_key(None, &mut buffer).unwrap(); @@ -890,23 +883,23 @@ mod tests { #[test] fn test_v1_protocol_detection() { // Test that receive_key properly detects V1 protocol magic bytes - let handshake = Handshake::::new(Network::Bitcoin, Role::Initiator).unwrap(); + let handshake = Handshake::::new(MAGIC, Role::Initiator).unwrap(); let mut buffer = vec![0u8; NUM_ELLIGATOR_SWIFT_BYTES]; let handshake = handshake.send_key(None, &mut buffer).unwrap(); // Create a key that starts with Bitcoin mainnet magic bytes let mut v1_key = [0u8; NUM_ELLIGATOR_SWIFT_BYTES]; - v1_key[..4].copy_from_slice(&Network::Bitcoin.magic().to_bytes()); + v1_key[..4].copy_from_slice(&MAGIC); let result = handshake.receive_key(v1_key); assert!(matches!(result, Err(Error::V1Protocol))); // Test with different networks - let handshake = Handshake::::new(Network::Testnet, Role::Responder).unwrap(); + let handshake = Handshake::::new([0u8; 4], Role::Responder).unwrap(); let handshake = handshake.send_key(None, &mut buffer).unwrap(); let mut v1_testnet_key = [0u8; NUM_ELLIGATOR_SWIFT_BYTES]; - v1_testnet_key[..4].copy_from_slice(&Network::Testnet.magic().to_bytes()); + v1_testnet_key[..4].copy_from_slice(&[0u8; 4]); let result = handshake.receive_key(v1_testnet_key); assert!(matches!(result, Err(Error::V1Protocol))); diff --git a/protocol/src/io.rs b/protocol/src/io.rs index 2a3ff4d..b1face3 100644 --- a/protocol/src/io.rs +++ b/protocol/src/io.rs @@ -16,7 +16,7 @@ //! use std::net::TcpStream; //! use std::io::BufReader; //! use bip324::io::{Protocol, Payload}; -//! use bip324::{Network, Role}; +//! use bip324::Role; //! //! # fn main() -> Result<(), Box> { //! // Connect to a Bitcoin node @@ -28,7 +28,7 @@ //! //! // Establish BIP-324 encrypted connection //! let mut protocol = Protocol::new( -//! Network::Bitcoin, +//! p2p::Magic::BITCOIN, //! Role::Initiator, //! None, // no garbage bytes //! None, // no decoy packets @@ -44,13 +44,12 @@ //! # } //! ``` +use core::borrow::Borrow; use core::fmt; use std::io::{Chain, Cursor, Read, Write}; use std::vec; use std::vec::Vec; -use bitcoin::Network; - use crate::{ handshake::{self, GarbageResult, VersionResult}, Error, Handshake, InboundCipher, OutboundCipher, PacketType, Role, @@ -279,7 +278,7 @@ impl fmt::Display for ProtocolError { /// /// * `Io` - Includes a flag for if the remote probably only understands the V1 protocol. pub fn handshake( - network: Network, + magic: impl Borrow<[u8; 4]>, role: Role, garbage: Option>, decoys: Option>>, @@ -290,7 +289,7 @@ where R: Read, W: Write, { - let handshake = Handshake::::new(network, role)?; + let handshake = Handshake::::new(magic, role)?; handshake_with_initialized(handshake, garbage, decoys, reader, writer) } @@ -452,7 +451,7 @@ where /// /// * `Io` - Includes a flag for if the remote probably only understands the V1 protocol. pub fn new( - network: Network, + magic: impl Borrow<[u8; 4]>, role: Role, garbage: Option>, decoys: Option>>, @@ -460,7 +459,7 @@ where mut writer: W, ) -> Result, ProtocolError> { let (inbound_cipher, outbound_cipher, session_reader) = - handshake(network, role, garbage, decoys, reader, &mut writer)?; + handshake(magic, role, garbage, decoys, reader, &mut writer)?; Ok(Protocol { reader: ProtocolReader { @@ -582,9 +581,11 @@ where #[cfg(test)] mod tests { use super::*; - use bitcoin::secp256k1::rand::{rngs::StdRng, SeedableRng}; + use secp256k1::rand::{rngs::StdRng, SeedableRng}; use std::io::Cursor; + const MAGIC: [u8; 4] = [0xF9, 0xBE, 0xB4, 0xD9]; + /// Generate deterministic handshake messages for testing. /// Returns the complete handshake message (key + garbage + version) for the specified role. fn generate_handshake_messages( @@ -594,12 +595,12 @@ mod tests { garbage: Option<&[u8]>, decoys: Option<&[&[u8]]>, ) -> Vec { - let secp = bitcoin::secp256k1::Secp256k1::new(); + let secp = secp256k1::Secp256k1::new(); // Create both parties. let mut local_rng = StdRng::seed_from_u64(local_seed); let local_handshake = Handshake::::new_with_rng( - Network::Bitcoin, + MAGIC, local_role, &mut local_rng, &secp, @@ -612,7 +613,7 @@ mod tests { Role::Responder => Role::Initiator, }; let remote_handshake = Handshake::::new_with_rng( - Network::Bitcoin, + MAGIC, remote_role, &mut remote_rng, &secp, @@ -658,9 +659,9 @@ mod tests { #[test] fn test_handshake_session_reader() { let mut init_rng = StdRng::seed_from_u64(42); - let secp = bitcoin::secp256k1::Secp256k1::new(); + let secp = secp256k1::Secp256k1::new(); let init_handshake = Handshake::::new_with_rng( - Network::Bitcoin, + MAGIC, Role::Initiator, &mut init_rng, &secp, @@ -708,9 +709,9 @@ mod tests { // that would require excessive memory allocation. let mut init_rng = StdRng::seed_from_u64(42); - let secp = bitcoin::secp256k1::Secp256k1::new(); + let secp = secp256k1::Secp256k1::new(); let init_handshake = Handshake::::new_with_rng( - Network::Bitcoin, + MAGIC, Role::Initiator, &mut init_rng, &secp, diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index e61c5ab..e5ba37a 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -15,7 +15,6 @@ //! //! ```no_run //! use bip324::io::{Protocol, Payload}; -//! use bip324::serde::{serialize, deserialize, NetworkMessage}; //! use std::net::TcpStream; //! use std::io::BufReader; //! @@ -26,20 +25,22 @@ //! let reader = BufReader::new(stream.try_clone()?); //! let writer = stream; //! +//! // Bitcoin mainnet magic bytes +//! let magic = [0xF9, 0xBE, 0xB4, 0xD9]; +//! //! let mut protocol = Protocol::new( -//! bip324::Network::Bitcoin, +//! magic, //! bip324::Role::Initiator, //! None, None, // no garbage or decoys //! reader, //! writer, //! )?; //! -//! let ping_msg = NetworkMessage::Ping(0xdeadbeef); -//! let serialized = serialize(ping_msg); -//! protocol.write(&Payload::genuine(serialized))?; +//! // Send some example data (in practice, this would be a properly formatted bitcoin message) +//! protocol.write(&Payload::genuine(b"hello world".to_vec()))?; //! //! let response = protocol.read()?; -//! let response_msg: NetworkMessage = deserialize(&response.contents())?; +//! println!("Received {} bytes", response.contents().len()); //! # Ok(()) //! # } //! ``` @@ -52,7 +53,6 @@ //! # async fn main() -> Result<(), Box> { //! use bip324::futures::Protocol; //! use bip324::io::Payload; -//! use bip324::serde::{serialize, deserialize, NetworkMessage}; //! use tokio::net::TcpStream; //! use tokio::io::BufReader; //! @@ -62,39 +62,22 @@ //! // Wrap reader in BufReader for efficiency (protocol makes many small reads) //! let buffered_reader = BufReader::new(reader); //! +//! // Bitcoin mainnet magic bytes +//! let magic = [0xF9, 0xBE, 0xB4, 0xD9]; +//! //! let mut protocol = Protocol::new( -//! bip324::Network::Bitcoin, +//! magic, //! bip324::Role::Initiator, //! None, None, // no garbage or decoys //! buffered_reader, //! writer, //! ).await?; //! -//! let ping_msg = NetworkMessage::Ping(12345); // nonce -//! let serialized = serialize(ping_msg); -//! protocol.write(&Payload::genuine(serialized)).await?; +//! // Send some example data (in practice, this would be a properly formatted bitcoin message) +//! protocol.write(&Payload::genuine(b"hello world".to_vec())).await?; //! //! let response = protocol.read().await?; -//! let response_msg: NetworkMessage = deserialize(&response.contents())?; -//! # Ok(()) -//! # } -//! ``` -//! -//! # Message Serialization -//! -//! BIP-324 introduces specific changes to how bitcoin P2P messages are serialized for V2 transport. -//! The [`serde`] module provides these serialization functions. -//! -//! ```no_run -//! # #[cfg(feature = "std")] -//! # fn main() -> Result<(), Box> { -//! use bip324::serde::{serialize, deserialize, NetworkMessage}; -//! -//! let ping_msg = NetworkMessage::Ping(0xdeadbeef); -//! let serialized = serialize(ping_msg); -//! -//! let received_bytes = vec![0x12, 0xef, 0xbe, 0xad, 0xde, 0, 0, 0, 0]; -//! let message: NetworkMessage = deserialize(&received_bytes)?; +//! println!("Received {} bytes", response.contents().len()); //! # Ok(()) //! # } //! ``` @@ -148,19 +131,15 @@ pub mod futures; mod handshake; #[cfg(feature = "std")] pub mod io; -#[cfg(feature = "std")] -pub mod serde; -use core::fmt; +use core::{borrow::Borrow, fmt}; -use bitcoin::secp256k1::{ +use bitcoin_hashes::{hkdf, sha256, Hkdf}; +use secp256k1::{ self, ellswift::{ElligatorSwift, ElligatorSwiftParty}, SecretKey, }; -use bitcoin_hashes::{hkdf, sha256, Hkdf}; - -pub use bitcoin::Network; pub use handshake::{ GarbageResult, Handshake, Initialized, ReceivedGarbage, ReceivedKey, SentKey, SentVersion, @@ -347,14 +326,16 @@ impl SessionKeyMaterial { b: ElligatorSwift, secret: SecretKey, party: ElligatorSwiftParty, - network: Network, + magic: impl Borrow<[u8; 4]>, ) -> Result { let data = "bip324_ellswift_xonly_ecdh".as_bytes(); let ecdh_sk = ElligatorSwift::shared_secret(a, b, secret, party, Some(data)); - let ikm_salt = "bitcoin_v2_shared_secret".as_bytes(); - let magic = network.magic().to_bytes(); - let salt = [ikm_salt, &magic].concat(); + let ikm_salt = b"bitcoin_v2_shared_secret"; // 24 bytes + let mut salt = [0u8; 28]; + salt[..24].copy_from_slice(ikm_salt); + salt[24..].copy_from_slice(magic.borrow()); + let hk = Hkdf::::new(salt.as_slice(), ecdh_sk.as_secret_bytes()); let mut session_id = [0u8; 32]; let session_info = "session_id".as_bytes(); @@ -795,7 +776,7 @@ macro_rules! impl_fill_bytes { ($rng:ident) => { impl FillBytes for $rng { fn fill_bytes(&mut self, dest: &mut [u8; 32]) { - use bitcoin::secp256k1::rand::RngCore; + use secp256k1::rand::RngCore; RngCore::fill_bytes(self, dest); } } @@ -803,7 +784,7 @@ macro_rules! impl_fill_bytes { } #[cfg(feature = "std")] -use bitcoin::secp256k1::rand::rngs::{StdRng, ThreadRng}; +use secp256k1::rand::rngs::{StdRng, ThreadRng}; #[cfg(feature = "std")] impl_fill_bytes!(StdRng); #[cfg(feature = "std")] @@ -813,14 +794,16 @@ impl_fill_bytes!(ThreadRng); mod tests { use super::*; - use bitcoin::secp256k1::ellswift::{ElligatorSwift, ElligatorSwiftParty}; - use bitcoin::secp256k1::rand::Rng; - use bitcoin::secp256k1::SecretKey; use core::str::FromStr; use hex::prelude::*; + use secp256k1::ellswift::{ElligatorSwift, ElligatorSwiftParty}; + use secp256k1::rand::Rng; + use secp256k1::SecretKey; use std::vec; use std::vec::Vec; + const MAGIC: [u8; 4] = [0xF9, 0xBE, 0xB4, 0xD9]; + fn gen_garbage(garbage_len: u32, rng: &mut impl Rng) -> Vec { let buffer: Vec = (0..garbage_len).map(|_| rng.gen()).collect(); buffer @@ -838,7 +821,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + MAGIC, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Initiator); @@ -894,7 +877,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + MAGIC, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Initiator); @@ -951,7 +934,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + MAGIC, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys, Role::Initiator); @@ -984,7 +967,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + MAGIC, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys, Role::Initiator); @@ -1009,7 +992,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + MAGIC, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Initiator); @@ -1076,7 +1059,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + MAGIC, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Initiator); @@ -1125,7 +1108,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + MAGIC, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Initiator); @@ -1178,7 +1161,7 @@ mod tests { elliswift_alice, alice, ElligatorSwiftParty::B, - Network::Bitcoin, + MAGIC, ) .unwrap(); let id = session_keys.session_id; @@ -1224,7 +1207,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + MAGIC, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Initiator); @@ -1252,7 +1235,7 @@ mod tests { elliswift_alice, alice, ElligatorSwiftParty::B, - Network::Bitcoin, + MAGIC, ) .unwrap(); let id = session_keys.session_id; @@ -1294,7 +1277,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + MAGIC, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Initiator); @@ -1330,7 +1313,7 @@ mod tests { elliswift_alice, alice, ElligatorSwiftParty::B, - Network::Bitcoin, + MAGIC, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Responder); @@ -1371,7 +1354,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + MAGIC, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Initiator); diff --git a/protocol/src/serde.rs b/protocol/src/serde.rs deleted file mode 100644 index ee9227a..0000000 --- a/protocol/src/serde.rs +++ /dev/null @@ -1,314 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Serialize and deserialize V2 messages over the wire. -//! -//! A subset of commands are represented with a single byte in V2 instead of the 12-byte ASCII encoding like V1. Message ID mappings are defined in [BIP-324](https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki#user-content-v2_Bitcoin_P2P_message_structure). - -use core::fmt; - -use bitcoin::{ - block, - consensus::{encode, Decodable, Encodable}, - VarInt, -}; -use std::vec::Vec; - -pub use bitcoin::p2p::message::{CommandString, NetworkMessage}; - -#[derive(Debug)] -pub enum Error { - Deserialize(bitcoin::consensus::encode::Error), - UnknownShortID(u8), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Error::Deserialize(e) => write!(f, "Unable to deserialize {e}"), - Error::UnknownShortID(b) => write!(f, "Unrecognized short ID when deserializing {b}"), - } - } -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Error::Deserialize(e) => Some(e), - Error::UnknownShortID(_) => None, - } - } -} - -/// Serialize a [`NetworkMessage`] into a buffer. -/// -/// This function is infallible because the underlying `consensus_encode()` -/// operations only fail on I/O errors, which cannot occur when writing to an in-memory `Vec`. -pub fn serialize(msg: NetworkMessage) -> Vec { - let mut buffer = Vec::new(); - match &msg { - NetworkMessage::Addr(_) => { - buffer.push(1u8); - } - NetworkMessage::Inv(_) => { - buffer.push(14u8); - } - NetworkMessage::GetData(_) => { - buffer.push(11u8); - } - NetworkMessage::NotFound(_) => { - buffer.push(17u8); - } - NetworkMessage::GetBlocks(_) => { - buffer.push(9u8); - } - NetworkMessage::GetHeaders(_) => { - buffer.push(12u8); - } - NetworkMessage::MemPool => { - buffer.push(15u8); - } - NetworkMessage::Tx(_) => { - buffer.push(21u8); - } - NetworkMessage::Block(_) => { - buffer.push(2u8); - } - NetworkMessage::Headers(_) => { - buffer.push(13u8); - } - NetworkMessage::Ping(_) => { - buffer.push(18u8); - } - NetworkMessage::Pong(_) => { - buffer.push(19u8); - } - NetworkMessage::MerkleBlock(_) => { - buffer.push(16u8); - } - NetworkMessage::FilterLoad(_) => { - buffer.push(8u8); - } - NetworkMessage::FilterAdd(_) => { - buffer.push(6u8); - } - NetworkMessage::FilterClear => { - buffer.push(7u8); - } - NetworkMessage::GetCFilters(_) => { - buffer.push(22u8); - } - NetworkMessage::CFilter(_) => { - buffer.push(23u8); - } - NetworkMessage::GetCFHeaders(_) => { - buffer.push(24u8); - } - NetworkMessage::CFHeaders(_) => { - buffer.push(25u8); - } - NetworkMessage::GetCFCheckpt(_) => { - buffer.push(26u8); - } - NetworkMessage::CFCheckpt(_) => { - buffer.push(27u8); - } - NetworkMessage::SendCmpct(_) => { - buffer.push(20u8); - } - NetworkMessage::CmpctBlock(_) => { - buffer.push(4u8); - } - NetworkMessage::GetBlockTxn(_) => { - buffer.push(10u8); - } - NetworkMessage::BlockTxn(_) => { - buffer.push(3u8); - } - NetworkMessage::FeeFilter(_) => { - buffer.push(5u8); - } - NetworkMessage::AddrV2(_) => { - buffer.push(28u8); - } - // Messages which are not optimized and use the zero-byte + 12 following bytes to encode command in ascii. - NetworkMessage::Version(_) - | NetworkMessage::Verack - | NetworkMessage::SendHeaders - | NetworkMessage::GetAddr - | NetworkMessage::WtxidRelay - | NetworkMessage::SendAddrV2 - | NetworkMessage::Alert(_) - | NetworkMessage::Reject(_) => { - buffer.push(0u8); - msg.command() - .consensus_encode(&mut buffer) - .expect("Encoding to Vec never fails"); - } - NetworkMessage::Unknown { - command, - payload: _, - } => { - buffer.push(0u8); - command - .consensus_encode(&mut buffer) - .expect("Encoding to Vec never fails"); - } - } - - msg.consensus_encode(&mut buffer) - .expect("Encoding to Vec never fails"); - - buffer -} - -/// Deserialize v2 message into [`NetworkMessage`]. -pub fn deserialize(buffer: &[u8]) -> Result { - let short_id = buffer[0]; - let mut payload_buffer = &buffer[1..]; - match short_id { - // Zero-byte means the command is encoded in the next 12 bytes. - 0u8 => { - // Next 12 bytes have encoded command. - let mut command_buffer = &buffer[1..13]; - let command = - CommandString::consensus_decode(&mut command_buffer).map_err(Error::Deserialize)?; - // Rest of buffer is payload. - payload_buffer = &buffer[13..]; - // There are a handful of "known" messages which don't use a short ID, otherwise Unknown. - match command.as_ref() { - "version" => Ok(NetworkMessage::Version( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - "verack" => Ok(NetworkMessage::Verack), - "sendheaders" => Ok(NetworkMessage::SendHeaders), - "getaddr" => Ok(NetworkMessage::GetAddr), - "wtxidrelay" => Ok(NetworkMessage::WtxidRelay), - "sendaddrv2" => Ok(NetworkMessage::SendAddrV2), - "alert" => Ok(NetworkMessage::Alert( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - "reject" => Ok(NetworkMessage::Reject( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - _ => Ok(NetworkMessage::Unknown { - command, - payload: payload_buffer.to_vec(), - }), - } - } - // The following single byte IDs map to command short IDs. - 1u8 => Ok(NetworkMessage::Addr( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 2u8 => Ok(NetworkMessage::Block( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 3u8 => Ok(NetworkMessage::BlockTxn( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 4u8 => Ok(NetworkMessage::CmpctBlock( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 5u8 => Ok(NetworkMessage::FeeFilter( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 6u8 => Ok(NetworkMessage::FilterAdd( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 7u8 => Ok(NetworkMessage::FilterClear), - 8u8 => Ok(NetworkMessage::FilterLoad( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 9u8 => Ok(NetworkMessage::GetBlocks( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 10u8 => Ok(NetworkMessage::GetBlockTxn( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 11u8 => Ok(NetworkMessage::GetData( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 12u8 => Ok(NetworkMessage::GetHeaders( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - // This one gets a little weird and needs a bit of love in the future. - 13u8 => Ok(NetworkMessage::Headers( - HeaderDeserializationWrapper::consensus_decode(&mut payload_buffer) - .map_err(Error::Deserialize)? - .0, - )), - 14u8 => Ok(NetworkMessage::Inv( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 15u8 => Ok(NetworkMessage::MemPool), - 16u8 => Ok(NetworkMessage::MerkleBlock( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 17u8 => Ok(NetworkMessage::NotFound( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 18u8 => Ok(NetworkMessage::Ping( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 19u8 => Ok(NetworkMessage::Pong( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 20u8 => Ok(NetworkMessage::SendCmpct( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 21u8 => Ok(NetworkMessage::Tx( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 22u8 => Ok(NetworkMessage::GetCFilters( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 23u8 => Ok(NetworkMessage::CFilter( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 24u8 => Ok(NetworkMessage::GetCFHeaders( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 25u8 => Ok(NetworkMessage::CFHeaders( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 26u8 => Ok(NetworkMessage::GetCFCheckpt( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 27u8 => Ok(NetworkMessage::CFCheckpt( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - 28u8 => Ok(NetworkMessage::AddrV2( - Decodable::consensus_decode(&mut payload_buffer).map_err(Error::Deserialize)?, - )), - - // Unsupported short ID. - unknown => Err(Error::UnknownShortID(unknown)), - } -} - -// Copied from rust-bitcoin internals. -// -// Only the deserialized side needs to be copied over since -// the serialize side is applied at the NetworkMessage level. -struct HeaderDeserializationWrapper(Vec); - -impl Decodable for HeaderDeserializationWrapper { - #[inline] - fn consensus_decode_from_finite_reader( - r: &mut R, - ) -> Result { - let len = VarInt::consensus_decode(r)?.0; - // should be above usual number of items to avoid - // allocation - let mut ret = Vec::with_capacity(core::cmp::min(1024 * 16, len as usize)); - for _ in 0..len { - ret.push(Decodable::consensus_decode(r)?); - if u8::consensus_decode(r)? != 0u8 { - return Err(encode::Error::ParseFailed( - "Headers message should not contain transactions", - )); - } - } - Ok(HeaderDeserializationWrapper(ret)) - } -} diff --git a/protocol/tests/round_trips.rs b/protocol/tests/round_trips.rs index 2ac384f..8b07daa 100644 --- a/protocol/tests/round_trips.rs +++ b/protocol/tests/round_trips.rs @@ -2,6 +2,7 @@ #[cfg(feature = "std")] const PORT: u16 = 18444; +const MAGIC: [u8; 4] = [0xF9, 0xBE, 0xB4, 0xD9]; #[test] #[cfg(feature = "std")] @@ -10,17 +11,16 @@ fn hello_world_happy_path() { GarbageResult, Handshake, Initialized, PacketType, ReceivedKey, Role, VersionResult, NUM_LENGTH_BYTES, }; - use bitcoin::Network; // Create initiator handshake - let init_handshake = Handshake::::new(Network::Bitcoin, Role::Initiator).unwrap(); + let init_handshake = Handshake::::new(MAGIC, Role::Initiator).unwrap(); // Send initiator key let mut init_key_buffer = vec![0u8; Handshake::::send_key_len(None)]; let init_handshake = init_handshake.send_key(None, &mut init_key_buffer).unwrap(); // Create responder handshake - let resp_handshake = Handshake::::new(Network::Bitcoin, Role::Responder).unwrap(); + let resp_handshake = Handshake::::new(MAGIC, Role::Responder).unwrap(); // Send responder key let mut resp_key_buffer = vec![0u8; Handshake::::send_key_len(None)]; @@ -164,18 +164,22 @@ fn regtest_handshake() { }; use bip324::{ - serde::{deserialize, serialize, NetworkMessage}, GarbageResult, Handshake, Initialized, PacketType, ReceivedKey, VersionResult, NUM_LENGTH_BYTES, }; - use bitcoin::p2p::{message_network::VersionMessage, Address, ServiceFlags}; + use bitcoin::consensus; + use p2p::{ + message::{NetworkMessage, V2NetworkMessage}, + message_network::{UserAgent, VersionMessage}, + Address, ProtocolVersion, ServiceFlags, + }; let bitcoind = regtest_process(TransportVersion::V2); let mut stream = TcpStream::connect(bitcoind.params.p2p_socket.unwrap()).unwrap(); // Initialize handshake let handshake = - Handshake::::new(bip324::Network::Regtest, bip324::Role::Initiator).unwrap(); + Handshake::::new(p2p::Magic::REGTEST, bip324::Role::Initiator).unwrap(); // Send our public key let mut public_key = vec![0u8; Handshake::::send_key_len(None)]; @@ -262,17 +266,17 @@ fn regtest_handshake() { let ip = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), PORT); let from_and_recv = Address::new(&ip, ServiceFlags::NONE); let msg = VersionMessage { - version: 70015, + version: ProtocolVersion::INVALID_CB_NO_BAN_VERSION, services: ServiceFlags::NONE, timestamp: now as i64, receiver: from_and_recv.clone(), sender: from_and_recv, nonce: 1, - user_agent: "BIP-324 Client".to_string(), + user_agent: UserAgent::from_nonstandard("BIP-324 Client"), start_height: 0, relay: false, }; - let message = serialize(NetworkMessage::Version(msg)); + let message = consensus::serialize(&V2NetworkMessage::new(NetworkMessage::Version(msg))); let packet_len = bip324::OutboundCipher::encryption_buffer_len(message.len()); let mut packet = vec![0u8; packet_len]; encrypter @@ -291,7 +295,7 @@ fn regtest_handshake() { let _ = decrypter .decrypt(&response_message, &mut decrypted_message, None) .unwrap(); - let message = deserialize(&decrypted_message[1..]).unwrap(); // Skip header byte + let message = consensus::deserialize::(&decrypted_message[1..]).unwrap(); // Skip header byte assert_eq!(message.cmd(), "version"); } @@ -303,11 +307,13 @@ fn regtest_handshake_std() { time::{SystemTime, UNIX_EPOCH}, }; - use bip324::{ - io::{Payload, Protocol}, - serde::{deserialize, serialize, NetworkMessage}, + use bip324::io::{Payload, Protocol}; + use bitcoin::consensus; + use p2p::{ + message::{NetworkMessage, V2NetworkMessage}, + message_network::{UserAgent, VersionMessage}, + Address, ProtocolVersion, ServiceFlags, }; - use bitcoin::p2p::{message_network::VersionMessage, Address, ServiceFlags}; let bitcoind = regtest_process(TransportVersion::V2); @@ -318,7 +324,7 @@ fn regtest_handshake_std() { // Initialize high-level protocol with handshake println!("Starting BIP-324 handshake"); let mut protocol = Protocol::new( - bip324::Network::Regtest, + p2p::Magic::REGTEST, bip324::Role::Initiator, None, // no garbage None, // no decoys @@ -337,25 +343,25 @@ fn regtest_handshake_std() { let ip = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), PORT); let from_and_recv = Address::new(&ip, ServiceFlags::NONE); let msg = VersionMessage { - version: 70015, + version: ProtocolVersion::INVALID_CB_NO_BAN_VERSION, services: ServiceFlags::NONE, timestamp: now as i64, receiver: from_and_recv.clone(), sender: from_and_recv, nonce: 1, - user_agent: "BIP-324 Client".to_string(), + user_agent: UserAgent::from_nonstandard("BIP-324 Client"), start_height: 0, relay: false, }; - let message = serialize(NetworkMessage::Version(msg)); + let message = consensus::serialize(&V2NetworkMessage::new(NetworkMessage::Version(msg))); println!("Sending version message using Protocol::write()"); protocol.write(&Payload::genuine(message)).unwrap(); println!("Reading version response using Protocol::read()"); let payload = protocol.read().unwrap(); - let response_message = deserialize(payload.contents()).unwrap(); + let response_message = consensus::deserialize::(payload.contents()).unwrap(); assert_eq!(response_message.cmd(), "version"); println!("Successfully exchanged version messages using Protocol API!"); @@ -369,12 +375,13 @@ async fn regtest_handshake_async() { time::{SystemTime, UNIX_EPOCH}, }; - use bip324::{ - futures::Protocol, - io::Payload, - serde::{deserialize, serialize, NetworkMessage}, + use bip324::{futures::Protocol, io::Payload}; + use bitcoin::consensus; + use p2p::{ + message::{NetworkMessage, V2NetworkMessage}, + message_network::{UserAgent, VersionMessage}, + Address, ProtocolVersion, ServiceFlags, }; - use bitcoin::p2p::{message_network::VersionMessage, Address, ServiceFlags}; use tokio::net::TcpStream; let bitcoind = regtest_process(TransportVersion::V2); @@ -388,7 +395,7 @@ async fn regtest_handshake_async() { // Initialize high-level async protocol with handshake println!("Starting async BIP-324 handshake"); let mut protocol = Protocol::new( - bip324::Network::Regtest, + p2p::Magic::REGTEST, bip324::Role::Initiator, None, // no garbage None, // no decoys @@ -408,25 +415,25 @@ async fn regtest_handshake_async() { let ip = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), PORT); let from_and_recv = Address::new(&ip, ServiceFlags::NONE); let msg = VersionMessage { - version: 70015, + version: ProtocolVersion::INVALID_CB_NO_BAN_VERSION, services: ServiceFlags::NONE, timestamp: now as i64, receiver: from_and_recv.clone(), sender: from_and_recv, nonce: 1, - user_agent: "BIP-324 Async Client".to_string(), + user_agent: UserAgent::from_nonstandard("BIP-324 Client"), start_height: 0, relay: false, }; - let message = serialize(NetworkMessage::Version(msg)); + let message = consensus::serialize(&V2NetworkMessage::new(NetworkMessage::Version(msg))); println!("Sending version message using async Protocol::write()"); protocol.write(&Payload::genuine(message)).await.unwrap(); println!("Reading version response using async Protocol::read()"); let payload = protocol.read().await.unwrap(); - let response_message = deserialize(payload.contents()).unwrap(); + let response_message = consensus::deserialize::(payload.contents()).unwrap(); assert_eq!(response_message.cmd(), "version"); println!("Successfully exchanged version messages using async Protocol API!"); @@ -447,7 +454,7 @@ fn regtest_handshake_v1_only() { let mut stream = TcpStream::connect(bitcoind.params.p2p_socket.unwrap()).unwrap(); let handshake = - Handshake::::new(bip324::Network::Regtest, bip324::Role::Initiator).unwrap(); + Handshake::::new(p2p::Magic::REGTEST, bip324::Role::Initiator).unwrap(); let mut public_key = vec![0u8; Handshake::::send_key_len(None)]; let _handshake = handshake.send_key(None, &mut public_key).unwrap(); println!("Writing public key to the remote node"); @@ -471,7 +478,7 @@ fn regtest_process(transport: TransportVersion) -> bitcoind::Node { // Pull executable from auto-downloaded location, unless // environment variable override is present. Some operating // systems (e.g. NixOS) don't like the downloaded executable - // so the environment varible must be used. + // so the environment variable must be used. let exe_path = bitcoind::exe_path().unwrap(); println!("Using bitcoind at {exe_path}"); let mut conf = bitcoind::Conf::default(); diff --git a/traffic/src/futures.rs b/traffic/src/futures.rs index ad68591..b3f783f 100644 --- a/traffic/src/futures.rs +++ b/traffic/src/futures.rs @@ -1,5 +1,6 @@ //! Async traffic shaping wrapper for BIP-324 protocol. +use std::borrow::Borrow; use std::sync::Arc; use std::time::Duration; @@ -8,7 +9,7 @@ use tokio::sync::{mpsc, oneshot}; use bip324::futures::{Protocol, ProtocolReader, ProtocolWriter}; use bip324::io::{Payload, ProtocolError, ProtocolFailureSuggestion}; -use bip324::{Network, Role}; +use bip324::Role; use crate::{ AtomicTrafficStats, TrafficConfig, TrafficShaper, TrafficStats, DEFAULT_CHECK_INTERVAL_MS, @@ -77,7 +78,7 @@ where /// use tokio::io::{AsyncReadExt, AsyncWriteExt}; /// use bip324_traffic::{TrafficConfig, PaddingStrategy, DecoyStrategy}; /// use bip324_traffic::futures::ShapedProtocol; - /// use bip324::{Network, Role}; + /// use bip324::Role; /// /// # #[tokio::main(flavor = "current_thread")] /// # async fn main() -> Result<(), Box> { @@ -89,7 +90,7 @@ where /// .with_decoy_strategy(DecoyStrategy::Random); /// /// let mut protocol = ShapedProtocol::new( - /// Network::Bitcoin, + /// [0xF9, 0xBE, 0xB4, 0xD9], // Bitcoin mainnet magic bytes /// Role::Initiator, /// config, /// reader, @@ -105,7 +106,7 @@ where /// /// This function is *not* cancellation-safe. pub async fn new( - network: Network, + magic: impl Borrow<[u8; 4]>, role: Role, config: TrafficConfig, reader: R, @@ -118,7 +119,7 @@ where let mut shaper = TrafficShaper::new(config); let (garbage, decoys) = shaper.handshake(&stats); - let protocol = Protocol::new(network, role, garbage, decoys, reader, writer).await?; + let protocol = Protocol::new(magic, role, garbage, decoys, reader, writer).await?; let (protocol_reader, protocol_writer) = protocol.into_split(); let (write_tx, write_rx) = mpsc::unbounded_channel(); @@ -254,6 +255,8 @@ mod tests { use super::*; use bip324::futures::Protocol; + const MAGIC: [u8; 4] = [0xF9, 0xBE, 0xB4, 0xD9]; + #[tokio::test] async fn test_async_protocol_drop() { let (local, remote) = tokio::io::duplex(400_000); @@ -262,7 +265,7 @@ mod tests { let _responder_task = tokio::spawn(async move { let responder = Protocol::new( - Network::Bitcoin, + MAGIC, Role::Responder, None, None, @@ -276,15 +279,10 @@ mod tests { }); let config = TrafficConfig::new().with_decoy_strategy(crate::DecoyStrategy::Random); - let shaped_protocol = ShapedProtocol::new( - Network::Bitcoin, - Role::Initiator, - config, - local_read, - local_write, - ) - .await - .expect("initiator handshake should succeed"); + let shaped_protocol = + ShapedProtocol::new(MAGIC, Role::Initiator, config, local_read, local_write) + .await + .expect("initiator handshake should succeed"); drop(shaped_protocol); } diff --git a/traffic/src/io.rs b/traffic/src/io.rs index 514ebc0..974cb48 100644 --- a/traffic/src/io.rs +++ b/traffic/src/io.rs @@ -1,13 +1,14 @@ //! Blocking I/O traffic shaping wrapper for BIP-324 protocol. use core::time::Duration; +use std::borrow::Borrow; use std::io::{Read, Write}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::thread; use bip324::io::{Payload, Protocol, ProtocolError, ProtocolReader, ProtocolWriter}; -use bip324::{Network, Role}; +use bip324::Role; use rand::Rng; use crate::{ @@ -84,7 +85,7 @@ where /// use std::net::TcpStream; /// use bip324_traffic::{TrafficConfig, PaddingStrategy, DecoyStrategy}; /// use bip324_traffic::io::ShapedProtocol; - /// use bip324::{Network, Role}; + /// use bip324::Role; /// /// # fn main() -> Result<(), Box> { /// let stream = TcpStream::connect("127.0.0.1:8333")?; @@ -96,7 +97,7 @@ where /// .with_decoy_strategy(DecoyStrategy::Random); /// /// let mut protocol = ShapedProtocol::new( - /// Network::Bitcoin, + /// p2p::Magic::BITCOIN, /// Role::Initiator, /// config, /// reader, @@ -107,7 +108,7 @@ where /// # } /// ``` pub fn new( - network: Network, + magic: impl Borrow<[u8; 4]>, role: Role, config: TrafficConfig, reader: R, @@ -117,7 +118,7 @@ where let mut shaper = TrafficShaper::new(config); let (garbage, decoys) = shaper.handshake(&stats); - let protocol = Protocol::new(network, role, garbage, decoys, reader, writer)?; + let protocol = Protocol::new(magic, role, garbage, decoys, reader, writer)?; let (protocol_reader, protocol_writer) = protocol.into_split(); let writer_state = Arc::new(Mutex::new(WriterState { @@ -236,13 +237,15 @@ mod tests { use super::*; use std::io::Cursor; + const MAGIC: [u8; 4] = [0xF9, 0xBE, 0xB4, 0xD9]; + #[test] fn test_protocol_drop() { let reader = Cursor::new(Vec::new()); let writer = Cursor::new(Vec::new()); let config = TrafficConfig::new().with_decoy_strategy(crate::DecoyStrategy::Random); - let result = ShapedProtocol::new(Network::Bitcoin, Role::Initiator, config, reader, writer); + let result = ShapedProtocol::new(MAGIC, Role::Initiator, config, reader, writer); drop(result); } } diff --git a/traffic/src/lib.rs b/traffic/src/lib.rs index 71cf456..d7be82a 100644 --- a/traffic/src/lib.rs +++ b/traffic/src/lib.rs @@ -12,7 +12,7 @@ //! use std::net::TcpStream; //! use bip324_traffic::{TrafficConfig, PaddingStrategy, DecoyStrategy}; //! use bip324_traffic::io::ShapedProtocol; -//! use bip324::{Network, Role}; +//! use bip324::Role; //! //! # fn main() -> Result<(), Box> { //! let config = TrafficConfig::new() @@ -24,7 +24,7 @@ //! let writer = stream; //! //! let mut protocol = ShapedProtocol::new( -//! Network::Bitcoin, +//! [0xF9, 0xBE, 0xB4, 0xD9], // Bitcoin mainnet magic bytes //! Role::Initiator, //! config, //! reader, @@ -49,7 +49,7 @@ //! use tokio::io::{AsyncReadExt, AsyncWriteExt}; //! use bip324_traffic::{TrafficConfig, PaddingStrategy, DecoyStrategy}; //! use bip324_traffic::futures::ShapedProtocol; -//! use bip324::{Network, Role}; +//! use bip324::Role; //! //! # #[tokio::main(flavor = "current_thread")] //! # async fn main() -> Result<(), Box> { @@ -60,7 +60,7 @@ //! .with_decoy_strategy(DecoyStrategy::Random); //! //! let mut protocol = ShapedProtocol::new( -//! Network::Bitcoin, +//! [0xF9, 0xBE, 0xB4, 0xD9], // Bitcoin mainnet magic bytes //! Role::Initiator, //! config, //! reader, diff --git a/traffic/tests/bitcoin_integration.rs b/traffic/tests/bitcoin_integration.rs index ee030cc..b33db93 100644 --- a/traffic/tests/bitcoin_integration.rs +++ b/traffic/tests/bitcoin_integration.rs @@ -33,7 +33,7 @@ fn sync_protocol_with_traffic_shaping() { // Initialize traffic-shaped protocol with handshake println!("Starting BIP-324 handshake with traffic shaping"); let mut protocol = ShapedProtocol::new( - bip324::Network::Regtest, + p2p::Magic::REGTEST, bip324::Role::Initiator, config, reader, @@ -152,7 +152,7 @@ async fn async_protocol_with_traffic_shaping() { // Initialize traffic-shaped async protocol with handshake println!("Starting async BIP-324 handshake with traffic shaping"); let mut protocol = ShapedProtocol::new( - bip324::Network::Regtest, + p2p::Magic::REGTEST, bip324::Role::Initiator, config, reader,