From be8b4ed4c0cb44542eb68b56f103a8c23592bdd0 Mon Sep 17 00:00:00 2001 From: rustaceanrob Date: Thu, 24 Jul 2025 13:35:08 +0100 Subject: [PATCH] lib: remove the dependency on `bitcoin` --- protocol/Cargo.toml | 11 +- protocol/src/futures.rs | 20 +- protocol/src/handshake.rs | 80 +++---- protocol/src/io.rs | 28 ++- protocol/src/lib.rs | 93 +++----- protocol/src/serde.rs | 314 --------------------------- protocol/tests/round_trips.rs | 43 ++-- proxy/Cargo.toml | 3 +- proxy/src/bin/proxy.rs | 13 +- proxy/src/lib.rs | 18 +- traffic/Cargo.toml | 3 +- traffic/tests/bitcoin_integration.rs | 60 ++--- 12 files changed, 186 insertions(+), 500 deletions(-) delete mode 100644 protocol/src/serde.rs diff --git a/protocol/Cargo.toml b/protocol/Cargo.toml index 7ec4989..7edf12e 100644 --- a/protocol/Cargo.toml +++ b/protocol/Cargo.toml @@ -13,21 +13,24 @@ rust-version = "1.63.0" default = ["std"] # High-level wrappers using tokio traits - may affect MSRV requirements. tokio = ["std", "dep:tokio"] -std = ["bitcoin/std", "bitcoin_hashes/std", "chacha20-poly1305/std", "rand/std", "rand/std_rng"] +std = ["chacha20-poly1305/std", "bitcoin_hashes/std", "secp256k1/std", "rand/std", "rand/std_rng"] [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"] } -rand = { version = "0.8.0", default-features = false } -bitcoin = { version = "0.32.4", default-features = false } +secp256k1 = { version = "0.30.0", default-features = false, features = ["alloc"] } +rand = { version = "0.8.5", default-features = false } +bitcoin_hashes = { version ="0.15.0", default-features = false } +p2p = { package = "bitcoin-p2p-messages", default-features = false, git = "https://github.com/rust-bitcoin/rust-bitcoin", rev = "16cc257c3695dea0e7301a5fa9cab44b8ed60598" } + # 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", default-features = false } 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"] } hex = { package = "hex-conservative", version = "0.2.0" } +p2p = { package = "bitcoin-p2p-messages", default-features = true, git = "https://github.com/rust-bitcoin/rust-bitcoin", rev = "16cc257c3695dea0e7301a5fa9cab44b8ed60598" } tokio = { version = "1", features = ["io-util", "net", "rt-multi-thread", "macros"] } diff --git a/protocol/src/futures.rs b/protocol/src/futures.rs index 7c75bac..9c48f64 100644 --- a/protocol/src/futures.rs +++ b/protocol/src/futures.rs @@ -30,7 +30,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 @@ -51,7 +51,6 @@ 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 +145,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: p2p::Magic, role: Role, garbage: Option>, decoys: Option>>, @@ -166,7 +165,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 +283,7 @@ where /// /// * `Io` - Includes a flag for if the remote probably only understands the V1 protocol. pub async fn new( - network: Network, + magic: p2p::Magic, role: Role, garbage: Option>, decoys: Option>>, @@ -292,7 +291,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 +479,6 @@ where #[cfg(test)] mod tests { use super::*; - use bitcoin::Network; #[tokio::test] async fn test_async_handshake_functions() { @@ -493,7 +491,7 @@ mod tests { let local_handshake = tokio::spawn(async move { handshake( - Network::Bitcoin, + p2p::Magic::BITCOIN, Role::Initiator, Some(b"local garbage".to_vec()), Some(vec![b"local decoy".to_vec()]), @@ -505,7 +503,7 @@ mod tests { let remote_handshake = tokio::spawn(async move { handshake( - Network::Bitcoin, + p2p::Magic::BITCOIN, Role::Responder, Some(b"remote garbage".to_vec()), Some(vec![b"remote decoy 1".to_vec(), b"remote decoy 2".to_vec()]), @@ -532,7 +530,7 @@ mod tests { let local_handshake = tokio::spawn(async move { handshake( - Network::Bitcoin, + p2p::Magic::BITCOIN, Role::Initiator, None, None, @@ -545,7 +543,7 @@ mod tests { let remote_handshake = tokio::spawn(async move { let large_decoy = vec![0u8; MAX_PACKET_SIZE_FOR_ALLOCATION + 1]; handshake( - Network::Bitcoin, + p2p::Magic::BITCOIN, Role::Responder, None, Some(vec![large_decoy]), diff --git a/protocol/src/handshake.rs b/protocol/src/handshake.rs index 6c60440..35cd08d 100644 --- a/protocol/src/handshake.rs +++ b/protocol/src/handshake.rs @@ -8,20 +8,16 @@ //! 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 bitcoin::{ - key::Secp256k1, - secp256k1::{ - ellswift::{ElligatorSwift, ElligatorSwiftParty}, - PublicKey, SecretKey, Signing, - }, - Network, +use secp256k1::{ + ellswift::{ElligatorSwift, ElligatorSwiftParty}, + PublicKey, Secp256k1, SecretKey, Signing, }; -use rand::Rng; use crate::{ CipherSession, Error, OutboundCipher, PacketType, Role, SessionKeyMaterial, NUM_ELLIGATOR_SWIFT_BYTES, NUM_GARBAGE_TERMINTOR_BYTES, VERSION_CONTENT, }; +use rand::Rng; // Maximum number of garbage bytes before the terminator. const MAX_NUM_GARBAGE_BYTES: usize = 4095; @@ -93,7 +89,7 @@ pub enum VersionResult<'a> { /// 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, + magic: p2p::Magic, /// Local role in the handshake, initiator or responder. role: Role, /// State-specific data. @@ -103,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) -> p2p::Magic { + self.magic } /// Get the local role in the handshake. @@ -116,22 +112,22 @@ impl Handshake { impl Handshake { /// Initialize a V2 transport handshake with a remote peer. #[cfg(feature = "std")] - pub fn new(network: Network, role: Role) -> Result { + pub fn new(magic: p2p::Magic, role: Role) -> Result { let mut rng = 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: p2p::Magic, role: Role, rng: &mut impl Rng, curve: &Secp256k1, ) -> Result { let mut secret_key_buffer = [0u8; 32]; rng.fill(&mut secret_key_buffer[..]); - let sk = SecretKey::from_slice(&secret_key_buffer)?; + let sk = SecretKey::from_byte_array(&secret_key_buffer)?; let pk = PublicKey::from_secret_key(curve, &sk); let es = ElligatorSwift::from_pubkey(pk); @@ -141,7 +137,7 @@ impl Handshake { }; Ok(Handshake { - network, + magic, role, state: Initialized { point }, }) @@ -198,7 +194,7 @@ impl Handshake { } Ok(Handshake { - network: self.network, + magic: self.magic, role: self.role, state: SentKey { point: self.state.point, @@ -235,8 +231,8 @@ 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( + if self.magic + == p2p::Magic::from_bytes( their_key[..4] .try_into() .expect("64 byte array to have 4 byte prefix"), @@ -266,11 +262,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 +369,7 @@ impl<'a> Handshake> { )?; Ok(Handshake { - network: self.network, + magic: self.magic, role: self.role, state: SentVersion { cipher, @@ -414,7 +410,7 @@ impl Handshake { /// use bip324::{Handshake, GarbageResult, SentVersion}; /// # use bip324::{Role, Network}; /// # 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 +441,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, @@ -534,7 +530,7 @@ impl<'a> Handshake> { /// use bip324::{Handshake, VersionResult, ReceivedGarbage, NUM_LENGTH_BYTES}; /// # use bip324::{Role, Network}; /// # 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!(); @@ -602,7 +598,7 @@ mod tests { let responder_garbage = vec![4u8, 5u8]; let init_handshake = - Handshake::::new(Network::Bitcoin, Role::Initiator).unwrap(); + Handshake::::new(p2p::Magic::BITCOIN, Role::Initiator).unwrap(); // Send initiator key + garbage. let mut init_buffer = @@ -612,7 +608,7 @@ mod tests { .unwrap(); let resp_handshake = - Handshake::::new(Network::Bitcoin, Role::Responder).unwrap(); + Handshake::::new(p2p::Magic::BITCOIN, Role::Responder).unwrap(); // Send responder key + garbage. let mut resp_buffer = @@ -740,21 +736,24 @@ 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(p2p::Magic::BITCOIN, 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(p2p::Magic::BITCOIN, 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(p2p::Magic::BITCOIN, 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 +761,8 @@ mod tests { ); // Test with no garbage - let handshake = Handshake::::new(Network::Bitcoin, Role::Initiator).unwrap(); + let handshake = + Handshake::::new(p2p::Magic::BITCOIN, Role::Initiator).unwrap(); let result = handshake.send_key(None, &mut buffer); assert!(result.is_ok()); } @@ -773,9 +773,9 @@ mod tests { #[test] fn test_handshake_receive_garbage_buffer() { let init_handshake = - Handshake::::new(Network::Bitcoin, Role::Initiator).unwrap(); + Handshake::::new(p2p::Magic::BITCOIN, Role::Initiator).unwrap(); let resp_handshake = - Handshake::::new(Network::Bitcoin, Role::Responder).unwrap(); + Handshake::::new(p2p::Magic::BITCOIN, 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 +855,8 @@ 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(p2p::Magic::BITCOIN, Role::Initiator).unwrap(); let mut buffer = vec![0u8; NUM_ELLIGATOR_SWIFT_BYTES]; let handshake = handshake.send_key(None, &mut buffer).unwrap(); @@ -890,23 +891,26 @@ 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(p2p::Magic::BITCOIN, 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()); + let magic = p2p::Magic::BITCOIN.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(p2p::Magic::SIGNET, 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()); + let foo_magic = p2p::Magic::SIGNET.to_bytes(); + v1_testnet_key[..4].copy_from_slice(&foo_magic); 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 845796e..a449a2e 100644 --- a/protocol/src/io.rs +++ b/protocol/src/io.rs @@ -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 @@ -49,8 +49,6 @@ 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 +277,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: p2p::Magic, role: Role, garbage: Option>, decoys: Option>>, @@ -290,7 +288,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 +450,7 @@ where /// /// * `Io` - Includes a flag for if the remote probably only understands the V1 protocol. pub fn new( - network: Network, + magic: p2p::Magic, role: Role, garbage: Option>, decoys: Option>>, @@ -460,7 +458,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,7 +580,7 @@ where #[cfg(test)] mod tests { use super::*; - use rand::{rngs::StdRng, SeedableRng}; + use secp256k1::rand::{rngs::StdRng, SeedableRng}; use std::io::Cursor; /// Generate deterministic handshake messages for testing. @@ -594,12 +592,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, + p2p::Magic::BITCOIN, local_role, &mut local_rng, &secp, @@ -612,7 +610,7 @@ mod tests { Role::Responder => Role::Initiator, }; let remote_handshake = Handshake::::new_with_rng( - Network::Bitcoin, + p2p::Magic::BITCOIN, remote_role, &mut remote_rng, &secp, @@ -658,9 +656,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, + p2p::Magic::BITCOIN, Role::Initiator, &mut init_rng, &secp, @@ -708,9 +706,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, + p2p::Magic::BITCOIN, Role::Initiator, &mut init_rng, &secp, diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index 7a626c3..def7cb5 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -15,7 +15,8 @@ //! //! ```no_run //! use bip324::io::{Protocol, Payload}; -//! use bip324::serde::{serialize, deserialize, NetworkMessage}; +//! use bitcoin::consensus::{serialize, deserialize}; +//! use p2p::message::{NetworkMessage, V2NetworkMessage}; //! use std::net::TcpStream; //! use std::io::BufReader; //! @@ -27,19 +28,19 @@ //! let writer = stream; //! //! let mut protocol = Protocol::new( -//! bip324::Network::Bitcoin, +//! bip324::p2p::Magic::BITCOIN, //! bip324::Role::Initiator, //! None, None, // no garbage or decoys //! reader, //! writer, //! )?; //! -//! let ping_msg = NetworkMessage::Ping(0xdeadbeef); -//! let serialized = serialize(ping_msg); +//! let ping_msg = V2NetworkMessage::new(NetworkMessage::Ping(0xdeadbeef)); +//! let serialized = serialize(&ping_msg); //! protocol.write(&Payload::genuine(serialized))?; //! //! let response = protocol.read()?; -//! let response_msg: NetworkMessage = deserialize(&response.contents())?; +//! let response_msg: V2NetworkMessage = deserialize(&response.contents())?; //! # Ok(()) //! # } //! ``` @@ -52,7 +53,8 @@ //! # async fn main() -> Result<(), Box> { //! use bip324::futures::Protocol; //! use bip324::io::Payload; -//! use bip324::serde::{serialize, deserialize, NetworkMessage}; +//! use bitcoin::consensus::{deserialize, serialize}; +//! use p2p::message::{NetworkMessage, V2NetworkMessage}; //! use tokio::net::TcpStream; //! use tokio::io::BufReader; //! @@ -63,38 +65,19 @@ //! let buffered_reader = BufReader::new(reader); //! //! let mut protocol = Protocol::new( -//! bip324::Network::Bitcoin, +//! bip324::p2p::Magic::BITCOIN, //! 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); +//! let ping_msg = V2NetworkMessage::new(NetworkMessage::Ping(12345)); // nonce +//! let serialized = serialize(&ping_msg); //! protocol.write(&Payload::genuine(serialized)).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)?; +//! let response_msg: V2NetworkMessage = deserialize(&response.contents())?; //! # 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 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, @@ -234,10 +213,10 @@ impl fmt::Display for Error { "Packet size exceeds maximum 4MiB size for automatic allocation." ), Error::NoGarbageTerminator => { - write!(f, "More than 4095 bytes of garbage recieved in the handshake before a terminator was sent.") + write!(f, "More than 4095 bytes of garbage received in the handshake before a terminator was sent.") } Error::SecretGeneration(e) => write!(f, "Cannot generate secrets: {e:?}."), - Error::Decryption(e) => write!(f, "Decrytion error: {e:?}."), + Error::Decryption(e) => write!(f, "Decryption error: {e:?}."), Error::V1Protocol => write!(f, "The remote peer is communicating on the V1 protocol."), Error::TooMuchGarbage => write!( f, @@ -347,13 +326,13 @@ impl SessionKeyMaterial { b: ElligatorSwift, secret: SecretKey, party: ElligatorSwiftParty, - network: Network, + magic: p2p::Magic, ) -> 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 magic = magic.to_bytes(); let salt = [ikm_salt, &magic].concat(); let hk = Hkdf::::new(salt.as_slice(), ecdh_sk.as_secret_bytes()); let mut session_id = [0u8; 32]; @@ -376,7 +355,7 @@ impl SessionKeyMaterial { hk.expand(garbage_info, &mut garbage)?; let initiator_garbage_terminator: [u8; 16] = garbage[..16] .try_into() - .expect("first 16 btyes of expanded garbage"); + .expect("first 16 bytes of expanded garbage"); let responder_garbage_terminator: [u8; 16] = garbage[16..] .try_into() .expect("last 16 bytes of expanded garbage"); @@ -787,11 +766,11 @@ impl CipherSession { mod tests { use super::*; - use bitcoin::secp256k1::ellswift::{ElligatorSwift, ElligatorSwiftParty}; - use bitcoin::secp256k1::SecretKey; use core::str::FromStr; use hex::prelude::*; - use rand::Rng; + use secp256k1::ellswift::{ElligatorSwift, ElligatorSwiftParty}; + use secp256k1::rand::{self, Rng}; + use secp256k1::SecretKey; use std::vec; use std::vec::Vec; @@ -812,7 +791,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + p2p::Magic::BITCOIN, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Initiator); @@ -868,7 +847,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + p2p::Magic::BITCOIN, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Initiator); @@ -925,7 +904,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + p2p::Magic::BITCOIN, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys, Role::Initiator); @@ -958,7 +937,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + p2p::Magic::BITCOIN, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys, Role::Initiator); @@ -972,7 +951,7 @@ mod tests { #[test] fn test_fuzz_packets() { - let mut rng = rand::thread_rng(); + let mut rng = secp256k1::rand::thread_rng(); let alice = SecretKey::from_str("61062ea5071d800bbfd59e2e8b53d47d194b095ae5a4df04936b49772ef0d4d7") .unwrap(); @@ -983,7 +962,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + p2p::Magic::BITCOIN, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Initiator); @@ -1050,7 +1029,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + p2p::Magic::BITCOIN, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Initiator); @@ -1099,7 +1078,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + p2p::Magic::BITCOIN, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Initiator); @@ -1152,7 +1131,7 @@ mod tests { elliswift_alice, alice, ElligatorSwiftParty::B, - Network::Bitcoin, + p2p::Magic::BITCOIN, ) .unwrap(); let id = session_keys.session_id; @@ -1198,7 +1177,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + p2p::Magic::BITCOIN, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Initiator); @@ -1226,7 +1205,7 @@ mod tests { elliswift_alice, alice, ElligatorSwiftParty::B, - Network::Bitcoin, + p2p::Magic::BITCOIN, ) .unwrap(); let id = session_keys.session_id; @@ -1268,7 +1247,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + p2p::Magic::BITCOIN, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Initiator); @@ -1304,7 +1283,7 @@ mod tests { elliswift_alice, alice, ElligatorSwiftParty::B, - Network::Bitcoin, + p2p::Magic::BITCOIN, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys.clone(), Role::Responder); @@ -1345,7 +1324,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + p2p::Magic::BITCOIN, ) .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..62e7560 100644 --- a/protocol/tests/round_trips.rs +++ b/protocol/tests/round_trips.rs @@ -164,11 +164,15 @@ 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::{deserialize, serialize}; + use p2p::{ + message::{NetworkMessage, V2NetworkMessage}, + message_network::VersionMessage, + Address, ServiceFlags, + }; let bitcoind = regtest_process(TransportVersion::V2); let mut stream = TcpStream::connect(bitcoind.params.p2p_socket.unwrap()).unwrap(); @@ -272,7 +276,7 @@ fn regtest_handshake() { start_height: 0, relay: false, }; - let message = serialize(NetworkMessage::Version(msg)); + let message = 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: V2NetworkMessage = 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::{deserialize, serialize}; + use p2p::{ + message::{NetworkMessage, V2NetworkMessage}, + message_network::VersionMessage, + Address, ServiceFlags, }; - use bitcoin::p2p::{message_network::VersionMessage, Address, ServiceFlags}; let bitcoind = regtest_process(TransportVersion::V2); @@ -348,14 +354,14 @@ fn regtest_handshake_std() { relay: false, }; - let message = serialize(NetworkMessage::Version(msg)); + let message = 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: V2NetworkMessage = 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::{deserialize, serialize}; + use p2p::{ + message::{NetworkMessage, V2NetworkMessage}, + message_network::VersionMessage, + Address, ServiceFlags, }; - use bitcoin::p2p::{message_network::VersionMessage, Address, ServiceFlags}; use tokio::net::TcpStream; let bitcoind = regtest_process(TransportVersion::V2); @@ -419,14 +426,14 @@ async fn regtest_handshake_async() { relay: false, }; - let message = serialize(NetworkMessage::Version(msg)); + let message = 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: V2NetworkMessage = deserialize(payload.contents()).unwrap(); assert_eq!(response_message.cmd(), "version"); println!("Successfully exchanged version messages using async Protocol API!"); @@ -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/proxy/Cargo.toml b/proxy/Cargo.toml index 068a576..3069888 100644 --- a/proxy/Cargo.toml +++ b/proxy/Cargo.toml @@ -11,7 +11,8 @@ readme = "README.md" spec = "config_spec.toml" [dependencies] -bitcoin = { version = "0.32.4" } +bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin", default-features = true, rev = "16cc257c3695dea0e7301a5fa9cab44b8ed60598" } +p2p = { package = "bitcoin-p2p-messages", git = "https://github.com/rust-bitcoin/rust-bitcoin", rev = "16cc257c3695dea0e7301a5fa9cab44b8ed60598" } tokio = { version = "1", features = ["full"] } hex = { package = "hex-conservative", version = "0.2.0" } bip324 = { version = "0.10.0", path = "../protocol", features = ["tokio"] } diff --git a/proxy/src/bin/proxy.rs b/proxy/src/bin/proxy.rs index 8df624b..6f6f5f7 100644 --- a/proxy/src/bin/proxy.rs +++ b/proxy/src/bin/proxy.rs @@ -8,12 +8,15 @@ use std::str::FromStr; use bip324::{ futures::Protocol, io::{Payload, ProtocolFailureSuggestion}, - serde::{deserialize, serialize}, PacketType, Role, }; use bip324_proxy::{V1ProtocolReader, V1ProtocolWriter}; -use bitcoin::Network; +use bitcoin::{ + consensus::{deserialize, serialize}, + Network, +}; use log::{debug, error, info}; +use p2p::message::V2NetworkMessage; use tokio::{ net::{TcpListener, TcpStream}, select, @@ -116,7 +119,7 @@ async fn v2_proxy( ); v2_remote_writer - .write(&Payload::genuine(serialize(msg))) + .write(&Payload::genuine(serialize(&msg))) .await .expect("write to remote"); }, @@ -124,13 +127,13 @@ async fn v2_proxy( let payload = result.expect("read packet"); // Ignore decoy packets. if payload.packet_type() == PacketType::Genuine { - let msg = deserialize(payload.contents()) + let msg: V2NetworkMessage = deserialize(payload.contents()) .expect("deserializable contents into network message"); debug!( "Read {} message from remote, writing to client.", msg.command() ); - v1_client_writer.write(msg).await.expect("write to client"); + v1_client_writer.write(msg.into_payload()).await.expect("write to client"); } }, } diff --git a/proxy/src/lib.rs b/proxy/src/lib.rs index cd8fa6f..34cda46 100644 --- a/proxy/src/lib.rs +++ b/proxy/src/lib.rs @@ -5,18 +5,18 @@ //! The V1 and V2 p2p protocols have different header encodings, so a proxy has to do //! a little more work than just encrypt/decrypt. The [`NetworkMessage`] //! type is the intermediate state for messages. The V1 side can use the RawNetworkMessage wrapper, but the V2 side -//! cannot since things like the checksum are not relevant (those responsibilites are pushed +//! cannot since things like the checksum are not relevant (those responsibilities are pushed //! onto the transport in V2). use std::fmt; use std::net::SocketAddr; use bitcoin::consensus::{Decodable, Encodable}; -use bitcoin::p2p::message::{NetworkMessage, RawNetworkMessage}; -use bitcoin::p2p::Address; use bitcoin::Network; use hex::prelude::*; use log::debug; +use p2p::message::{NetworkMessage, RawNetworkMessage}; +use p2p::{Address, NetworkExt}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::net::TcpStream; @@ -27,7 +27,7 @@ const VERSION_COMMAND: [u8; 12] = [ 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, ]; -/// An error occured while establishing the proxy connection or during the main loop. +/// An error occurred while establishing the proxy connection or during the main loop. #[derive(Debug)] pub enum Error { WrongNetwork, @@ -40,9 +40,9 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Error::WrongNetwork => write!(f, "recieved message on wrong network"), + Error::WrongNetwork => write!(f, "received message on wrong network"), Error::Io(e) => write!(f, "network {e:?}"), - Error::WrongCommand => write!(f, "recieved message with wrong command"), + Error::WrongCommand => write!(f, "received message with wrong command"), Error::Protocol(e) => write!(f, "protocol error {e:?}"), Error::Serde => write!(f, "unable to serialize command"), } @@ -82,7 +82,8 @@ pub async fn peek_addr(client: &TcpStream, network: Network) -> Result V1ProtocolWriter { /// Write message to the output stream using v1. pub async fn write(&mut self, msg: NetworkMessage) -> Result<(), Error> { - let raw = RawNetworkMessage::new(self.network.magic(), msg); + let magic = self.network.default_network_magic(); + let raw = RawNetworkMessage::new(magic, msg); let mut buffer = vec![]; raw.consensus_encode(&mut buffer) .map_err(|_| Error::Serde)?; diff --git a/traffic/Cargo.toml b/traffic/Cargo.toml index 443e8c9..22183ef 100644 --- a/traffic/Cargo.toml +++ b/traffic/Cargo.toml @@ -19,5 +19,6 @@ tokio = { version = "1", features = ["sync", "time", "rt", "macros"], optional = [dev-dependencies] bitcoind = { package = "corepc-node", version = "0.7.1", default-features = false, features = ["26_0","download"] } -bitcoin = { version = "0.32.4" } +bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin", default-features = false, rev = "16cc257c3695dea0e7301a5fa9cab44b8ed60598" } +p2p = { package = "bitcoin-p2p-messages", git = "https://github.com/rust-bitcoin/rust-bitcoin", rev = "16cc257c3695dea0e7301a5fa9cab44b8ed60598" } tokio = { version = "1", features = ["sync", "time", "rt", "macros", "net", "io-util"] } diff --git a/traffic/tests/bitcoin_integration.rs b/traffic/tests/bitcoin_integration.rs index 2c03c91..920da06 100644 --- a/traffic/tests/bitcoin_integration.rs +++ b/traffic/tests/bitcoin_integration.rs @@ -11,12 +11,14 @@ fn sync_protocol_with_traffic_shaping() { time::{SystemTime, UNIX_EPOCH}, }; - use bip324::{ - io::Payload, - serde::{deserialize, serialize, NetworkMessage}, - }; + use bip324::io::Payload; use bip324_traffic::{io::ShapedProtocol, DecoyStrategy, PaddingStrategy, TrafficConfig}; - use bitcoin::p2p::{message_network::VersionMessage, Address, ServiceFlags}; + use bitcoin::consensus::{deserialize, serialize}; + use p2p::{ + message::{NetworkMessage, V2NetworkMessage}, + message_network::VersionMessage, + Address, ServiceFlags, + }; let bitcoind = regtest_process(); @@ -62,43 +64,43 @@ fn sync_protocol_with_traffic_shaping() { }; // Send version message - let version_message = NetworkMessage::Version(msg); + let version_message = V2NetworkMessage::new(NetworkMessage::Version(msg)); println!("Sending version message with traffic shaping"); protocol - .write(&Payload::genuine(serialize(version_message))) + .write(&Payload::genuine(serialize(&version_message))) .unwrap(); // Read version response println!("Reading version response"); let response = protocol.read().unwrap(); - let response_message: NetworkMessage = deserialize(response.contents()).unwrap(); + let response_message: V2NetworkMessage = deserialize(response.contents()).unwrap(); assert_eq!(response_message.cmd(), "version"); // Send verack - let verack_message = NetworkMessage::Verack; + let verack_message = V2NetworkMessage::new(NetworkMessage::Verack); println!("Sending verack with traffic shaping"); protocol - .write(&Payload::genuine(serialize(verack_message))) + .write(&Payload::genuine(serialize(&verack_message))) .unwrap(); // Read verack response println!("Reading verack response"); let response = protocol.read().unwrap(); - let response_message: NetworkMessage = deserialize(response.contents()).unwrap(); + let response_message: V2NetworkMessage = deserialize(response.contents()).unwrap(); assert_eq!(response_message.cmd(), "verack"); // Exchange a few ping/pong messages to verify the connection remains stable for i in 0..3 { - let ping_message = NetworkMessage::Ping(i); + let ping_message = V2NetworkMessage::new(NetworkMessage::Ping(i)); println!("Sending ping {i} with traffic shaping"); protocol - .write(&Payload::genuine(serialize(ping_message))) + .write(&Payload::genuine(serialize(&ping_message))) .unwrap(); // Read until we get a pong (might get other messages) loop { let response = protocol.read().unwrap(); - let response_message: NetworkMessage = deserialize(response.contents()).unwrap(); + let response_message: V2NetworkMessage = deserialize(response.contents()).unwrap(); if response_message.cmd() == "pong" { println!("Received pong {i}"); break; @@ -122,12 +124,14 @@ async fn async_protocol_with_traffic_shaping() { time::{SystemTime, UNIX_EPOCH}, }; - use bip324::{ - io::Payload, - serde::{deserialize, serialize, NetworkMessage}, - }; + use bip324::io::Payload; use bip324_traffic::{futures::ShapedProtocol, DecoyStrategy, PaddingStrategy, TrafficConfig}; - use bitcoin::p2p::{message_network::VersionMessage, Address, ServiceFlags}; + use bitcoin::consensus::{deserialize, serialize}; + use p2p::{ + message::{NetworkMessage, V2NetworkMessage}, + message_network::VersionMessage, + Address, ServiceFlags, + }; use tokio::net::TcpStream; let bitcoind = regtest_process(); @@ -177,46 +181,46 @@ async fn async_protocol_with_traffic_shaping() { }; // Send version message - let version_message = NetworkMessage::Version(msg); + let version_message = V2NetworkMessage::new(NetworkMessage::Version(msg)); println!("Sending version message with async traffic shaping"); protocol - .write(&Payload::genuine(serialize(version_message))) + .write(&Payload::genuine(serialize(&version_message))) .await .unwrap(); // Read version response println!("Reading version response"); let response = protocol.read().await.unwrap(); - let response_message: NetworkMessage = deserialize(response.contents()).unwrap(); + let response_message: V2NetworkMessage = deserialize(response.contents()).unwrap(); assert_eq!(response_message.cmd(), "version"); // Send verack - let verack_message = NetworkMessage::Verack; + let verack_message = V2NetworkMessage::new(NetworkMessage::Verack); println!("Sending verack with async traffic shaping"); protocol - .write(&Payload::genuine(serialize(verack_message))) + .write(&Payload::genuine(serialize(&verack_message))) .await .unwrap(); // Read verack response println!("Reading verack response"); let response = protocol.read().await.unwrap(); - let response_message: NetworkMessage = deserialize(response.contents()).unwrap(); + let response_message: V2NetworkMessage = deserialize(response.contents()).unwrap(); assert_eq!(response_message.cmd(), "verack"); // Exchange a few ping/pong messages to verify the connection remains stable for i in 0..3 { - let ping_message = NetworkMessage::Ping(i); + let ping_message = V2NetworkMessage::new(NetworkMessage::Ping(i)); println!("Sending async ping {i} with traffic shaping"); protocol - .write(&Payload::genuine(serialize(ping_message))) + .write(&Payload::genuine(serialize(&ping_message))) .await .unwrap(); // Read until we get a pong (might get other messages) loop { let response = protocol.read().await.unwrap(); - let response_message: NetworkMessage = deserialize(response.contents()).unwrap(); + let response_message: V2NetworkMessage = deserialize(response.contents()).unwrap(); if response_message.cmd() == "pong" { println!("Received async pong {i}"); break;