p2p: Implement `encoding` traits for `PrefilledTransaction`
What changed, and why it matters
This commit adds new encoding/decoding machinery for a Bitcoin P2P data structure called PrefilledTransaction. The main security-relevant detail is that it now explicitly rejects an invalid (too-large) transaction index instead of silently truncating or misinterpreting it. It is a defensive correctness improvement, not a fix for a known exploitable vulnerability.
Treat as a routine correctness/hardening change. Review whether callers handle the new `InvalidIndex` error appropriately and ensure test coverage exists for oversized indices. No urgent security response is indicated by the commit alone.
Security signals we found
New explicit validation of decoded compact-size index against u16 range
New error variant `InvalidIndex(usize)` surfaces malformed input instead of silently truncating
Adds `Encodable`/`Decodable` trait implementations for `PrefilledTransaction`
No removal of existing `consensus_encode` implementation; old path remains available
Evidence from the diff
The patch implements the new encoding::Encodable/Decodable traits for PrefilledTransaction in p2p/src/bip152.rs. It composes a compact-size decoder and a transaction decoder, then validates that the decoded compact-size index fits in a u16. If it does not, decoding returns a new PrefilledTransactionDecoderError::InvalidIndex error carrying the offending value. This replaces implicit behavior and makes the failure observable. No memory-safety bug, remote crash, or consensus-critical change is evident from the diff.
Changed components
p2p/src/bip152.rsPrefilledTransactionPrefilledTransactionDecoderPrefilledTransactionEncoderInspect captured patch +92 / −0
diff --git a/p2p/src/bip152.rs b/p2p/src/bip152.rs
index 4b991511..d592bc68 100644
--- a/p2p/src/bip152.rs
+++ b/p2p/src/bip152.rs
@@ -22,6 +22,7 @@ use internals::array::ArrayExt as _;
use internals::write_err;
use io::{BufRead, Write};
use primitives::block::{BlockHashDecoder, BlockHashEncoder};
+use primitives::transaction::{TransactionDecoder, TransactionEncoder};
/// A BIP-0152 error
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -78,6 +79,97 @@ impl convert::AsRef<Transaction> for PrefilledTransaction {
fn as_ref(&self) -> &Transaction { &self.tx }
}
+encoding::encoder_newtype! {
+ /// The encoder for a [`PrefilledTransaction`] message.
+ pub struct PrefilledTransactionEncoder<'e>(Encoder2<CompactSizeEncoder, TransactionEncoder<'e>>);
+}
+
+impl encoding::Encodable for PrefilledTransaction {
+ type Encoder<'e> =PrefilledTransactionEncoder<'e>
+ where
+ Self: 'e;
+
+ fn encoder(&self) -> Self::Encoder<'_> {
+ PrefilledTransactionEncoder::new(
+ Encoder2::new(CompactSizeEncoder::new(self.idx.into()), self.tx.encoder())
+ )
+ }
+}
+
+type PrefilledTransactionInnerDecoder = Decoder2<CompactSizeDecoder, TransactionDecoder>;
+
+/// The decoder for a [`PrefilledTransaction`] message.
+pub struct PrefilledTransactionDecoder(PrefilledTransactionInnerDecoder);
+
+impl PrefilledTransactionDecoder {
+ fn err_from_inner(inner: <PrefilledTransactionInnerDecoder as encoding::Decoder>::Error) -> PrefilledTransactionDecoderError {
+ PrefilledTransactionDecoderError::Decoder(inner)
+ }
+}
+
+impl encoding::Decoder for PrefilledTransactionDecoder {
+ type Output = PrefilledTransaction;
+ type Error = PrefilledTransactionDecoderError;
+
+ #[inline]
+ fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
+ self.0.push_bytes(bytes).map_err(Self::err_from_inner)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Output, Self::Error> {
+ let (cs, tx) = self.0.end().map_err(Self::err_from_inner)?;
+ let idx = u16::try_from(cs)
+ .map_err(|_| PrefilledTransactionDecoderError::InvalidIndex(cs))?;
+ Ok(PrefilledTransaction { idx, tx })
+ }
+
+ #[inline]
+ fn read_limit(&self) -> usize { self.0.read_limit() }
+}
+
+impl encoding::Decodable for PrefilledTransaction {
+ type Decoder = PrefilledTransactionDecoder;
+
+ fn decoder() -> Self::Decoder {
+ PrefilledTransactionDecoder(
+ Decoder2::new(CompactSizeDecoder::new(), TransactionDecoder::new())
+ )
+ }
+}
+
+/// An error occuring when decoding a [`PrefilledTransaction`].
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum PrefilledTransactionDecoderError {
+ /// Inner decoder error.
+ Decoder(<PrefilledTransactionInnerDecoder as encoding::Decoder>::Error),
+ /// The differential encoding may be no more than 16 bits.
+ InvalidIndex(usize),
+}
+
+impl From<Infallible> for PrefilledTransactionDecoderError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+impl fmt::Display for PrefilledTransactionDecoderError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Decoder(d) => write_err!(f, "prefilled transaction error"; d),
+ Self::InvalidIndex(idx) => write!(f, "index overflowed u16 {}", idx),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for PrefilledTransactionDecoderError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::Decoder(d) => Some(d),
+ Self::InvalidIndex(_idx) => None,
+ }
+ }
+}
+
impl Encodable for PrefilledTransaction {
#[inline]
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
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.