What changed, and why it matters
This commit is a routine API cleanup: it changes custom I/O traits in the rust-bitcoin project so they can be used with Rust's dynamic-dispatch feature ('dyn'). The code changes are purely about making the traits more flexible for downstream users. There is no indication in the commit or title that this fixes a security vulnerability, and the diff does not change any security-critical behavior such as bounds checking, parsing limits, or cryptographic validation.
No security action required. Treat as a normal API/ergonomics improvement. Reviewers may optionally verify that the new dyn-compatible API does not inadvertently relax any caller-side constraints, but the diff shows the existing MAX_VEC_SIZE and 1024-byte limits remain unchanged.
Security signals we found
No security-relevant keywords in commit title or message
No changes to length limits, bounds checks, or parsing constraints
No changes to cryptographic operations or memory safety checks
Diff is purely trait-object compatibility and API ergonomics
Evidence from the diff
The patch makes the project’s custom io::Read, io::BufRead, and io::Write traits dyn-compatible (object-safe). Key changes: (1) Read::take and BufRead::read_to_limit are now called via explicit trait syntax (io::Read::take(r, …)) so the receiver can be a trait object; (2) Read::take’s signature changed from &mut self returning Take<’_, Self> to self returning Take
Changed components
io/src/lib.rsbitcoin/src/blockdata/block.rsbitcoin/src/internal_macros.rsbitcoin/src/psbt/raw.rsp2p/src/consensus.rsio/tests/api.rsInspect captured patch +32 / −14
diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs
index 8507c9a4..201853fc 100644
--- a/bitcoin/src/blockdata/block.rs
+++ b/bitcoin/src/blockdata/block.rs
@@ -418,7 +418,7 @@ impl Decodable for Block<Unchecked> {
#[inline]
fn consensus_decode<R: io::BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
- let mut r = r.take(internals::ToU64::to_u64(encode::MAX_VEC_SIZE));
+ let mut r = io::Read::take(r, internals::ToU64::to_u64(encode::MAX_VEC_SIZE));
let header = Decodable::consensus_decode(&mut r)?;
let transactions = Decodable::consensus_decode(&mut r)?;
diff --git a/bitcoin/src/internal_macros.rs b/bitcoin/src/internal_macros.rs
index 82b10e72..b7a469d2 100644
--- a/bitcoin/src/internal_macros.rs
+++ b/bitcoin/src/internal_macros.rs
@@ -33,7 +33,7 @@ macro_rules! impl_consensus_encoding {
fn consensus_decode<R: $crate::io::BufRead + ?Sized>(
r: &mut R,
) -> core::result::Result<$thing, $crate::consensus::encode::Error> {
- let mut r = r.take(internals::ToU64::to_u64($crate::consensus::encode::MAX_VEC_SIZE));
+ let mut r = $crate::io::Read::take(r, internals::ToU64::to_u64($crate::consensus::encode::MAX_VEC_SIZE));
Ok($thing {
$($field: $crate::consensus::Decodable::consensus_decode(&mut r)?),+
})
diff --git a/bitcoin/src/psbt/raw.rs b/bitcoin/src/psbt/raw.rs
index 33f6a1c5..91d50590 100644
--- a/bitcoin/src/psbt/raw.rs
+++ b/bitcoin/src/psbt/raw.rs
@@ -157,7 +157,7 @@ where
// The limit is a DOS protection mechanism the exact value is not
// important, 1024 bytes is bigger than any key should be.
let mut key = vec![];
- let _ = r.read_to_limit(&mut key, 1024)?;
+ let _ = io::Read::read_to_limit(r, &mut key, 1024)?;
Ok(Self { prefix, subtype, key })
}
diff --git a/io/src/lib.rs b/io/src/lib.rs
index 2bfe2bde..40952362 100644
--- a/io/src/lib.rs
+++ b/io/src/lib.rs
@@ -84,7 +84,9 @@ pub trait Read {
/// Constructs a new adapter which will read at most `limit` bytes.
#[inline]
- fn take(&mut self, limit: u64) -> Take<'_, Self> { Take { reader: self, remaining: limit } }
+ fn take(self, limit: u64) -> Take<Self>
+ where Self: Sized,
+ { Take { reader: self, remaining: limit } }
/// Attempts to read up to limit bytes from the reader, allocating space in `buf` as needed.
///
@@ -125,12 +127,12 @@ pub trait BufRead: Read {
///
/// Created by calling `[Read::take]`.
#[derive(Debug)]
-pub struct Take<'a, R: Read + ?Sized> {
- reader: &'a mut R,
+pub struct Take<R> {
+ reader: R,
remaining: u64,
}
-impl<R: Read + ?Sized> Take<'_, R> {
+impl<R: Read> Take<R> {
/// Reads all bytes until EOF from the underlying reader into `buf`.
///
/// Allocates space in `buf` as needed.
@@ -158,7 +160,7 @@ impl<R: Read + ?Sized> Take<'_, R> {
}
}
-impl<R: Read + ?Sized> Read for Take<'_, R> {
+impl<R: Read> Read for Take<R> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
let len = cmp::min(buf.len(), self.remaining.try_into().unwrap_or(buf.len()));
@@ -169,7 +171,7 @@ impl<R: Read + ?Sized> Read for Take<'_, R> {
}
// Impl copied from Rust stdlib.
-impl<R: BufRead + ?Sized> BufRead for Take<'_, R> {
+impl<R: BufRead> BufRead for Take<R> {
#[inline]
fn fill_buf(&mut self) -> Result<&[u8]> {
// Don't call into inner reader at all at EOF because it may still block
@@ -192,7 +194,7 @@ impl<R: BufRead + ?Sized> BufRead for Take<'_, R> {
}
}
-impl<T: Read> Read for &'_ mut T {
+impl<T: Read + ?Sized> Read for &'_ mut T {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> Result<usize> { (**self).read(buf) }
@@ -200,7 +202,7 @@ impl<T: Read> Read for &'_ mut T {
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { (**self).read_exact(buf) }
}
-impl<T: BufRead> BufRead for &'_ mut T {
+impl<T: BufRead + ?Sized> BufRead for &'_ mut T {
#[inline]
fn fill_buf(&mut self) -> Result<&[u8]> { (**self).fill_buf() }
diff --git a/io/tests/api.rs b/io/tests/api.rs
index f17d165d..28be86a1 100644
--- a/io/tests/api.rs
+++ b/io/tests/api.rs
@@ -54,8 +54,8 @@ impl Structs {
}
#[derive(Debug)] // `Take` implements Debug (C-DEBUG).
-struct Taker<'a> {
- a: Take<'a, Dummy>,
+struct Taker<Dummy> {
+ a: Take<Dummy>,
}
/// An arbitrary `Dummy` instance.
@@ -142,3 +142,19 @@ fn all_non_error_types_implement_send_sync() {
assert_send::<Errors>();
assert_sync::<Errors>();
}
+
+#[test]
+fn dyn_compatible() {
+ // Sanity check, these are all dyn compatible.
+ struct StdlibTraits {
+ p: Box<dyn std::io::Read>,
+ q: Box<dyn std::io::Write>,
+ r: Box<dyn std::io::BufRead>,
+ }
+ // If this builds then our three traits are dyn compatible also.
+ struct OurTraits {
+ p: Box<dyn Read>,
+ q: Box<dyn Write>,
+ r: Box<dyn BufRead>,
+ }
+}
diff --git a/p2p/src/consensus.rs b/p2p/src/consensus.rs
index b84a4ebd..5bc377d7 100644
--- a/p2p/src/consensus.rs
+++ b/p2p/src/consensus.rs
@@ -46,7 +46,7 @@ macro_rules! impl_consensus_encoding {
fn consensus_decode<R: io::BufRead + ?Sized>(
r: &mut R,
) -> core::result::Result<$thing, bitcoin::consensus::encode::Error> {
- let mut r = r.take(internals::ToU64::to_u64(bitcoin::consensus::encode::MAX_VEC_SIZE));
+ let mut r = io::Read::take(r, internals::ToU64::to_u64(bitcoin::consensus::encode::MAX_VEC_SIZE));
Ok($thing {
$($field: bitcoin::consensus::Decodable::consensus_decode(&mut r)?),+
})
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.