What changed, and why it matters
This commit is a straightforward internal code reorganization. It moves a small helper type called CheckedData—used to bundle data with a 4-byte Bitcoin P2P checksum—from the main bitcoin crate into the p2p crate. The logic, behavior, and tests are copied almost unchanged. There is no security fix or vulnerability being patched.
No security action needed. Treat as a normal refactoring/reorganization change. Reviewers may verify that the moved tests still pass and that downstream users importing CheckedData from the old location are updated.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change relocates the CheckedData struct, its Encodable/Decodable trait implementations, the sha2_checksum helper, and the read_bytes_from_finite_reader helper from bitcoin/src/consensus/encode.rs to p2p/src/message.rs. It also re-exports CheckedData from p2p/src/lib.rs. The implementation is functionally identical: double-SHA256 first four bytes as checksum, u32 length prefix, checksum verification on decode, and chunked bounded reading. The move is motivated by crate-dependency hygiene ahead of splitting consensus encoding traits into a new crate, not by any security issue.
Changed components
bitcoin/src/consensus/encode.rsp2p/src/message.rsp2p/src/lib.rsInspect captured patch +108 / −77
diff --git a/bitcoin/src/consensus/encode.rs b/bitcoin/src/consensus/encode.rs
index c3bf6ba7..20eff670 100644
--- a/bitcoin/src/consensus/encode.rs
+++ b/bitcoin/src/consensus/encode.rs
@@ -319,30 +319,6 @@ pub trait Decodable: Sized {
}
}
-/// Data and a 4-byte checksum.
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub struct CheckedData {
- data: Vec<u8>,
- checksum: [u8; 4],
-}
-
-impl CheckedData {
- /// Constructs a new `CheckedData` computing the checksum of given data.
- pub fn new(data: Vec<u8>) -> Self {
- let checksum = sha2_checksum(&data);
- Self { data, checksum }
- }
-
- /// Returns a reference to the raw data without the checksum.
- pub fn data(&self) -> &[u8] { &self.data }
-
- /// Returns the raw data without the checksum.
- pub fn into_data(self) -> Vec<u8> { self.data }
-
- /// Returns the checksum of the data.
- pub fn checksum(&self) -> [u8; 4] { self.checksum }
-}
-
// Primitive types
macro_rules! impl_int_encodable {
($ty:ident, $meth_dec:ident, $meth_enc:ident) => {
@@ -615,42 +591,6 @@ impl Decodable for Box<[u8]> {
}
}
-/// Does a double-SHA256 on `data` and returns the first 4 bytes.
-fn sha2_checksum(data: &[u8]) -> [u8; 4] {
- let checksum = sha256d::hash(data);
- let checksum = checksum.to_byte_array();
- [checksum[0], checksum[1], checksum[2], checksum[3]]
-}
-
-impl Encodable for CheckedData {
- #[inline]
- fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
- u32::try_from(self.data.len())
- .expect("network message use u32 as length")
- .consensus_encode(w)?;
- self.checksum().consensus_encode(w)?;
- Ok(8 + w.emit_slice(&self.data)?)
- }
-}
-
-impl Decodable for CheckedData {
- #[inline]
- fn consensus_decode_from_finite_reader<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, Error> {
- let len = u32::consensus_decode_from_finite_reader(r)? as usize;
-
- let checksum = <[u8; 4]>::consensus_decode_from_finite_reader(r)?;
- let opts = ReadBytesFromFiniteReaderOpts { len, chunk_size: MAX_VEC_SIZE };
- let data = read_bytes_from_finite_reader(r, opts)?;
- let expected_checksum = sha2_checksum(&data);
- if expected_checksum != checksum {
- Err(ParseError::InvalidChecksum { expected: expected_checksum, actual: checksum }
- .into())
- } else {
- Ok(CheckedData { data, checksum })
- }
- }
-}
-
impl<T: Encodable> Encodable for &'_ T {
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
(**self).consensus_encode(w)
@@ -910,12 +850,6 @@ mod tests {
assert!(deserialize::<Vec<u8>>(&vec_253).is_ok());
}
- #[test]
- fn serialize_checkeddata() {
- let cd = CheckedData::new(vec![1u8, 2, 3, 4, 5]);
- assert_eq!(serialize(&cd), [5, 0, 0, 0, 162, 107, 175, 90, 1, 2, 3, 4, 5]);
- }
-
#[test]
fn serialize_vector() {
assert_eq!(serialize(&vec![1u8, 2, 3]), [3u8, 1, 2, 3]);
@@ -1060,13 +994,6 @@ mod tests {
);
}
- #[test]
- fn deserialize_checkeddata() {
- let cd: Result<CheckedData, _> =
- deserialize(&[5u8, 0, 0, 0, 162, 107, 175, 90, 1, 2, 3, 4, 5]);
- assert_eq!(cd.ok(), Some(CheckedData::new(vec![1u8, 2, 3, 4, 5])));
- }
-
#[test]
fn limit_read() {
let witness = vec![vec![0u8; 3_999_999]; 2];
diff --git a/p2p/src/lib.rs b/p2p/src/lib.rs
index 07425a35..be6b3e1f 100644
--- a/p2p/src/lib.rs
+++ b/p2p/src/lib.rs
@@ -52,7 +52,7 @@ pub use self::network_ext::NetworkExt;
#[cfg(feature = "std")]
#[rustfmt::skip]
#[doc(inline)]
-pub use self::address::Address;
+pub use self::{address::Address, message::CheckedData};
/// Version of the protocol as appearing in network version handshakes and some message headers.
///
diff --git a/p2p/src/message.rs b/p2p/src/message.rs
index c8bb508c..14dde067 100644
--- a/p2p/src/message.rs
+++ b/p2p/src/message.rs
@@ -9,14 +9,15 @@ use alloc::borrow::{Cow, ToOwned};
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
-use core::fmt;
+use alloc::vec;
+use core::{cmp, fmt};
-use bitcoin::consensus::encode::{self, CheckedData, Decodable, Encodable, ReadExt, WriteExt};
+use bitcoin::consensus::encode::{self, Decodable, Encodable, ReadExt, WriteExt};
use bitcoin::merkle_tree::MerkleBlock;
use bitcoin::{block, transaction};
use hashes::sha256d;
use internals::ToU64 as _;
-use io::{BufRead, Write};
+use io::{self, BufRead, Read, Write};
use units::FeeRate;
use crate::address::{AddrV2Message, Address};
@@ -744,6 +745,96 @@ impl Decodable for V2NetworkMessage {
}
}
+/// Data and a 4-byte checksum.
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub struct CheckedData {
+ data: Vec<u8>,
+ checksum: [u8; 4],
+}
+
+impl CheckedData {
+ /// Constructs a new `CheckedData` computing the checksum of given data.
+ pub fn new(data: Vec<u8>) -> Self {
+ let checksum = sha2_checksum(&data);
+ Self { data, checksum }
+ }
+
+ /// Returns a reference to the raw data without the checksum.
+ pub fn data(&self) -> &[u8] { &self.data }
+
+ /// Returns the raw data without the checksum.
+ pub fn into_data(self) -> Vec<u8> { self.data }
+
+ /// Returns the checksum of the data.
+ pub fn checksum(&self) -> [u8; 4] { self.checksum }
+}
+
+impl Encodable for CheckedData {
+ #[inline]
+ fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
+ u32::try_from(self.data.len())
+ .expect("network message use u32 as length")
+ .consensus_encode(w)?;
+ self.checksum().consensus_encode(w)?;
+ Ok(8 + w.emit_slice(&self.data)?)
+ }
+}
+
+impl Decodable for CheckedData {
+ #[inline]
+ fn consensus_decode_from_finite_reader<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
+ let len = u32::consensus_decode_from_finite_reader(r)? as usize;
+
+ let checksum = <[u8; 4]>::consensus_decode_from_finite_reader(r)?;
+ let opts = ReadBytesFromFiniteReaderOpts { len, chunk_size: encode::MAX_VEC_SIZE };
+ let data = read_bytes_from_finite_reader(r, opts)?;
+ let expected_checksum = sha2_checksum(&data);
+ if expected_checksum != checksum {
+ Err(encode::ParseError::InvalidChecksum { expected: expected_checksum, actual: checksum }
+ .into())
+ } else {
+ Ok(CheckedData { data, checksum })
+ }
+ }
+}
+
+struct ReadBytesFromFiniteReaderOpts {
+ len: usize,
+ chunk_size: usize,
+}
+
+/// Read `opts.len` bytes from reader, where `opts.len` could potentially be malicious.
+///
+/// This function relies on reader being bound in amount of data
+/// it returns for OOM protection. See [`Decodable::consensus_decode_from_finite_reader`].
+#[inline]
+fn read_bytes_from_finite_reader<D: Read + ?Sized>(
+ d: &mut D,
+ mut opts: ReadBytesFromFiniteReaderOpts,
+) -> Result<Vec<u8>, encode::Error> {
+ let mut ret = vec![];
+
+ assert_ne!(opts.chunk_size, 0);
+
+ while opts.len > 0 {
+ let chunk_start = ret.len();
+ let chunk_size = cmp::min(opts.len, opts.chunk_size);
+ let chunk_end = chunk_start + chunk_size;
+ ret.resize(chunk_end, 0u8);
+ d.read_slice(&mut ret[chunk_start..chunk_end])?;
+ opts.len -= chunk_size;
+ }
+
+ Ok(ret)
+}
+
+/// Does a double-SHA256 on `data` and returns the first 4 bytes.
+fn sha2_checksum(data: &[u8]) -> [u8; 4] {
+ let checksum = sha256d::hash(data);
+ let checksum = checksum.to_byte_array();
+ [checksum[0], checksum[1], checksum[2], checksum[3]]
+}
+
#[cfg(test)]
mod test {
use alloc::string::ToString;
@@ -1185,4 +1276,17 @@ mod test {
panic!("wrong message type");
}
}
+
+ #[test]
+ fn serialize_checkeddata() {
+ let cd = CheckedData::new(vec![1u8, 2, 3, 4, 5]);
+ assert_eq!(serialize(&cd), [5, 0, 0, 0, 162, 107, 175, 90, 1, 2, 3, 4, 5]);
+ }
+
+ #[test]
+ fn deserialize_checkeddata() {
+ let cd: Result<CheckedData, _> =
+ deserialize(&[5u8, 0, 0, 0, 162, 107, 175, 90, 1, 2, 3, 4, 5]);
+ assert_eq!(cd.ok(), Some(CheckedData::new(vec![1u8, 2, 3, 4, 5])));
+ }
}
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.