From 27c7c4e26a3eeef3b85c9a392e3abaabef089fa9 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Wed, 4 Oct 2023 05:51:26 +0000 Subject: [PATCH] Add a `bitcoin_io` crate In order to support standard (de)serialization of structs, the `rust-bitcoin` ecosystem uses the standard `std::io::{Read,Write}` traits. This works great for environments with `std`, however sadly the `std::io` module has not yet been added to the `core` crate. Thus, in `no-std`, the `rust-bitcoin` ecosystem has historically used the `core2` crate to provide copies of the `std::io` module without any major dependencies. Sadly, its one dependency, `memchr`, recently broke our MSRV. Worse, because we didn't want to take on any excess dependencies for `std` builds, `rust-bitcoin` has had to have mutually-exclusive `std` and `no-std` builds. This breaks general assumptions about how features work in Rust, causing substantial pain for applications far downstream of `rust-bitcoin` crates. Here, we add a new `bitcoin_io` crate, making it an unconditional dependency and using its `io` module in the in-repository crates in place of `std::io` and `core2::io`. As it is not substantial additional code, the `hashes` io implementations are no longer feature-gated. This doesn't actually accomplish anything on its own, only adding the new crate which still depends on `core2`. --- Cargo-minimal.lock | 11 ++++++-- Cargo-recent.lock | 11 ++++++-- Cargo.toml | 5 +++- bitcoin/Cargo.toml | 8 +++--- bitcoin/embedded/Cargo.toml | 4 ++- bitcoin/src/lib.rs | 25 ++++++------------ hashes/Cargo.toml | 9 +++---- hashes/embedded/Cargo.toml | 5 +++- hashes/embedded/src/main.rs | 2 +- hashes/extended_tests/schemars/Cargo.toml | 3 +++ hashes/src/lib.rs | 12 +++------ io/Cargo.toml | 25 ++++++++++++++++++ io/src/lib.rs | 32 +++++++++++++++++++++++ 13 files changed, 110 insertions(+), 42 deletions(-) create mode 100644 io/Cargo.toml create mode 100644 io/src/lib.rs diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index 0b1392fd..9f7011eb 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -38,9 +38,9 @@ dependencies = [ "bech32", "bincode", "bitcoin-internals", + "bitcoin-io", "bitcoin_hashes", "bitcoinconsensus", - "core2", "hex-conservative", "hex_lit", "mutagen", @@ -69,11 +69,18 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin-io" +version = "0.1.0" +dependencies = [ + "core2", +] + [[package]] name = "bitcoin_hashes" version = "0.13.0" dependencies = [ - "core2", + "bitcoin-io", "hex-conservative", "schemars", "serde", diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 307e739e..932cd2b5 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -37,9 +37,9 @@ dependencies = [ "bech32", "bincode", "bitcoin-internals", + "bitcoin-io", "bitcoin_hashes", "bitcoinconsensus", - "core2", "hex-conservative", "hex_lit", "mutagen", @@ -68,11 +68,18 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin-io" +version = "0.1.0" +dependencies = [ + "core2", +] + [[package]] name = "bitcoin_hashes" version = "0.13.0" dependencies = [ - "core2", + "bitcoin-io", "hex-conservative", "schemars", "serde", diff --git a/Cargo.toml b/Cargo.toml index 643dc29c..42895c92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["bitcoin", "hashes", "internals", "fuzz"] +members = ["bitcoin", "hashes", "internals", "fuzz", "io"] [patch.crates-io.bitcoin] path = "bitcoin" @@ -9,3 +9,6 @@ path = "hashes" [patch.crates-io.bitcoin-internals] path = "internals" + +[patch.crates-io.bitcoin-io] +path = "io" diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index 1f1926a4..5556e3e4 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -26,8 +26,8 @@ bitcoinconsensus-std = ["bitcoinconsensus/std", "std"] # The no-std feature doesn't disable std - you need to turn off the std feature for that by disabling default. # Instead no-std enables additional features required for this crate to be usable without std. # As a result, both can be enabled without conflict. -std = ["secp256k1/std", "hashes/std", "bech32/std", "internals/std", "hex/std"] -no-std = ["core2", "hashes/alloc", "hashes/core2", "bech32/alloc", "secp256k1/alloc", "hex/alloc", "hex/core2"] +std = ["secp256k1/std", "bitcoin-io/std", "hashes/std", "bech32/std", "internals/std", "hex/std"] +no-std = ["hashes/alloc", "hashes/core2", "bitcoin-io/core2", "bitcoin-io/alloc", "bech32/alloc", "secp256k1/alloc", "hex/alloc", "hex/core2"] [package.metadata.docs.rs] all-features = true @@ -41,12 +41,12 @@ hashes = { package = "bitcoin_hashes", version = "0.13.0", default-features = fa secp256k1 = { version = "0.28.0", default-features = false, features = ["hashes"] } hex_lit = "0.1.1" +bitcoin-io = { version = "0.1", default-features = false } + base64 = { version = "0.21.3", optional = true } # Only use this feature for no-std builds, otherwise use bitcoinconsensus-std. bitcoinconsensus = { version = "0.20.2-0.5.0", default-features = false, optional = true } -# There is no reason to use this dependency directly, it is activated by the "no-std" feature. -core2 = { version = "0.3.2", default-features = false, features = ["alloc"], optional = true } # Do NOT use this as a feature! Use the `serde` feature instead. actual-serde = { package = "serde", version = "1.0.103", default-features = false, features = [ "derive", "alloc" ], optional = true } diff --git a/bitcoin/embedded/Cargo.toml b/bitcoin/embedded/Cargo.toml index f68d7f3f..9ea41c17 100644 --- a/bitcoin/embedded/Cargo.toml +++ b/bitcoin/embedded/Cargo.toml @@ -17,7 +17,6 @@ panic-halt = "0.2.0" alloc-cortex-m = "0.4.1" bitcoin = { path="../", default-features = false, features = ["no-std", "secp-lowmemory"] } - [[bin]] name = "embedded" test = false @@ -33,3 +32,6 @@ path = "../../hashes" [patch.crates-io.bitcoin-internals] path = "../../internals" + +[patch.crates-io.bitcoin-io] +path = "../../io" diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs index 68b7d246..03d516ed 100644 --- a/bitcoin/src/lib.rs +++ b/bitcoin/src/lib.rs @@ -23,7 +23,7 @@ //! deserialization. //! * `secp-lowmemory` - optimizations for low-memory devices. //! * `no-std` - enables additional features required for this crate to be usable -//! without std. Does **not** disable `std`. Depends on `core2`. +//! without std. Does **not** disable `std`. //! * `bitcoinconsensus-std` - enables `std` in `bitcoinconsensus` and communicates it //! to this crate so it knows how to implement //! `std::error::Error`. At this time there's a hack to @@ -68,9 +68,6 @@ pub extern crate bech32; /// Bitcoin's libbitcoinconsensus with Rust binding. pub extern crate bitcoinconsensus; -#[cfg(not(feature = "std"))] -extern crate core2; - /// Rust implementation of cryptographic hash function algorithems. pub extern crate hashes; @@ -118,16 +115,8 @@ pub mod taproot; // May depend on crate features and we don't want to bother with it #[allow(unused)] -#[cfg(feature = "std")] -use std::error::Error as StdError; -#[cfg(feature = "std")] -use std::io; - -#[allow(unused)] -#[cfg(not(feature = "std"))] -use core2::error::Error as StdError; -#[cfg(not(feature = "std"))] -use core2::io; +use bitcoin_io::error::Error as StdError; +use bitcoin_io::io; pub use crate::address::{Address, AddressType}; pub use crate::amount::{Amount, Denomination, SignedAmount}; @@ -163,6 +152,8 @@ pub use crate::taproot::{ #[cfg(not(feature = "std"))] mod io_extras { + use crate::io; + /// A writer which will move data into the void. pub struct Sink { _priv: (), @@ -171,12 +162,12 @@ mod io_extras { /// Creates an instance of a writer which will successfully consume all data. pub const fn sink() -> Sink { Sink { _priv: () } } - impl core2::io::Write for Sink { + impl io::Write for Sink { #[inline] - fn write(&mut self, buf: &[u8]) -> core2::io::Result { Ok(buf.len()) } + fn write(&mut self, buf: &[u8]) -> io::Result { Ok(buf.len()) } #[inline] - fn flush(&mut self) -> core2::io::Result<()> { Ok(()) } + fn flush(&mut self) -> io::Result<()> { Ok(()) } } } diff --git a/hashes/Cargo.toml b/hashes/Cargo.toml index 6f91a020..276fb3de 100644 --- a/hashes/Cargo.toml +++ b/hashes/Cargo.toml @@ -14,11 +14,11 @@ exclude = ["tests", "contrib"] [features] default = ["std"] -std = ["alloc", "hex/std"] +std = ["alloc", "hex/std", "bitcoin-io/std"] alloc = ["hex/alloc"] serde-std = ["serde/std"] # If you want I/O you must enable either "std" or "core2". -core2 = ["actual-core2", "hex/core2"] +core2 = ["bitcoin-io/core2", "hex/core2"] # Smaller (but slower) implementation of sha256, sha512 and ripemd160 small-hash = [] @@ -29,13 +29,12 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] hex = { package = "hex-conservative", version = "0.1.1", default-features = false } +bitcoin-io = { version = "0.1", default-features = false, optional = true } + schemars = { version = "0.8.3", optional = true } # Only enable this if you explicitly do not want to use "std", otherwise enable "serde-std". serde = { version = "1.0", default-features = false, optional = true } -# Do NOT use this feature! Use the "core2" feature instead. -actual-core2 = { package = "core2", version = "0.3.2", default-features = false, optional = true } - [dev-dependencies] serde_test = "1.0" serde_json = "1.0" diff --git a/hashes/embedded/Cargo.toml b/hashes/embedded/Cargo.toml index e44e0344..5bb9af26 100644 --- a/hashes/embedded/Cargo.toml +++ b/hashes/embedded/Cargo.toml @@ -19,7 +19,7 @@ cortex-m-semihosting = "0.3.3" panic-halt = "0.2.0" alloc-cortex-m = { version = "0.4.1", optional = true } bitcoin_hashes = { path="../", default-features = false, features = ["core2"] } -core2 = { version = "0.3.0", default_features = false } +bitcoin-io = { path = "../../io", default_features = false } [[bin]] name = "embedded" @@ -33,3 +33,6 @@ lto = true # better optimizations [patch.crates-io.bitcoin-internals] path = "../../internals" + +[patch.crates-io.bitcoin-io] +path = "../../io" diff --git a/hashes/embedded/src/main.rs b/hashes/embedded/src/main.rs index a43e7106..bba22ff8 100644 --- a/hashes/embedded/src/main.rs +++ b/hashes/embedded/src/main.rs @@ -10,7 +10,7 @@ extern crate bitcoin_hashes; #[cfg(feature = "alloc")] use alloc::string::ToString; use bitcoin_hashes::{sha256, Hash, HashEngine}; -use core2::io::Write; +use bitcoin_io::io::Write; use core::str::FromStr; use cortex_m_rt::entry; use cortex_m_semihosting::{debug, hprintln}; diff --git a/hashes/extended_tests/schemars/Cargo.toml b/hashes/extended_tests/schemars/Cargo.toml index 7c523c0f..8a8943d2 100644 --- a/hashes/extended_tests/schemars/Cargo.toml +++ b/hashes/extended_tests/schemars/Cargo.toml @@ -21,3 +21,6 @@ serde_json = "1.0" [patch.crates-io.bitcoin-internals] path = "../../../internals" + +[patch.crates-io.bitcoin-io] +path = "../../../io" diff --git a/hashes/src/lib.rs b/hashes/src/lib.rs index aa1c6ffc..91740f4e 100644 --- a/hashes/src/lib.rs +++ b/hashes/src/lib.rs @@ -81,8 +81,6 @@ // Exclude clippy lints we don't think are valuable #![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 -#[cfg(all(not(test), not(feature = "std"), feature = "core2"))] -extern crate actual_core2 as core2; #[cfg(all(feature = "alloc", not(feature = "std")))] extern crate alloc; #[cfg(any(test, feature = "std"))] @@ -119,7 +117,7 @@ pub mod serde_macros; pub mod cmp; pub mod hash160; pub mod hmac; -#[cfg(any(test, feature = "std", feature = "core2"))] +#[cfg(feature = "bitcoin-io")] mod impls; pub mod ripemd160; pub mod sha1; @@ -131,12 +129,10 @@ pub mod sha512_256; pub mod siphash24; use core::{borrow, fmt, hash, ops}; -// You get I/O if you enable "std" or "core2" (as well as during testing). -#[cfg(any(test, feature = "std"))] -use std::io; -#[cfg(all(not(test), not(feature = "std"), feature = "core2"))] -use core2::io; +// You get I/O if you enable "std" or "core2" (as well as during testing). +#[cfg(feature = "bitcoin-io")] +use bitcoin_io::io; pub use hmac::{Hmac, HmacEngine}; /// A hashing engine which bytes can be serialized into. diff --git a/io/Cargo.toml b/io/Cargo.toml new file mode 100644 index 00000000..ea5bc43a --- /dev/null +++ b/io/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "bitcoin-io" +version = "0.1.0" +authors = ["Matt Corallo "] +license = "CC0-1.0" +repository = "https://github.com/rust-bitcoin/rust-bitcoin" +documentation = "https://docs.rs/bitcoin-io/" +description = "Simple I/O traits for no-std (and std) environments" +categories = ["no-std"] +keywords = [ "io", "no-std" ] +readme = "README.md" +edition = "2018" +exclude = ["tests", "contrib"] + +[features] +default = ["std"] +std = [] +alloc = ["core2/alloc"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +core2 = { version = "0.3", default-features = false, optional = true } diff --git a/io/src/lib.rs b/io/src/lib.rs new file mode 100644 index 00000000..766dce62 --- /dev/null +++ b/io/src/lib.rs @@ -0,0 +1,32 @@ +//! Rust-Bitcoin IO Library +//! +//! Because the core `std::io` module is not yet exposed in `no-std` Rust, building `no-std` +//! applications which require reading and writing objects via standard traits is not generally +//! possible. While there is ongoing work to improve this situation, this module is not likely to +//! be available for applications with broad rustc version support for some time. +//! +//! Thus, this library exists to export a minmal version of `std::io`'s traits which `no-std` +//! applications may need. With the `std` feature, these traits are also implemented for the +//! `std::io` traits, allowing standard objects to be used wherever the traits from this crate are +//! required. +//! +//! This traits are not one-for-one drop-ins, but are as close as possible while still implementing +//! `std::io`'s traits without unnecessary complexity. + +// Experimental features we need. +#![cfg_attr(docsrs, feature(doc_auto_cfg))] + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(not(feature = "std"), not(feature = "core2")))] +compile_error!("At least one of std or core2 must be enabled"); + +#[cfg(feature = "std")] +pub use std::io; +#[cfg(feature = "std")] +pub use std::error; + +#[cfg(not(feature = "std"))] +pub use core2::io; +#[cfg(not(feature = "std"))] +pub use core2::error;