From 2017659465397449a790307e38de953c8e021e7d Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Thu, 3 Jul 2025 11:13:33 -0700 Subject: [PATCH 1/2] feat!: remove the futures flag Simplifying the protocol crate's interface by dropping the more general futures flag in favor of just the tokio one. All users of the library have been tokio runtime users. If the rust standard library gets its act together and adopts some async I/O interfaces, those will be used in the future instead. Until then, simplifying the interface to explore other things like ffi bindings. --- protocol/Cargo.toml | 3 --- protocol/README.md | 5 ++--- protocol/src/io.rs | 40 +++++++++++++--------------------------- protocol/src/lib.rs | 9 +-------- proxy/src/bin/proxy.rs | 5 +++-- proxy/src/lib.rs | 6 +++--- 6 files changed, 22 insertions(+), 46 deletions(-) diff --git a/protocol/Cargo.toml b/protocol/Cargo.toml index 1d21473..a8e4343 100644 --- a/protocol/Cargo.toml +++ b/protocol/Cargo.toml @@ -11,14 +11,11 @@ rust-version = "1.63.0" [features] default = ["std"] -# High-level wrappers using futures traits. -futures = ["std", "dep:futures"] # 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"] [dependencies] -futures = { version = "0.3.30", default-features = false, optional = true, features = ["std"] } # 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"] } diff --git a/protocol/README.md b/protocol/README.md index 26c0246..d4db084 100644 --- a/protocol/README.md +++ b/protocol/README.md @@ -4,15 +4,14 @@ A BIP324 library to establish and communicate over an encrypted channel. The library is designed with a bare `no_std` and "Sans I/O" interface to keep it as agnostic as possible to application runtimes, but higher level interfaces are exposed for ease of use. -The `futures` feature includes the high-level `AsyncProcotol` type which helps create and manage an encrypted channel. +The `tokio` feature includes the high-level `AsyncProcotol` type which helps create and manage an encrypted channel. The lower-level `CipherSession` and `Handshake` types can be directly used by applications which require more control. The handshake performs the one-and-a-half round trip dance between the peers in order to generate secret materials and verify a channel. A successful handshake results in a cipher session which performs the encrypt and decrypt operations for the lifetime of the channel. ## Feature Flags * `std` -- Standard library dependencies for I/O, memory allocation, and random number generators. -* `futures` -- High level wrappers for asynchronous read and write runtimes using agnostic futures-rs traits. -* `tokio` -- Same wrappers as `futures`, but using the popular tokio runtime's specific traits instead of futures-rs. +* `tokio` -- High level I/O wrappers for asynchronous tokio runtime. ## Minimum Supported Rust Version (MSRV) diff --git a/protocol/src/io.rs b/protocol/src/io.rs index 40d34ab..05e8112 100644 --- a/protocol/src/io.rs +++ b/protocol/src/io.rs @@ -4,35 +4,26 @@ //! connections over Read/Write transports. use core::fmt; - -#[cfg(feature = "std")] use std::vec; -#[cfg(feature = "std")] use std::vec::Vec; use bitcoin::Network; -// Default to the futures-rs traits, but can overwrite with more specific -// tokio implementations for easier caller integration. -#[cfg(all(feature = "futures", not(feature = "tokio")))] -use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; -#[cfg(feature = "tokio")] -use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; - use crate::{ handshake::{self, GarbageResult, VersionResult}, Error, Handshake, InboundCipher, OutboundCipher, PacketType, Role, NUM_ELLIGATOR_SWIFT_BYTES, NUM_GARBAGE_TERMINTOR_BYTES, }; +#[cfg(feature = "tokio")] +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; + /// A decrypted BIP324 payload with its packet type. -#[cfg(feature = "std")] pub struct Payload { contents: Vec, packet_type: PacketType, } -#[cfg(feature = "std")] impl Payload { /// Create a new payload. pub fn new(contents: Vec, packet_type: PacketType) -> Self { @@ -54,7 +45,6 @@ impl Payload { } /// High level error type for the protocol interface. -#[cfg(feature = "std")] #[derive(Debug)] pub enum ProtocolError { /// Wrap all IO errors with suggestion for next step on failure. @@ -64,7 +54,6 @@ pub enum ProtocolError { } /// Suggest to caller next step on protocol failure. -#[cfg(feature = "std")] #[derive(Debug)] pub enum ProtocolFailureSuggestion { /// Caller could attempt to retry the connection with protocol V1 if desired. @@ -73,7 +62,6 @@ pub enum ProtocolFailureSuggestion { Abort, } -#[cfg(feature = "std")] impl From for ProtocolError { fn from(error: std::io::Error) -> Self { // Detect IO errors which possibly mean the remote doesn't understand @@ -94,14 +82,12 @@ impl From for ProtocolError { } } -#[cfg(feature = "std")] impl From for ProtocolError { fn from(error: Error) -> Self { ProtocolError::Internal(error) } } -#[cfg(feature = "std")] impl ProtocolError { /// Create an EOF error that suggests retrying with V1 protocol. /// @@ -118,7 +104,6 @@ impl ProtocolError { } } -#[cfg(feature = "std")] impl std::error::Error for ProtocolError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { @@ -128,7 +113,6 @@ impl std::error::Error for ProtocolError { } } -#[cfg(feature = "std")] impl fmt::Display for ProtocolError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -149,13 +133,13 @@ impl fmt::Display for ProtocolError { } /// A protocol session with handshake and send/receive packet management. -#[cfg(any(feature = "futures", feature = "tokio"))] +#[cfg(feature = "tokio")] pub struct AsyncProtocol { reader: AsyncProtocolReader, writer: AsyncProtocolWriter, } -#[cfg(any(feature = "futures", feature = "tokio"))] +#[cfg(feature = "tokio")] impl AsyncProtocol { /// New protocol session which completes the initial handshake and returns a handler. /// @@ -297,7 +281,9 @@ impl AsyncProtocol { } /// State machine of an asynchronous packet read. -#[cfg(any(feature = "futures", feature = "tokio"))] +/// +/// This maintains state between await points to ensure cancellation safety. +#[cfg(feature = "tokio")] #[derive(Debug)] enum DecryptState { ReadingLength { @@ -310,7 +296,7 @@ enum DecryptState { }, } -#[cfg(any(feature = "futures", feature = "tokio"))] +#[cfg(feature = "tokio")] impl DecryptState { /// Transition state to reading the length bytes. fn init_reading_length() -> Self { @@ -330,13 +316,13 @@ impl DecryptState { } /// Manages an async buffer to automatically decrypt contents of received packets. -#[cfg(any(feature = "futures", feature = "tokio"))] +#[cfg(feature = "tokio")] pub struct AsyncProtocolReader { inbound_cipher: InboundCipher, state: DecryptState, } -#[cfg(any(feature = "futures", feature = "tokio"))] +#[cfg(feature = "tokio")] impl AsyncProtocolReader { /// Decrypt contents of received packet from buffer. /// @@ -397,12 +383,12 @@ impl AsyncProtocolReader { } /// Manages an async buffer to automatically encrypt and send contents in packets. -#[cfg(any(feature = "futures", feature = "tokio"))] +#[cfg(feature = "tokio")] pub struct AsyncProtocolWriter { outbound_cipher: OutboundCipher, } -#[cfg(any(feature = "futures", feature = "tokio"))] +#[cfg(feature = "tokio")] impl AsyncProtocolWriter { /// Encrypt contents and write packet buffer. /// diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index 99224c7..6996762 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -23,7 +23,7 @@ extern crate std; mod fschacha20poly1305; mod handshake; -#[cfg(any(feature = "futures", feature = "tokio"))] +#[cfg(feature = "std")] pub mod io; #[cfg(feature = "std")] pub mod serde; @@ -43,14 +43,7 @@ pub use handshake::{ GarbageResult, Handshake, Initialized, ReceivedGarbage, ReceivedKey, SentKey, SentVersion, VersionResult, }; -// Re-exports from io module (async I/O types for backwards compatibility) -#[cfg(any(feature = "futures", feature = "tokio"))] -pub use io::{ - AsyncProtocol, AsyncProtocolReader, AsyncProtocolWriter, Payload, ProtocolError, - ProtocolFailureSuggestion, -}; -// Internal imports use fschacha20poly1305::{FSChaCha20, FSChaCha20Poly1305}; /// Value for header byte with the decoy flag flipped to true. diff --git a/proxy/src/bin/proxy.rs b/proxy/src/bin/proxy.rs index 1320726..4dacf49 100644 --- a/proxy/src/bin/proxy.rs +++ b/proxy/src/bin/proxy.rs @@ -6,8 +6,9 @@ use std::str::FromStr; use bip324::{ + io::{AsyncProtocol, ProtocolFailureSuggestion}, serde::{deserialize, serialize}, - AsyncProtocol, PacketType, ProtocolFailureSuggestion, Role, + PacketType, Role, }; use bip324_proxy::{V1ProtocolReader, V1ProtocolWriter}; use bitcoin::Network; @@ -87,7 +88,7 @@ async fn v2_proxy( .await { Ok(p) => p, - Err(bip324::ProtocolError::Io(_, ProtocolFailureSuggestion::RetryV1)) if v1_fallback => { + Err(bip324::io::ProtocolError::Io(_, ProtocolFailureSuggestion::RetryV1)) if v1_fallback => { info!("V2 protocol failed, falling back to V1..."); return v1_proxy(client, network).await; } diff --git a/proxy/src/lib.rs b/proxy/src/lib.rs index 6f65ec4..cd8fa6f 100644 --- a/proxy/src/lib.rs +++ b/proxy/src/lib.rs @@ -34,7 +34,7 @@ pub enum Error { WrongCommand, Serde, Io(std::io::Error), - Protocol(bip324::ProtocolError), + Protocol(bip324::io::ProtocolError), } impl fmt::Display for Error { @@ -61,8 +61,8 @@ impl std::error::Error for Error { } } -impl From for Error { - fn from(e: bip324::ProtocolError) -> Self { +impl From for Error { + fn from(e: bip324::io::ProtocolError) -> Self { Error::Protocol(e) } } From ab6e129ce322b9a9da1334572ed1035bf8945456 Mon Sep 17 00:00:00 2001 From: Nick Johnson Date: Thu, 3 Jul 2025 11:38:57 -0700 Subject: [PATCH 2/2] fix: temporarily feature gate shared imports Adding a std protocol will clean up all these gates. --- protocol/src/io.rs | 8 +++++++- proxy/src/bin/proxy.rs | 4 +++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/protocol/src/io.rs b/protocol/src/io.rs index 05e8112..726bd5d 100644 --- a/protocol/src/io.rs +++ b/protocol/src/io.rs @@ -4,14 +4,19 @@ //! connections over Read/Write transports. use core::fmt; +#[cfg(feature = "tokio")] use std::vec; use std::vec::Vec; +use crate::{Error, PacketType}; + +#[cfg(feature = "tokio")] use bitcoin::Network; +#[cfg(feature = "tokio")] use crate::{ handshake::{self, GarbageResult, VersionResult}, - Error, Handshake, InboundCipher, OutboundCipher, PacketType, Role, NUM_ELLIGATOR_SWIFT_BYTES, + Handshake, InboundCipher, OutboundCipher, Role, NUM_ELLIGATOR_SWIFT_BYTES, NUM_GARBAGE_TERMINTOR_BYTES, }; @@ -93,6 +98,7 @@ impl ProtocolError { /// /// This is used when the remote peer closes the connection during handshake, /// which often indicates they don't support the V2 protocol. + #[cfg(feature = "tokio")] fn eof() -> Self { ProtocolError::Io( std::io::Error::new( diff --git a/proxy/src/bin/proxy.rs b/proxy/src/bin/proxy.rs index 4dacf49..2e58aaa 100644 --- a/proxy/src/bin/proxy.rs +++ b/proxy/src/bin/proxy.rs @@ -88,7 +88,9 @@ async fn v2_proxy( .await { Ok(p) => p, - Err(bip324::io::ProtocolError::Io(_, ProtocolFailureSuggestion::RetryV1)) if v1_fallback => { + Err(bip324::io::ProtocolError::Io(_, ProtocolFailureSuggestion::RetryV1)) + if v1_fallback => + { info!("V2 protocol failed, falling back to V1..."); return v1_proxy(client, network).await; }