p2p: Add `HeadersMessage` sanity check methods
What changed, and why it matters
This commit adds two optional helper methods to check whether a list of Bitcoin block headers is correctly chained and whether each header meets its own proof-of-work target. It is a defensive, non-breaking feature addition for users of the library; it does not fix an existing vulnerability or change any default behavior.
No immediate action required. Users of `rust-bitcoin` p2p who process `HeadersMessage` may consider calling the new `is_connected()` and `all_targets_satisfied()` methods after receipt to reject obviously invalid header sequences.
Security signals we found
Defensive sanity-check helpers added for P2P `HeadersMessage`
Proof-of-work target validation exposed as an explicit method
Header chain linkage check exposed as an explicit method
No automatic enforcement on decode; callers must opt in
Evidence from the diff
The patch introduces HeadersMessage::is_connected() and HeadersMessage::all_targets_satisfied() in p2p/src/message.rs. is_connected() verifies that each header’s block_hash() equals the next header’s prev_blockhash. all_targets_satisfied() calls HeaderExt::validate_pow(header.target()) for every header. A unit test with three real mainnet headers (heights 900000-900002) is added. No existing code paths are modified, and the methods are not invoked automatically during decoding.
Changed components
p2p/src/message.rsHeadersMessage structInspect captured patch +38 / −1
diff --git a/p2p/src/message.rs b/p2p/src/message.rs
index b5506ae9..b28cf903 100644
--- a/p2p/src/message.rs
+++ b/p2p/src/message.rs
@@ -14,7 +14,7 @@ use core::{cmp, fmt};
use bitcoin::consensus::encode::{self, Decodable, Encodable, ReadExt, WriteExt};
use bitcoin::merkle_tree::MerkleBlock;
-use bitcoin::{block, transaction};
+use bitcoin::{block, block::HeaderExt, transaction};
use hashes::sha256d;
use internals::ToU64 as _;
use io::{self, BufRead, Read, Write};
@@ -527,6 +527,27 @@ impl Encodable for V2NetworkMessage {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HeadersMessage(pub Vec<block::Header>);
+impl HeadersMessage {
+ /// Does each header point to the previous block hash in the list.
+ pub fn is_connected(&self) -> bool {
+ self.0
+ .iter()
+ .zip(self.0.iter().skip(1))
+ .all(|(first, second)| first.block_hash().eq(&second.prev_blockhash))
+ }
+
+ /// Each header passes its own proof-of-work target.
+ pub fn all_targets_satisfied(&self) -> bool {
+ !self.0
+ .iter()
+ .any(|header| {
+ let target = header.target();
+ let valid_pow = header.validate_pow(target);
+ valid_pow.is_err()
+ })
+ }
+}
+
impl Decodable for HeadersMessage {
#[inline]
fn consensus_decode_from_finite_reader<R: BufRead + ?Sized>(
@@ -1289,4 +1310,20 @@ mod test {
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 headers_message() {
+ let block_900_000 = deserialize::<block::Header>(
+ &hex!("00a0ab20247d4d9f582f9750344cdf62c46d81d046be960340960100000000000000000070f96945530651135839d8adc3f40e595118ec74c7ad81a3d17bb022e554fb0c937f4268743702177ad05f92")
+ ).unwrap();
+ let block_900_001 = deserialize::<block::Header>(
+ &hex!("00e000208a96960d6d1ca4ee4a283fd83da309b8d5d2bfed380501000000000000000000371c9ffd63d75fb36c57d58eb842d23c0e7ec049daf16d94cc38805c346e9d52e880426874370217973dc83b")
+ ).unwrap();
+ let block_900_002 = deserialize::<block::Header>(
+ &hex!("0400ff3ffc834fac4e1eb2ae41f1f9776e0f8e24a6090603ffa8010000000000000000002efba7e7280aa60f0a650f29e30332d52e11af57bc58cc6e71f343851f016c676182426874370217e3615653")
+ ).unwrap();
+ let headers_message = HeadersMessage(vec![block_900_000, block_900_001, block_900_002]);
+ assert!(headers_message.is_connected());
+ assert!(headers_message.all_targets_satisfied());
+ }
}
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.