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..6c0f692 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, +//! MAGIC, //! Role::Initiator, //! None, // no garbage bytes //! None, // no decoy packets @@ -46,12 +46,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 +146,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 +166,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 +284,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 +292,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 +480,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 +494,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 +506,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 +533,7 @@ mod tests { let local_handshake = tokio::spawn(async move { handshake( - Network::Bitcoin, + MAGIC, Role::Initiator, None, None, @@ -545,7 +546,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..1f75e5e 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,7 +28,7 @@ //! let writer = stream; //! //! let mut protocol = Protocol::new( -//! bip324::Network::Bitcoin, +//! p2p::Magic::BITCOIN, //! bip324::Role::Initiator, //! None, None, // no garbage or decoys //! reader, @@ -35,11 +36,11 @@ //! )?; //! //! let ping_msg = NetworkMessage::Ping(0xdeadbeef); -//! let serialized = serialize(ping_msg); +//! let serialized = serialize(&V2NetworkMessage::new(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::{serialize, deserialize}; +//! use p2p::message::{NetworkMessage, V2NetworkMessage}; //! use tokio::net::TcpStream; //! use tokio::io::BufReader; //! @@ -63,7 +65,7 @@ //! let buffered_reader = BufReader::new(reader); //! //! let mut protocol = Protocol::new( -//! bip324::Network::Bitcoin, +//! p2p::Magic::BITCOIN, //! bip324::Role::Initiator, //! None, None, // no garbage or decoys //! buffered_reader, @@ -71,30 +73,11 @@ //! ).await?; //! //! let ping_msg = NetworkMessage::Ping(12345); // nonce -//! let serialized = serialize(ping_msg); +//! let serialized = serialize(&V2NetworkMessage::new(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 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,14 @@ 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 magic = magic.borrow(); + let salt = [ikm_salt, magic].concat(); 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 +774,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 +782,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 +792,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 +819,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 +875,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 +932,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + MAGIC, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys, Role::Initiator); @@ -984,7 +965,7 @@ mod tests { elliswift_bob, alice, ElligatorSwiftParty::A, - Network::Bitcoin, + MAGIC, ) .unwrap(); let mut alice_cipher = CipherSession::new(session_keys, Role::Initiator); @@ -1009,7 +990,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 +1057,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 +1106,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 +1159,7 @@ mod tests { elliswift_alice, alice, ElligatorSwiftParty::B, - Network::Bitcoin, + MAGIC, ) .unwrap(); let id = session_keys.session_id; @@ -1224,7 +1205,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 +1233,7 @@ mod tests { elliswift_alice, alice, ElligatorSwiftParty::B, - Network::Bitcoin, + MAGIC, ) .unwrap(); let id = session_keys.session_id; @@ -1294,7 +1275,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 +1311,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 +1352,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..17e2849 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, @@ -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/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,