Skip to main content

blvm_protocol/wire/
mod.rs

1//! Bitcoin P2P wire format serialization
2//!
3//! Implements Bitcoin P2P protocol message framing:
4//! Format: [magic:4][command:12][length:4][checksum:4][payload:var]
5//!
6//! - Magic bytes: Network identifier (mainnet, testnet, etc.)
7//! - Command: 12-byte ASCII string (null-padded)
8//! - Length: Payload length in bytes (little-endian u32)
9//! - Checksum: First 4 bytes of double SHA256 of payload
10//! - Payload: Message-specific data
11
12use crate::ConsensusError;
13use crate::Result;
14use crate::error::ProtocolError;
15use crate::network::NetworkMessage;
16use std::borrow::Cow;
17use std::io::Read;
18use std::sync::Arc;
19
20mod frame_header;
21pub use frame_header::{MAX_MESSAGE_PAYLOAD, MESSAGE_HEADER_SIZE, calculate_checksum};
22
23/// Serialize a network message to Bitcoin P2P wire format
24pub fn serialize_message(message: &NetworkMessage, magic_bytes: [u8; 4]) -> Result<Vec<u8>> {
25    use crate::network::*;
26
27    // Serialize payload based on message type
28    let (command, payload) = match message {
29        NetworkMessage::Version(v) => ("version", serialize_version(v)?),
30        NetworkMessage::VerAck => ("verack", vec![]),
31        NetworkMessage::Addr(a) => ("addr", serialize_addr(a)?),
32        NetworkMessage::AddrV2(a) => ("addrv2", serialize_addrv2(a)?),
33        NetworkMessage::Inv(i) => ("inv", serialize_inv(i)?),
34        NetworkMessage::GetData(g) => ("getdata", serialize_getdata(g)?),
35        NetworkMessage::GetHeaders(gh) => ("getheaders", serialize_getheaders(gh)?),
36        NetworkMessage::Headers(h) => ("headers", serialize_headers(h)?),
37        NetworkMessage::Block(b, witnesses) => ("block", serialize_block_witnesses(b, witnesses)?),
38        NetworkMessage::Tx(tx, witnesses) => ("tx", serialize_tx_with_witness(tx, witnesses)?),
39        NetworkMessage::Ping(p) => ("ping", serialize_ping(p)?),
40        NetworkMessage::Pong(p) => ("pong", serialize_pong(p)?),
41        NetworkMessage::MemPool => ("mempool", vec![]),
42        NetworkMessage::FeeFilter(f) => ("feefilter", serialize_feefilter(f)?),
43        NetworkMessage::GetBlocks(gb) => ("getblocks", serialize_getblocks(gb)?),
44        NetworkMessage::GetAddr => ("getaddr", vec![]),
45        NetworkMessage::NotFound(nf) => ("notfound", serialize_notfound(nf)?),
46        NetworkMessage::Reject(r) => ("reject", serialize_reject(r)?),
47        NetworkMessage::SendHeaders => ("sendheaders", vec![]),
48        NetworkMessage::SendCmpct(sc) => ("sendcmpct", serialize_sendcmpct(sc)?),
49        NetworkMessage::CmpctBlock(cb) => ("cmpctblock", serialize_cmpctblock(cb)?),
50        NetworkMessage::GetBlockTxn(gbt) => ("getblocktxn", serialize_getblocktxn(gbt)?),
51        NetworkMessage::BlockTxn(bt) => ("blocktxn", serialize_blocktxn(bt)?),
52        #[cfg(feature = "utxo-commitments")]
53        NetworkMessage::GetUTXOSet(gus) => ("getutxoset", serialize_getutxoset(gus)?),
54        #[cfg(feature = "utxo-commitments")]
55        NetworkMessage::UTXOSet(us) => ("utxoset", serialize_utxoset(us)?),
56        #[cfg(feature = "utxo-commitments")]
57        NetworkMessage::GetFilteredBlock(gfb) => {
58            ("getfilteredblock", serialize_getfilteredblock(gfb)?)
59        }
60        #[cfg(feature = "utxo-commitments")]
61        NetworkMessage::FilteredBlock(fb) => ("filteredblock", serialize_filteredblock(fb)?),
62        NetworkMessage::GetBanList(gbl) => ("getbanlist", serialize_getbanlist(gbl)?),
63        NetworkMessage::BanList(bl) => ("banlist", serialize_banlist(bl)?),
64    };
65
66    // Validate payload size
67    if payload.len() > MAX_MESSAGE_PAYLOAD {
68        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
69            Cow::Owned(format!(
70                "Message payload too large: {} bytes",
71                payload.len()
72            )),
73        )));
74    }
75
76    // Calculate checksum
77    let checksum = calculate_checksum(&payload);
78
79    // Build message
80    let mut message_bytes = Vec::with_capacity(MESSAGE_HEADER_SIZE + payload.len());
81
82    // Magic bytes
83    message_bytes.extend_from_slice(&magic_bytes);
84
85    // Command (12 bytes, null-padded)
86    let mut command_bytes = [0u8; 12];
87    let cmd_len = command.len().min(12);
88    command_bytes[..cmd_len].copy_from_slice(&command.as_bytes()[..cmd_len]);
89    message_bytes.extend_from_slice(&command_bytes);
90
91    // Payload length (little-endian u32)
92    message_bytes.extend_from_slice(&(payload.len() as u32).to_le_bytes());
93
94    // Checksum
95    message_bytes.extend_from_slice(&checksum);
96
97    // Payload
98    message_bytes.extend_from_slice(&payload);
99
100    Ok(message_bytes)
101}
102
103/// Deserialize Bitcoin P2P wire format to network message
104pub fn deserialize_message<R: Read>(
105    reader: &mut R,
106    expected_magic: [u8; 4],
107) -> Result<(NetworkMessage, usize)> {
108    use crate::network::*;
109
110    // Read header
111    let mut header = [0u8; MESSAGE_HEADER_SIZE];
112    reader.read_exact(&mut header).map_err(|e| {
113        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
114            "IO error: {e}"
115        ))))
116    })?;
117
118    // Check magic bytes
119    let magic = [header[0], header[1], header[2], header[3]];
120    if magic != expected_magic {
121        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
122            Cow::Owned(format!(
123                "Invalid magic bytes: {magic:?}, expected {expected_magic:?}"
124            )),
125        )));
126    }
127
128    // Read command (12 bytes, null-terminated)
129    let command_bytes = &header[4..16];
130    let command_len = command_bytes.iter().position(|&b| b == 0).unwrap_or(12);
131    if command_bytes[command_len..].iter().any(|&b| b != 0) {
132        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
133            Cow::Owned(format!("Non-zero command padding in {command_bytes:?}")),
134        )));
135    }
136    let command = std::str::from_utf8(&command_bytes[..command_len]).map_err(|e| {
137        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
138            "Invalid command: {e}"
139        ))))
140    })?;
141
142    // Read payload length
143    let length_bytes = [header[16], header[17], header[18], header[19]];
144    let payload_length = u32::from_le_bytes(length_bytes) as usize;
145
146    if payload_length > MAX_MESSAGE_PAYLOAD {
147        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
148            Cow::Owned(format!("Payload length too large: {payload_length} bytes")),
149        )));
150    }
151
152    // Read checksum
153    let checksum = [header[20], header[21], header[22], header[23]];
154
155    // Read payload
156    let mut payload = vec![0u8; payload_length];
157    if payload_length > 0 {
158        reader.read_exact(&mut payload).map_err(|e| {
159            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
160                "IO error: {e}"
161            ))))
162        })?;
163    }
164
165    // Verify checksum
166    let calculated_checksum = calculate_checksum(&payload);
167    if calculated_checksum != checksum {
168        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
169            Cow::Owned("Checksum mismatch".to_string()),
170        )));
171    }
172
173    // Deserialize message based on command
174    let message = match command {
175        "version" => NetworkMessage::Version(deserialize_version(&payload)?),
176        "verack" => NetworkMessage::VerAck,
177        "addr" => NetworkMessage::Addr(deserialize_addr(&payload)?),
178        "addrv2" => NetworkMessage::AddrV2(deserialize_addrv2(&payload)?),
179        "inv" => NetworkMessage::Inv(deserialize_inv(&payload)?),
180        "getdata" => NetworkMessage::GetData(deserialize_getdata(&payload)?),
181        "getheaders" => NetworkMessage::GetHeaders(deserialize_getheaders(&payload)?),
182        "headers" => NetworkMessage::Headers(deserialize_headers(&payload)?),
183        "block" => {
184            let (block, witnesses) = deserialize_block_with_witnesses_pair(&payload)?;
185            NetworkMessage::Block(Arc::new(block), witnesses)
186        }
187        "tx" => {
188            let (tx, witnesses) = deserialize_tx_with_witness(&payload)?;
189            NetworkMessage::Tx(Arc::new(tx), witnesses)
190        }
191        "ping" => NetworkMessage::Ping(deserialize_ping(&payload)?),
192        "pong" => NetworkMessage::Pong(deserialize_pong(&payload)?),
193        "mempool" => NetworkMessage::MemPool,
194        "feefilter" => NetworkMessage::FeeFilter(deserialize_feefilter(&payload)?),
195        "getblocks" => NetworkMessage::GetBlocks(deserialize_getblocks(&payload)?),
196        "getaddr" => NetworkMessage::GetAddr,
197        "notfound" => NetworkMessage::NotFound(deserialize_notfound(&payload)?),
198        "reject" => NetworkMessage::Reject(deserialize_reject(&payload)?),
199        "sendheaders" => NetworkMessage::SendHeaders,
200        "sendcmpct" => NetworkMessage::SendCmpct(deserialize_sendcmpct(&payload)?),
201        "cmpctblock" => NetworkMessage::CmpctBlock(deserialize_cmpctblock(&payload)?),
202        "getblocktxn" => NetworkMessage::GetBlockTxn(deserialize_getblocktxn(&payload)?),
203        "blocktxn" => NetworkMessage::BlockTxn(deserialize_blocktxn(&payload)?),
204        #[cfg(feature = "utxo-commitments")]
205        "getutxoset" => NetworkMessage::GetUTXOSet(deserialize_getutxoset(&payload)?),
206        #[cfg(feature = "utxo-commitments")]
207        "utxoset" => NetworkMessage::UTXOSet(deserialize_utxoset(&payload)?),
208        #[cfg(feature = "utxo-commitments")]
209        "getfilteredblock" => {
210            NetworkMessage::GetFilteredBlock(deserialize_getfilteredblock(&payload)?)
211        }
212        #[cfg(feature = "utxo-commitments")]
213        "filteredblock" => NetworkMessage::FilteredBlock(deserialize_filteredblock(&payload)?),
214        "getbanlist" => NetworkMessage::GetBanList(deserialize_getbanlist(&payload)?),
215        "banlist" => NetworkMessage::BanList(deserialize_banlist(&payload)?),
216        _ => {
217            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
218                Cow::Owned(format!("Unknown command: {command}")),
219            )));
220        }
221    };
222
223    Ok((message, MESSAGE_HEADER_SIZE + payload_length))
224}
225
226// Serialization helpers - Bitcoin P2P wire format encoding
227
228/// Serialize NetworkAddress to Bitcoin wire format (26 bytes)
229/// Format: services (8 bytes LE) + ip (16 bytes) + port (2 bytes BE)
230fn serialize_network_address(addr: &crate::network::NetworkAddress) -> Vec<u8> {
231    let mut buf = Vec::with_capacity(26);
232    // services: u64 little-endian
233    buf.extend_from_slice(&addr.services.to_le_bytes());
234    // ip: [u8; 16] (IPv6 format, IPv4 is 0x0000...0000FFFF + IPv4 bytes)
235    buf.extend_from_slice(&addr.ip);
236    // port: u16 big-endian (network byte order)
237    buf.extend_from_slice(&addr.port.to_be_bytes());
238    buf
239}
240
241/// Deserialize NetworkAddress from Bitcoin wire format (26 bytes)
242fn deserialize_network_address(data: &[u8]) -> Result<crate::network::NetworkAddress> {
243    if data.len() < 26 {
244        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
245            Cow::Owned("NetworkAddress too short".to_string()),
246        )));
247    }
248
249    // services: u64 little-endian (bytes 0-7)
250    let services = u64::from_le_bytes([
251        data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
252    ]);
253
254    // ip: [u8; 16] (bytes 8-23)
255    let mut ip = [0u8; 16];
256    ip.copy_from_slice(&data[8..24]);
257
258    // port: u16 big-endian (bytes 24-25)
259    let port = u16::from_be_bytes([data[24], data[25]]);
260
261    Ok(crate::network::NetworkAddress {
262        time: 0,
263        services,
264        ip,
265        port,
266    })
267}
268
269/// Serialize VersionMessage to Bitcoin wire format
270/// Format per Bitcoin protocol specification:
271/// - version: i32 (4 bytes LE)
272/// - services: u64 (8 bytes LE)
273/// - timestamp: i64 (8 bytes LE)
274/// - addr_recv: NetworkAddress (26 bytes)
275/// - addr_from: NetworkAddress (26 bytes)
276/// - nonce: u64 (8 bytes LE)
277/// - user_agent: CompactSize + string bytes
278/// - start_height: i32 (4 bytes LE)
279/// - relay: u8 (1 byte, 0 or 1)
280pub fn serialize_version(v: &crate::network::VersionMessage) -> Result<Vec<u8>> {
281    use crate::varint::write_varint;
282
283    let mut buf = Vec::new();
284
285    buf.extend_from_slice(&v.version.to_le_bytes());
286
287    // services: u64 (8 bytes, little-endian)
288    buf.extend_from_slice(&v.services.to_le_bytes());
289
290    // timestamp: i64 (8 bytes, little-endian)
291    buf.extend_from_slice(&v.timestamp.to_le_bytes());
292
293    // addr_recv: NetworkAddress (26 bytes)
294    buf.extend_from_slice(&serialize_network_address(&v.addr_recv));
295
296    // addr_from: NetworkAddress (26 bytes)
297    buf.extend_from_slice(&serialize_network_address(&v.addr_from));
298
299    // nonce: u64 (8 bytes, little-endian)
300    buf.extend_from_slice(&v.nonce.to_le_bytes());
301
302    // user_agent: CompactSize (varint) + string bytes
303    let user_agent_bytes = v.user_agent.as_bytes();
304    write_varint(&mut buf, user_agent_bytes.len() as u64)?;
305    buf.extend_from_slice(user_agent_bytes);
306
307    // start_height: i32 (4 bytes, little-endian)
308    buf.extend_from_slice(&v.start_height.to_le_bytes());
309
310    // relay: u8 (1 byte, 0 or 1)
311    buf.push(if v.relay { 1 } else { 0 });
312
313    Ok(buf)
314}
315
316/// Deserialize VersionMessage from Bitcoin wire format
317pub fn deserialize_version(data: &[u8]) -> Result<crate::network::VersionMessage> {
318    use crate::varint::read_varint;
319    use std::io::Cursor;
320
321    let mut cursor = Cursor::new(data);
322
323    // version: i32 (4 bytes, little-endian)
324    let mut version_bytes = [0u8; 4];
325    cursor.read_exact(&mut version_bytes).map_err(|e| {
326        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
327            "Failed to read version: {e}"
328        ))))
329    })?;
330    let version = i32::from_le_bytes(version_bytes);
331
332    // services: u64 (8 bytes, little-endian)
333    let mut services_bytes = [0u8; 8];
334    cursor.read_exact(&mut services_bytes).map_err(|e| {
335        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
336            "Failed to read services: {e}"
337        ))))
338    })?;
339    let services = u64::from_le_bytes(services_bytes);
340
341    // timestamp: i64 (8 bytes, little-endian)
342    let mut timestamp_bytes = [0u8; 8];
343    cursor.read_exact(&mut timestamp_bytes).map_err(|e| {
344        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
345            "Failed to read timestamp: {e}"
346        ))))
347    })?;
348    let timestamp = i64::from_le_bytes(timestamp_bytes);
349
350    // addr_recv: NetworkAddress (26 bytes)
351    let mut addr_recv_bytes = [0u8; 26];
352    cursor.read_exact(&mut addr_recv_bytes).map_err(|e| {
353        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
354            "Failed to read addr_recv: {e}"
355        ))))
356    })?;
357    let addr_recv = deserialize_network_address(&addr_recv_bytes)?;
358
359    // addr_from: NetworkAddress (26 bytes)
360    let mut addr_from_bytes = [0u8; 26];
361    cursor.read_exact(&mut addr_from_bytes).map_err(|e| {
362        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
363            "Failed to read addr_from: {e}"
364        ))))
365    })?;
366    let addr_from = deserialize_network_address(&addr_from_bytes)?;
367
368    // nonce: u64 (8 bytes, little-endian)
369    let mut nonce_bytes = [0u8; 8];
370    cursor.read_exact(&mut nonce_bytes).map_err(|e| {
371        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
372            "Failed to read nonce: {e}"
373        ))))
374    })?;
375    let nonce = u64::from_le_bytes(nonce_bytes);
376
377    // user_agent: CompactSize (varint) + string bytes
378    let user_agent_len = read_varint(&mut cursor)?;
379    if user_agent_len > 10000 {
380        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
381            Cow::Owned("User agent too long".to_string()),
382        )));
383    }
384    let mut user_agent_bytes = vec![0u8; user_agent_len as usize];
385    cursor.read_exact(&mut user_agent_bytes).map_err(|e| {
386        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
387            "Failed to read user_agent: {e}"
388        ))))
389    })?;
390    // Peers send this field as raw bytes. Invalid UTF-8 must not fail the handshake.
391    let user_agent = String::from_utf8_lossy(&user_agent_bytes).into_owned();
392
393    // start_height: i32 (4 bytes, little-endian)
394    let mut start_height_bytes = [0u8; 4];
395    cursor.read_exact(&mut start_height_bytes).map_err(|e| {
396        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
397            "Failed to read start_height: {e}"
398        ))))
399    })?;
400    let start_height = i32::from_le_bytes(start_height_bytes);
401
402    // relay: u8 (1 byte, 0 or 1) - OPTIONAL field (version >= 70001)
403    let relay = {
404        let mut relay_byte = [0u8; 1];
405        match cursor.read_exact(&mut relay_byte) {
406            Ok(_) => relay_byte[0] != 0,
407            Err(_) => true, // Default to relay=true if not present
408        }
409    };
410
411    Ok(crate::network::VersionMessage {
412        version,
413        services,
414        timestamp,
415        addr_recv,
416        addr_from,
417        nonce,
418        user_agent,
419        start_height,
420        relay,
421    })
422}
423
424/// Serialize AddrMessage to Bitcoin wire format (legacy addr)
425/// Format: CompactSize(count) + [timestamp(4) + services(8) + addr(16) + port(2)]*
426pub fn serialize_addr(a: &crate::network::AddrMessage) -> Result<Vec<u8>> {
427    use crate::varint::write_varint;
428
429    let mut buf = Vec::new();
430    write_varint(&mut buf, a.addresses.len() as u64)?;
431
432    for addr in &a.addresses {
433        buf.extend_from_slice(&addr.time.to_le_bytes());
434        // services: u64 (8 bytes, little-endian)
435        buf.extend_from_slice(&addr.services.to_le_bytes());
436        // address: 16 bytes (IPv6, IPv4-mapped)
437        buf.extend_from_slice(&addr.ip);
438        // port: u16 (2 bytes, big-endian)
439        buf.extend_from_slice(&addr.port.to_be_bytes());
440    }
441    Ok(buf)
442}
443pub fn deserialize_addr(data: &[u8]) -> Result<crate::network::AddrMessage> {
444    use crate::varint::read_varint;
445    use std::io::Read;
446
447    let mut cursor = std::io::Cursor::new(data);
448    let count = read_varint(&mut cursor)?;
449    if count > 1000 {
450        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
451            Cow::Owned("Too many addresses in addr".to_string()),
452        )));
453    }
454
455    let mut addresses = Vec::with_capacity(count as usize);
456    for _ in 0..count {
457        let mut time_bytes = [0u8; 4];
458        cursor.read_exact(&mut time_bytes).map_err(|e| {
459            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
460                "Addr time: {e}"
461            ))))
462        })?;
463        let time = u32::from_le_bytes(time_bytes);
464
465        let mut services_bytes = [0u8; 8];
466        cursor.read_exact(&mut services_bytes).map_err(|e| {
467            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
468                "Addr services: {e}"
469            ))))
470        })?;
471        let services = u64::from_le_bytes(services_bytes);
472
473        let mut ip = [0u8; 16];
474        cursor.read_exact(&mut ip).map_err(|e| {
475            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
476                "Addr ip: {e}"
477            ))))
478        })?;
479
480        let mut port_bytes = [0u8; 2];
481        cursor.read_exact(&mut port_bytes).map_err(|e| {
482            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
483                "Addr port: {e}"
484            ))))
485        })?;
486        let port = u16::from_be_bytes(port_bytes);
487
488        addresses.push(crate::network::NetworkAddress {
489            services,
490            ip,
491            port,
492            time,
493        });
494    }
495    Ok(crate::network::AddrMessage { addresses })
496}
497
498/// Serialize AddrV2Message to Bitcoin wire format (BIP155)
499/// Format: CompactSize(count) + [time(4) + services(8) + address_type(1) + address(var) + port(2)]*
500pub fn serialize_addrv2(addrv2: &crate::network::AddrV2Message) -> Result<Vec<u8>> {
501    use crate::varint::write_varint;
502
503    let mut buf = Vec::new();
504
505    // CompactSize: number of addresses
506    write_varint(&mut buf, addrv2.addresses.len() as u64)?;
507
508    // Serialize each address
509    for addr in &addrv2.addresses {
510        // time: u32 (4 bytes, little-endian)
511        buf.extend_from_slice(&addr.time.to_le_bytes());
512
513        // services: u64 (8 bytes, little-endian)
514        buf.extend_from_slice(&addr.services.to_le_bytes());
515
516        // address_type: u8 (1 byte)
517        buf.push(addr.address_type as u8);
518
519        // address: variable length based on type
520        buf.extend_from_slice(&addr.address);
521
522        // port: u16 (2 bytes, big-endian)
523        buf.extend_from_slice(&addr.port.to_be_bytes());
524    }
525
526    Ok(buf)
527}
528
529/// Deserialize AddrV2Message from Bitcoin wire format (BIP155)
530pub fn deserialize_addrv2(data: &[u8]) -> Result<crate::network::AddrV2Message> {
531    use crate::varint::read_varint;
532    use std::io::Cursor;
533
534    let mut cursor = Cursor::new(data);
535
536    // CompactSize: number of addresses
537    let count = read_varint(&mut cursor)?;
538    if count > 1000 {
539        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
540            Cow::Owned("Too many addresses in addrv2".to_string()),
541        )));
542    }
543
544    let mut addresses = Vec::with_capacity(count as usize);
545
546    // Deserialize each address
547    for _ in 0..count {
548        // time: u32 (4 bytes, little-endian)
549        let mut time_bytes = [0u8; 4];
550        cursor.read_exact(&mut time_bytes).map_err(|e| {
551            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
552                "Failed to read time: {e}"
553            ))))
554        })?;
555        let time = u32::from_le_bytes(time_bytes);
556
557        // services: u64 (8 bytes, little-endian)
558        let mut services_bytes = [0u8; 8];
559        cursor.read_exact(&mut services_bytes).map_err(|e| {
560            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
561                "Failed to read services: {e}"
562            ))))
563        })?;
564        let services = u64::from_le_bytes(services_bytes);
565
566        // address_type: u8 (1 byte)
567        let mut addr_type_byte = [0u8; 1];
568        cursor.read_exact(&mut addr_type_byte).map_err(|e| {
569            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
570                "Failed to read address_type: {e}"
571            ))))
572        })?;
573        let address_type =
574            crate::network::AddressType::from_u8(addr_type_byte[0]).ok_or_else(|| {
575                ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
576                    "Invalid address type: {}",
577                    addr_type_byte[0]
578                ))))
579            })?;
580
581        // address: variable length based on type
582        let addr_len = address_type.address_length();
583        let mut address = vec![0u8; addr_len];
584        cursor.read_exact(&mut address).map_err(|e| {
585            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
586                "Failed to read address: {e}"
587            ))))
588        })?;
589
590        // port: u16 (2 bytes, big-endian)
591        let mut port_bytes = [0u8; 2];
592        cursor.read_exact(&mut port_bytes).map_err(|e| {
593            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
594                "Failed to read port: {e}"
595            ))))
596        })?;
597        let port = u16::from_be_bytes(port_bytes);
598
599        // Create NetworkAddressV2
600        let addr_v2 =
601            crate::network::NetworkAddressV2::new(time, services, address_type, address, port)?;
602
603        addresses.push(addr_v2);
604    }
605
606    Ok(crate::network::AddrV2Message { addresses })
607}
608
609/// Serialize InvMessage to Bitcoin wire format
610/// Format: count (varint) + count * (type u32 LE + hash 32 bytes)
611pub fn serialize_inv(i: &crate::network::InvMessage) -> Result<Vec<u8>> {
612    use crate::varint::write_varint;
613
614    let capacity = 9 + (36 * i.inventory.len()); // varint + (4 + 32) per item
615    let mut buf = Vec::with_capacity(capacity);
616
617    write_varint(&mut buf, i.inventory.len() as u64)?;
618
619    for item in &i.inventory {
620        buf.extend_from_slice(&item.inv_type.to_le_bytes());
621        buf.extend_from_slice(&item.hash);
622    }
623
624    Ok(buf)
625}
626
627/// Deserialize InvMessage from Bitcoin wire format
628pub fn deserialize_inv(data: &[u8]) -> Result<crate::network::InvMessage> {
629    use crate::varint::read_varint;
630    use std::io::Cursor;
631
632    if data.is_empty() {
633        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
634            Cow::Owned("Inv message is empty".to_string()),
635        )));
636    }
637
638    let mut cursor = Cursor::new(data);
639
640    let count = read_varint(&mut cursor)? as usize;
641
642    // Sanity check (max 50000 inventory items per message)
643    if count > 50000 {
644        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
645            Cow::Owned(format!("Too many inventory items: {count}")),
646        )));
647    }
648
649    let mut inventory = Vec::with_capacity(count);
650
651    for _ in 0..count {
652        let mut type_bytes = [0u8; 4];
653        std::io::Read::read_exact(&mut cursor, &mut type_bytes).map_err(|e| {
654            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
655        })?;
656        let inv_type = u32::from_le_bytes(type_bytes);
657
658        let mut hash = [0u8; 32];
659        std::io::Read::read_exact(&mut cursor, &mut hash).map_err(|e| {
660            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
661        })?;
662
663        inventory.push(crate::network::InventoryVector { inv_type, hash });
664    }
665
666    Ok(crate::network::InvMessage { inventory })
667}
668
669/// Serialize GetDataMessage to Bitcoin wire format
670/// Format: identical to Inv - count (varint) + count * (type u32 LE + hash 32 bytes)
671pub fn serialize_getdata(g: &crate::network::GetDataMessage) -> Result<Vec<u8>> {
672    use crate::varint::write_varint;
673
674    let capacity = 9 + (36 * g.inventory.len());
675    let mut buf = Vec::with_capacity(capacity);
676
677    write_varint(&mut buf, g.inventory.len() as u64)?;
678
679    for item in &g.inventory {
680        buf.extend_from_slice(&item.inv_type.to_le_bytes());
681        buf.extend_from_slice(&item.hash);
682    }
683
684    Ok(buf)
685}
686
687/// Deserialize GetDataMessage from Bitcoin wire format
688pub fn deserialize_getdata(data: &[u8]) -> Result<crate::network::GetDataMessage> {
689    use crate::varint::read_varint;
690    use std::io::Cursor;
691
692    if data.is_empty() {
693        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
694            Cow::Owned("GetData message is empty".to_string()),
695        )));
696    }
697
698    let mut cursor = Cursor::new(data);
699
700    let count = read_varint(&mut cursor)? as usize;
701
702    if count > 50000 {
703        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
704            Cow::Owned(format!("Too many inventory items: {count}")),
705        )));
706    }
707
708    let mut inventory = Vec::with_capacity(count);
709
710    for _ in 0..count {
711        let mut type_bytes = [0u8; 4];
712        std::io::Read::read_exact(&mut cursor, &mut type_bytes).map_err(|e| {
713            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
714        })?;
715        let inv_type = u32::from_le_bytes(type_bytes);
716
717        let mut hash = [0u8; 32];
718        std::io::Read::read_exact(&mut cursor, &mut hash).map_err(|e| {
719            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
720        })?;
721
722        inventory.push(crate::network::InventoryVector { inv_type, hash });
723    }
724
725    Ok(crate::network::GetDataMessage { inventory })
726}
727
728/// Serialize GetHeadersMessage to Bitcoin wire format
729/// Format: version (4 bytes LE) + hash_count (varint) + hashes (32 bytes each) + hash_stop (32 bytes)
730pub fn serialize_getheaders(gh: &crate::network::GetHeadersMessage) -> Result<Vec<u8>> {
731    use crate::varint::write_varint;
732    use std::io::Cursor;
733
734    // Estimate capacity: 4 + 1-9 + (32 * hash_count) + 32
735    let capacity = 4 + 9 + (32 * gh.block_locator_hashes.len()) + 32;
736    let mut buf = Vec::with_capacity(capacity);
737
738    // Protocol version (4 bytes, little-endian)
739    buf.extend_from_slice(&(gh.version as i32).to_le_bytes());
740
741    // Hash count (varint)
742    let mut cursor = Cursor::new(&mut buf);
743    cursor.set_position(4);
744    write_varint(&mut buf, gh.block_locator_hashes.len() as u64)?;
745
746    // Block locator hashes (32 bytes each, in internal byte order)
747    for hash in &gh.block_locator_hashes {
748        buf.extend_from_slice(hash);
749    }
750
751    // Hash stop (32 bytes)
752    buf.extend_from_slice(&gh.hash_stop);
753
754    Ok(buf)
755}
756
757/// Deserialize GetHeadersMessage from Bitcoin wire format
758pub fn deserialize_getheaders(data: &[u8]) -> Result<crate::network::GetHeadersMessage> {
759    use crate::varint::read_varint;
760    use std::io::Cursor;
761
762    if data.len() < 4 + 1 + 32 {
763        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
764            Cow::Owned("GetHeaders message too short".to_string()),
765        )));
766    }
767
768    let mut cursor = Cursor::new(data);
769
770    // Protocol version (4 bytes, little-endian)
771    let mut version_bytes = [0u8; 4];
772    std::io::Read::read_exact(&mut cursor, &mut version_bytes).map_err(|e| {
773        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
774    })?;
775    let version = i32::from_le_bytes(version_bytes) as u32;
776
777    // Hash count (varint)
778    let hash_count = read_varint(&mut cursor)? as usize;
779
780    // Sanity check on hash count
781    if hash_count > 2000 {
782        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
783            Cow::Owned(format!("Too many locator hashes: {hash_count}")),
784        )));
785    }
786
787    // Block locator hashes
788    let mut block_locator_hashes = Vec::with_capacity(hash_count);
789    for _ in 0..hash_count {
790        let mut hash = [0u8; 32];
791        std::io::Read::read_exact(&mut cursor, &mut hash).map_err(|e| {
792            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
793        })?;
794        block_locator_hashes.push(hash);
795    }
796
797    // Hash stop (32 bytes)
798    let mut hash_stop = [0u8; 32];
799    std::io::Read::read_exact(&mut cursor, &mut hash_stop).map_err(|e| {
800        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
801    })?;
802
803    Ok(crate::network::GetHeadersMessage {
804        version,
805        block_locator_hashes,
806        hash_stop,
807    })
808}
809
810/// Serialize HeadersMessage to Bitcoin wire format
811/// Format: count (varint) + headers (each: 80 bytes header + varint tx_count which is always 0)
812pub fn serialize_headers(h: &crate::network::HeadersMessage) -> Result<Vec<u8>> {
813    use crate::varint::write_varint;
814
815    // Estimate capacity: 1-9 varint + (81 bytes per header: 80 header + 1 tx_count)
816    let capacity = 9 + (81 * h.headers.len());
817    let mut buf = Vec::with_capacity(capacity);
818
819    // Header count (varint)
820    write_varint(&mut buf, h.headers.len() as u64)?;
821
822    // Headers (80 bytes each + 1 byte tx_count = 0)
823    for header in &h.headers {
824        // version (4 bytes LE)
825        buf.extend_from_slice(&(header.version as i32).to_le_bytes());
826        // prev_block_hash (32 bytes)
827        buf.extend_from_slice(&header.prev_block_hash);
828        // merkle_root (32 bytes)
829        buf.extend_from_slice(&header.merkle_root);
830        // timestamp (4 bytes LE)
831        buf.extend_from_slice(&(header.timestamp as u32).to_le_bytes());
832        // bits (4 bytes LE)
833        buf.extend_from_slice(&(header.bits as u32).to_le_bytes());
834        // nonce (4 bytes LE)
835        buf.extend_from_slice(&(header.nonce as u32).to_le_bytes());
836        // tx_count (varint, always 0 for headers message)
837        buf.push(0);
838    }
839
840    Ok(buf)
841}
842
843/// Deserialize HeadersMessage from Bitcoin wire format
844pub fn deserialize_headers(data: &[u8]) -> Result<crate::network::HeadersMessage> {
845    use crate::varint::read_varint;
846    use std::io::Cursor;
847
848    if data.is_empty() {
849        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
850            Cow::Owned("Headers message is empty".to_string()),
851        )));
852    }
853
854    let mut cursor = Cursor::new(data);
855
856    // Header count (varint)
857    let header_count = read_varint(&mut cursor)? as usize;
858
859    // Sanity check on header count (max 2000 per Bitcoin protocol)
860    if header_count > 2000 {
861        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
862            Cow::Owned(format!("Too many headers: {header_count}")),
863        )));
864    }
865
866    let mut headers = Vec::with_capacity(header_count);
867
868    for _ in 0..header_count {
869        // version (4 bytes LE)
870        let mut version_bytes = [0u8; 4];
871        std::io::Read::read_exact(&mut cursor, &mut version_bytes).map_err(|e| {
872            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
873        })?;
874        let version = i32::from_le_bytes(version_bytes) as i64;
875
876        // prev_block_hash (32 bytes)
877        let mut prev_block_hash = [0u8; 32];
878        std::io::Read::read_exact(&mut cursor, &mut prev_block_hash).map_err(|e| {
879            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
880        })?;
881
882        // merkle_root (32 bytes)
883        let mut merkle_root = [0u8; 32];
884        std::io::Read::read_exact(&mut cursor, &mut merkle_root).map_err(|e| {
885            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
886        })?;
887
888        // timestamp (4 bytes LE)
889        let mut timestamp_bytes = [0u8; 4];
890        std::io::Read::read_exact(&mut cursor, &mut timestamp_bytes).map_err(|e| {
891            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
892        })?;
893        let timestamp = u32::from_le_bytes(timestamp_bytes) as u64;
894
895        // bits (4 bytes LE)
896        let mut bits_bytes = [0u8; 4];
897        std::io::Read::read_exact(&mut cursor, &mut bits_bytes).map_err(|e| {
898            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
899        })?;
900        let bits = u32::from_le_bytes(bits_bytes) as u64;
901
902        // nonce (4 bytes LE)
903        let mut nonce_bytes = [0u8; 4];
904        std::io::Read::read_exact(&mut cursor, &mut nonce_bytes).map_err(|e| {
905            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
906        })?;
907        let nonce = u32::from_le_bytes(nonce_bytes) as u64;
908
909        let tx_count = read_varint(&mut cursor)?;
910        if tx_count != 0 {
911            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
912                Cow::Owned(format!(
913                    "Headers message transaction count must be 0, got {tx_count}"
914                )),
915            )));
916        }
917
918        headers.push(crate::BlockHeader {
919            version,
920            prev_block_hash,
921            merkle_root,
922            timestamp,
923            bits,
924            nonce,
925        });
926    }
927
928    Ok(crate::network::HeadersMessage { headers })
929}
930
931/// Serialize a block with its witness stacks for P2P relay.
932///
933/// `witnesses` is the per-tx, per-input witness stack (same layout as [`BlockMessage::witnesses`]).
934/// Pass an empty slice to produce a legacy (pre-SegWit) serialization.
935pub fn serialize_block_witnesses(
936    b: &crate::Block,
937    witnesses: &[Vec<blvm_consensus::segwit::Witness>],
938) -> Result<Vec<u8>> {
939    use crate::serialization::serialize_block_with_witnesses;
940
941    let include_witness = !witnesses.is_empty() && witnesses.iter().any(|tx_w| !tx_w.is_empty());
942
943    // Pad witnesses to match transaction count if a non-empty slice was provided but is shorter.
944    let padded;
945    let ws: &[Vec<blvm_consensus::segwit::Witness>] = if witnesses.len() < b.transactions.len() {
946        padded = {
947            let mut v = witnesses.to_vec();
948            v.resize(b.transactions.len(), Vec::new());
949            v
950        };
951        &padded
952    } else {
953        witnesses
954    };
955
956    Ok(serialize_block_with_witnesses(b, ws, include_witness))
957}
958
959/// Deserialize a block from P2P wire bytes, preserving its witness stacks.
960///
961/// Returns `(block, per_tx_witnesses)`.  The witness vec is empty if the message
962/// was serialized in legacy (non-SegWit) format.
963pub fn deserialize_block_with_witnesses_pair(
964    data: &[u8],
965) -> Result<(crate::Block, Vec<Vec<blvm_consensus::segwit::Witness>>)> {
966    use crate::serialization::block::deserialize_block_with_witnesses;
967
968    deserialize_block_with_witnesses(data).map_err(|e| {
969        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
970    })
971}
972
973/// Deserialize only the block, discarding any witness data.
974///
975/// Prefer [`deserialize_block_with_witnesses_pair`] for new call-sites; this
976/// helper is retained for legacy uses where witnesses are not needed.
977pub fn deserialize_block(data: &[u8]) -> Result<crate::Block> {
978    let (block, _witnesses) = deserialize_block_with_witnesses_pair(data)?;
979    Ok(block)
980}
981
982pub fn serialize_tx(tx: &crate::Transaction) -> Result<Vec<u8>> {
983    Ok(crate::serialization::serialize_transaction(tx))
984}
985
986/// Serialize a transaction, including witness stacks when any stack is non-empty.
987pub fn serialize_tx_with_witness(
988    tx: &crate::Transaction,
989    witnesses: &[crate::segwit::Witness],
990) -> Result<Vec<u8>> {
991    let has_witness =
992        witnesses.len() == tx.inputs.len() && witnesses.iter().any(|stack| !stack.is_empty());
993    if has_witness {
994        crate::serialization::serialize_transaction_with_witness(tx, witnesses).map_err(|e| {
995            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
996        })
997    } else {
998        serialize_tx(tx)
999    }
1000}
1001
1002pub fn deserialize_tx(data: &[u8]) -> Result<crate::Transaction> {
1003    deserialize_tx_with_witness(data).map(|(tx, _)| tx)
1004}
1005
1006/// Deserialize a transaction and the witness stack for each input.
1007pub fn deserialize_tx_with_witness(
1008    data: &[u8],
1009) -> Result<(crate::Transaction, Vec<crate::segwit::Witness>)> {
1010    crate::serialization::deserialize_transaction_with_witness(data)
1011        .map(|(tx, witnesses, _)| (tx, witnesses))
1012        .map_err(|e| {
1013            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1014        })
1015}
1016
1017/// Serialize PingMessage to Bitcoin wire format (8-byte nonce)
1018pub fn serialize_ping(p: &crate::network::PingMessage) -> Result<Vec<u8>> {
1019    Ok(p.nonce.to_le_bytes().to_vec())
1020}
1021
1022/// Deserialize PingMessage from Bitcoin wire format
1023pub fn deserialize_ping(data: &[u8]) -> Result<crate::network::PingMessage> {
1024    if data.len() < 8 {
1025        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1026            Cow::Owned(format!("Ping message too short: {} bytes", data.len())),
1027        )));
1028    }
1029
1030    let nonce = u64::from_le_bytes([
1031        data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1032    ]);
1033
1034    Ok(crate::network::PingMessage { nonce })
1035}
1036
1037/// Serialize PongMessage to Bitcoin wire format (8-byte nonce)
1038pub fn serialize_pong(p: &crate::network::PongMessage) -> Result<Vec<u8>> {
1039    Ok(p.nonce.to_le_bytes().to_vec())
1040}
1041
1042/// Deserialize PongMessage from Bitcoin wire format
1043pub fn deserialize_pong(data: &[u8]) -> Result<crate::network::PongMessage> {
1044    if data.len() < 8 {
1045        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1046            Cow::Owned(format!("Pong message too short: {} bytes", data.len())),
1047        )));
1048    }
1049
1050    let nonce = u64::from_le_bytes([
1051        data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1052    ]);
1053
1054    Ok(crate::network::PongMessage { nonce })
1055}
1056
1057/// Serialize FeeFilterMessage per BIP133: 8-byte feerate (LE)
1058pub fn serialize_feefilter(f: &crate::network::FeeFilterMessage) -> Result<Vec<u8>> {
1059    Ok(f.feerate.to_le_bytes().to_vec())
1060}
1061pub fn deserialize_feefilter(data: &[u8]) -> Result<crate::network::FeeFilterMessage> {
1062    if data.len() < 8 {
1063        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1064            Cow::Owned("FeeFilter message too short".to_string()),
1065        )));
1066    }
1067    let feerate = u64::from_le_bytes([
1068        data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1069    ]);
1070    Ok(crate::network::FeeFilterMessage { feerate })
1071}
1072
1073/// Serialize GetBlocksMessage - same structure as GetHeaders (version + locator + hash_stop)
1074pub fn serialize_getblocks(gb: &crate::network::GetBlocksMessage) -> Result<Vec<u8>> {
1075    use crate::varint::write_varint;
1076
1077    let mut buf = Vec::new();
1078    buf.extend_from_slice(&gb.version.to_le_bytes());
1079    write_varint(&mut buf, gb.block_locator_hashes.len() as u64)?;
1080    for hash in &gb.block_locator_hashes {
1081        buf.extend_from_slice(hash);
1082    }
1083    buf.extend_from_slice(&gb.hash_stop);
1084    Ok(buf)
1085}
1086pub fn deserialize_getblocks(data: &[u8]) -> Result<crate::network::GetBlocksMessage> {
1087    use crate::varint::read_varint;
1088    use std::io::Read;
1089
1090    if data.len() < 4 {
1091        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1092            Cow::Owned("GetBlocks message too short".to_string()),
1093        )));
1094    }
1095    let mut cursor = std::io::Cursor::new(data);
1096    let version = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
1097    cursor.set_position(4);
1098
1099    let count = read_varint(&mut cursor)? as usize;
1100    if count > 101 {
1101        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1102            Cow::Owned("GetBlocks locator too long".to_string()),
1103        )));
1104    }
1105    let mut block_locator_hashes = Vec::with_capacity(count);
1106    for _ in 0..count {
1107        let mut hash = [0u8; 32];
1108        cursor.read_exact(&mut hash).map_err(|e| {
1109            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
1110                "GetBlocks: {e}"
1111            ))))
1112        })?;
1113        block_locator_hashes.push(hash);
1114    }
1115    let mut hash_stop = [0u8; 32];
1116    cursor.read_exact(&mut hash_stop).map_err(|e| {
1117        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
1118            "GetBlocks hash_stop: {e}"
1119        ))))
1120    })?;
1121
1122    Ok(crate::network::GetBlocksMessage {
1123        version,
1124        block_locator_hashes,
1125        hash_stop,
1126    })
1127}
1128
1129/// Serialize NotFoundMessage to Bitcoin wire format.
1130/// Format: identical to Inv/GetData - count (varint) + count * (type u32 LE + hash 32 bytes)
1131pub fn serialize_notfound(nf: &crate::network::NotFoundMessage) -> Result<Vec<u8>> {
1132    use crate::varint::write_varint;
1133
1134    let capacity = 9 + (36 * nf.inventory.len());
1135    let mut buf = Vec::with_capacity(capacity);
1136
1137    write_varint(&mut buf, nf.inventory.len() as u64)?;
1138
1139    for item in &nf.inventory {
1140        buf.extend_from_slice(&item.inv_type.to_le_bytes());
1141        buf.extend_from_slice(&item.hash);
1142    }
1143
1144    Ok(buf)
1145}
1146
1147/// Deserialize NotFoundMessage from Bitcoin wire format.
1148pub fn deserialize_notfound(data: &[u8]) -> Result<crate::network::NotFoundMessage> {
1149    use crate::varint::read_varint;
1150    use std::io::Cursor;
1151
1152    if data.is_empty() {
1153        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1154            Cow::Owned("NotFound message is empty".to_string()),
1155        )));
1156    }
1157
1158    let mut cursor = Cursor::new(data);
1159
1160    let count = read_varint(&mut cursor)? as usize;
1161
1162    if count > 50000 {
1163        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1164            Cow::Owned(format!("Too many inventory items: {count}")),
1165        )));
1166    }
1167
1168    let mut inventory = Vec::with_capacity(count);
1169
1170    for _ in 0..count {
1171        let mut type_bytes = [0u8; 4];
1172        std::io::Read::read_exact(&mut cursor, &mut type_bytes).map_err(|e| {
1173            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1174        })?;
1175        let inv_type = u32::from_le_bytes(type_bytes);
1176
1177        let mut hash = [0u8; 32];
1178        std::io::Read::read_exact(&mut cursor, &mut hash).map_err(|e| {
1179            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1180        })?;
1181
1182        inventory.push(crate::network::InventoryVector { inv_type, hash });
1183    }
1184
1185    Ok(crate::network::NotFoundMessage { inventory })
1186}
1187
1188/// Serialize RejectMessage per BIP61: message(12) + ccode(1) + reason(var) + data(32 optional)
1189pub fn serialize_reject(r: &crate::network::RejectMessage) -> Result<Vec<u8>> {
1190    use crate::varint::write_varint;
1191
1192    let mut buf = Vec::with_capacity(12 + 1 + 9 + r.reason.len() + 32);
1193    let msg_bytes = r.message.as_bytes();
1194    if msg_bytes.len() > 12 {
1195        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1196            Cow::Owned("Reject message field too long".to_string()),
1197        )));
1198    }
1199    buf.extend_from_slice(msg_bytes);
1200    buf.extend_from_slice(&[0u8; 12][msg_bytes.len()..]);
1201
1202    buf.push(r.code);
1203
1204    write_varint(&mut buf, r.reason.len() as u64)?;
1205    buf.extend_from_slice(r.reason.as_bytes());
1206
1207    if let Some(ref h) = r.extra_data {
1208        buf.extend_from_slice(h);
1209    }
1210    Ok(buf)
1211}
1212pub fn deserialize_reject(data: &[u8]) -> Result<crate::network::RejectMessage> {
1213    use crate::varint::read_varint;
1214
1215    if data.len() < 13 {
1216        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1217            Cow::Owned("Reject message too short".to_string()),
1218        )));
1219    }
1220    let message = String::from_utf8_lossy(&data[0..12])
1221        .trim_end_matches('\0')
1222        .to_string();
1223    let code = data[12];
1224    let mut cursor = std::io::Cursor::new(&data[13..]);
1225    let reason_len_u64 = read_varint(&mut cursor)?;
1226    let pos: usize = 13_usize.saturating_add(cursor.position() as usize);
1227    if reason_len_u64 > (data.len() as u64).saturating_sub(pos as u64) {
1228        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1229            Cow::Owned("Reject reason truncated".to_string()),
1230        )));
1231    }
1232    let reason_len: usize = reason_len_u64.try_into().map_err(|_| {
1233        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1234            "Reject reason length out of range".to_string(),
1235        )))
1236    })?;
1237    let end = pos.checked_add(reason_len).ok_or_else(|| {
1238        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1239            "Reject reason length out of range".to_string(),
1240        )))
1241    })?;
1242    if end > data.len() {
1243        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1244            Cow::Owned("Reject reason truncated".to_string()),
1245        )));
1246    }
1247    let reason = String::from_utf8_lossy(&data[pos..end]).to_string();
1248    let pos = end;
1249    let extra_data = if data.len() >= pos + 32 {
1250        Some({
1251            let mut h = [0u8; 32];
1252            h.copy_from_slice(&data[pos..pos + 32]);
1253            h
1254        })
1255    } else {
1256        None
1257    };
1258    Ok(crate::network::RejectMessage {
1259        message,
1260        code,
1261        reason,
1262        extra_data,
1263    })
1264}
1265
1266/// BIP152: sendcmpct - 1 byte prefer_cmpct + 8 bytes version (LE)
1267pub fn serialize_sendcmpct(sc: &crate::network::SendCmpctMessage) -> Result<Vec<u8>> {
1268    let mut buf = Vec::with_capacity(9);
1269    buf.push(sc.prefer_cmpct);
1270    buf.extend_from_slice(&sc.version.to_le_bytes());
1271    Ok(buf)
1272}
1273pub fn deserialize_sendcmpct(data: &[u8]) -> Result<crate::network::SendCmpctMessage> {
1274    if data.len() < 9 {
1275        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1276            Cow::Owned("SendCmpct message too short".to_string()),
1277        )));
1278    }
1279    Ok(crate::network::SendCmpctMessage {
1280        prefer_cmpct: data[0],
1281        version: u64::from_le_bytes([
1282            data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
1283        ]),
1284    })
1285}
1286
1287/// BIP152: cmpctblock - header(80) + nonce(8) + shortids(varint+6*count) + prefilled(varint+each: diff_index+tx)
1288pub fn serialize_cmpctblock(cb: &crate::network::CmpctBlockMessage) -> Result<Vec<u8>> {
1289    use crate::varint::write_varint;
1290
1291    let mut buf = Vec::new();
1292    buf.extend_from_slice(&crate::serialization::serialize_block_header(&cb.header));
1293    buf.extend_from_slice(&cb.nonce.to_le_bytes());
1294    write_varint(&mut buf, cb.short_ids.len() as u64)?;
1295    for sid in &cb.short_ids {
1296        buf.extend_from_slice(sid);
1297    }
1298    write_varint(&mut buf, cb.prefilled_txs.len() as u64)?;
1299    let mut last_index = -1i64;
1300    for pt in &cb.prefilled_txs {
1301        let diff = (pt.index as i64) - last_index - 1;
1302        write_varint(&mut buf, diff as u64)?;
1303        last_index = pt.index as i64;
1304        let tx_bytes = match &pt.witness {
1305            Some(wit) if wit.iter().any(|w| !w.is_empty()) => {
1306                crate::serialization::serialize_transaction_with_witness(&pt.tx, wit)?
1307            }
1308            _ => crate::serialization::serialize_transaction(&pt.tx),
1309        };
1310        buf.extend_from_slice(&tx_bytes);
1311    }
1312    Ok(buf)
1313}
1314pub fn deserialize_cmpctblock(data: &[u8]) -> Result<crate::network::CmpctBlockMessage> {
1315    use crate::varint::read_varint;
1316    use std::io::Read;
1317
1318    /// BIP152 wire sanity bound (aligns with `getblocktxn` and inventory caps in this module).
1319    const MAX_CMPCT_SHORTIDS: u64 = 50_000;
1320    const MAX_CMPCT_PREFILLED: u64 = 50_000;
1321
1322    if data.len() < 80 + 8 {
1323        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1324            Cow::Owned("CmpctBlock message too short".to_string()),
1325        )));
1326    }
1327    let header = crate::serialization::deserialize_block_header(&data[0..80]).map_err(|e| {
1328        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1329    })?;
1330    let nonce = u64::from_le_bytes([
1331        data[80], data[81], data[82], data[83], data[84], data[85], data[86], data[87],
1332    ]);
1333    let mut cursor = std::io::Cursor::new(&data[88..]);
1334    let shortids_len_u64 = read_varint(&mut cursor)?;
1335    if shortids_len_u64 > MAX_CMPCT_SHORTIDS {
1336        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1337            Cow::Owned("CmpctBlock too many shortids".to_string()),
1338        )));
1339    }
1340    let shortids_len: usize = shortids_len_u64.try_into().map_err(|_| {
1341        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1342            "CmpctBlock shortid count out of range".to_string(),
1343        )))
1344    })?;
1345    let rem_after_count = data.len().saturating_sub(88 + cursor.position() as usize);
1346    let need_shortids = shortids_len.checked_mul(6).ok_or_else(|| {
1347        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1348            "CmpctBlock shortid size overflow".to_string(),
1349        )))
1350    })?;
1351    if need_shortids > rem_after_count {
1352        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1353            Cow::Owned("CmpctBlock shortids truncated".to_string()),
1354        )));
1355    }
1356    let mut short_ids = Vec::with_capacity(shortids_len);
1357    for _ in 0..shortids_len {
1358        let mut sid = [0u8; 6];
1359        cursor.read_exact(&mut sid).map_err(|e| {
1360            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
1361                "CmpctBlock shortid: {e}"
1362            ))))
1363        })?;
1364        short_ids.push(sid);
1365    }
1366    let prefilled_len_u64 = read_varint(&mut cursor)?;
1367    if prefilled_len_u64 > MAX_CMPCT_PREFILLED {
1368        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1369            Cow::Owned("CmpctBlock too many prefilled txs".to_string()),
1370        )));
1371    }
1372    let prefilled_len: usize = prefilled_len_u64.try_into().map_err(|_| {
1373        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1374            "CmpctBlock prefilled count out of range".to_string(),
1375        )))
1376    })?;
1377    let mut prefilled_txs = Vec::with_capacity(prefilled_len);
1378    let mut last_index: i64 = -1;
1379    let mut pos;
1380    for _ in 0..prefilled_len {
1381        let diff = read_varint(&mut cursor)? as i64;
1382        if diff < 0 {
1383            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1384                Cow::Owned("CmpctBlock prefilled index diff invalid".to_string()),
1385            )));
1386        }
1387        let index = last_index
1388            .checked_add(diff)
1389            .and_then(|v| v.checked_add(1))
1390            .ok_or_else(|| {
1391                ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1392                    "CmpctBlock prefilled index overflow".to_string(),
1393                )))
1394            })?;
1395        if index > 0xffff {
1396            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1397                Cow::Owned("CmpctBlock prefilled index too large".to_string()),
1398            )));
1399        }
1400        last_index = index;
1401        pos = cursor.position() as usize;
1402        let slice = &data[88..];
1403        if pos >= slice.len() {
1404            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1405                Cow::Owned("CmpctBlock prefilled tx truncated".to_string()),
1406            )));
1407        }
1408        // Use deserialize_transaction_with_witness: returns actual bytes consumed.
1409        // Core sends prefilled txs with TX_WITH_WITNESS (SegWit); serialize_transaction().len()
1410        // would undercount and corrupt subsequent parsing.
1411        let (tx, witnesses, consumed) = crate::serialization::deserialize_transaction_with_witness(
1412            &slice[pos..],
1413        )
1414        .map_err(|e| {
1415            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1416        })?;
1417        cursor.set_position((pos + consumed) as u64);
1418        let witness = witnesses.iter().any(|w| !w.is_empty()).then_some(witnesses);
1419        prefilled_txs.push(crate::network::PrefilledTransaction {
1420            index: index as u16,
1421            tx,
1422            witness,
1423        });
1424    }
1425    Ok(crate::network::CmpctBlockMessage {
1426        header,
1427        nonce,
1428        short_ids,
1429        prefilled_txs,
1430    })
1431}
1432
1433/// BIP152: getblocktxn - block_hash(32) + indexes(varint count + diff-encoded varints)
1434pub fn serialize_getblocktxn(gbt: &crate::network::GetBlockTxnMessage) -> Result<Vec<u8>> {
1435    use crate::varint::write_varint;
1436
1437    let mut buf = Vec::new();
1438    buf.extend_from_slice(&gbt.block_hash);
1439    write_varint(&mut buf, gbt.indices.len() as u64)?;
1440    let mut last: i64 = -1;
1441    for &idx in &gbt.indices {
1442        let diff = (idx as i64) - last - 1;
1443        if diff < 0 {
1444            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1445                Cow::Owned("GetBlockTxn indices must be strictly increasing".to_string()),
1446            )));
1447        }
1448        write_varint(&mut buf, diff as u64)?;
1449        last = idx as i64;
1450    }
1451    Ok(buf)
1452}
1453pub fn deserialize_getblocktxn(data: &[u8]) -> Result<crate::network::GetBlockTxnMessage> {
1454    use crate::varint::read_varint;
1455
1456    if data.len() < 32 {
1457        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1458            Cow::Owned("GetBlockTxn message too short".to_string()),
1459        )));
1460    }
1461    let mut block_hash = [0u8; 32];
1462    block_hash.copy_from_slice(&data[0..32]);
1463    let mut cursor = std::io::Cursor::new(&data[32..]);
1464    let count = read_varint(&mut cursor)? as usize;
1465    if count > 50000 {
1466        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1467            Cow::Owned("GetBlockTxn too many indices".to_string()),
1468        )));
1469    }
1470    let mut indices = Vec::with_capacity(count);
1471    let mut last: i64 = -1;
1472    for _ in 0..count {
1473        let diff = read_varint(&mut cursor)? as i64;
1474        if diff < 0 {
1475            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1476                Cow::Owned("GetBlockTxn index diff invalid".to_string()),
1477            )));
1478        }
1479        last = last
1480            .checked_add(diff)
1481            .and_then(|v| v.checked_add(1))
1482            .ok_or_else(|| {
1483                ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1484                    "GetBlockTxn index overflow".to_string(),
1485                )))
1486            })?;
1487        if last > 0xffff {
1488            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1489                Cow::Owned("GetBlockTxn index too large".to_string()),
1490            )));
1491        }
1492        indices.push(last as u16);
1493    }
1494    Ok(crate::network::GetBlockTxnMessage {
1495        block_hash,
1496        indices,
1497    })
1498}
1499
1500/// BIP152: blocktxn - block_hash(32) + count(varint) + transactions
1501pub fn serialize_blocktxn(bt: &crate::network::BlockTxnMessage) -> Result<Vec<u8>> {
1502    use crate::varint::write_varint;
1503
1504    let mut buf = Vec::new();
1505    buf.extend_from_slice(&bt.block_hash);
1506    write_varint(&mut buf, bt.transactions.len() as u64)?;
1507    match (&bt.witnesses, bt.transactions.len()) {
1508        (Some(witnesses), len) if witnesses.len() == len => {
1509            for (tx, wit) in bt.transactions.iter().zip(witnesses.iter()) {
1510                buf.extend_from_slice(&crate::serialization::serialize_transaction_with_witness(
1511                    tx, wit,
1512                )?);
1513            }
1514        }
1515        _ => {
1516            for tx in &bt.transactions {
1517                buf.extend_from_slice(&crate::serialization::serialize_transaction(tx));
1518            }
1519        }
1520    }
1521    Ok(buf)
1522}
1523pub fn deserialize_blocktxn(data: &[u8]) -> Result<crate::network::BlockTxnMessage> {
1524    use crate::varint::read_varint;
1525
1526    if data.len() < 32 {
1527        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1528            Cow::Owned("BlockTxn message too short".to_string()),
1529        )));
1530    }
1531    let mut block_hash = [0u8; 32];
1532    block_hash.copy_from_slice(&data[0..32]);
1533    let mut cursor = std::io::Cursor::new(&data[32..]);
1534    let count = read_varint(&mut cursor)? as usize;
1535    // Same ceiling as compact-block short ids and getblocktxn indexes.
1536    if count > 50_000 {
1537        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1538            Cow::Owned("BlockTxn too many transactions".to_string()),
1539        )));
1540    }
1541    let mut transactions = Vec::with_capacity(count);
1542    let mut all_witnesses = Vec::with_capacity(count);
1543    let mut pos = 32 + (cursor.position() as usize);
1544    for _ in 0..count {
1545        if pos >= data.len() {
1546            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1547                Cow::Owned("BlockTxn truncated".to_string()),
1548            )));
1549        }
1550        // BIP152: blocktxn txs use same format as block (TX_WITH_WITNESS). Use
1551        // deserialize_transaction_with_witness for correct bytes consumed.
1552        let (tx, witnesses, consumed) = crate::serialization::deserialize_transaction_with_witness(
1553            &data[pos..],
1554        )
1555        .map_err(|e| {
1556            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1557        })?;
1558        pos += consumed;
1559        transactions.push(tx);
1560        all_witnesses.push(witnesses);
1561    }
1562    let witnesses = all_witnesses
1563        .iter()
1564        .any(|w| w.iter().any(|s| !s.is_empty()))
1565        .then_some(all_witnesses);
1566    Ok(crate::network::BlockTxnMessage {
1567        block_hash,
1568        transactions,
1569        witnesses,
1570    })
1571}
1572
1573#[cfg(feature = "utxo-commitments")]
1574fn serialize_getutxoset(gus: &crate::commons::GetUTXOSetMessage) -> Result<Vec<u8>> {
1575    let mut buf = Vec::with_capacity(40);
1576    buf.extend_from_slice(&gus.height.to_le_bytes());
1577    buf.extend_from_slice(&gus.block_hash);
1578    Ok(buf)
1579}
1580#[cfg(feature = "utxo-commitments")]
1581fn deserialize_getutxoset(data: &[u8]) -> Result<crate::commons::GetUTXOSetMessage> {
1582    if data.len() < 40 {
1583        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1584            Cow::Owned("GetUTXOSet message too short".to_string()),
1585        )));
1586    }
1587    let height = u64::from_le_bytes([
1588        data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1589    ]);
1590    let mut block_hash = [0u8; 32];
1591    block_hash.copy_from_slice(&data[8..40]);
1592    Ok(crate::commons::GetUTXOSetMessage { height, block_hash })
1593}
1594
1595#[cfg(feature = "utxo-commitments")]
1596fn serialize_utxoset(us: &crate::commons::UTXOSetMessage) -> Result<Vec<u8>> {
1597    bincode::serialize(us).map_err(|e| {
1598        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1599    })
1600}
1601#[cfg(feature = "utxo-commitments")]
1602fn deserialize_utxoset(data: &[u8]) -> Result<crate::commons::UTXOSetMessage> {
1603    bincode::deserialize(data).map_err(|e| {
1604        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1605    })
1606}
1607
1608#[cfg(feature = "utxo-commitments")]
1609fn serialize_getfilteredblock(gfb: &crate::commons::GetFilteredBlockMessage) -> Result<Vec<u8>> {
1610    bincode::serialize(gfb).map_err(|e| {
1611        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1612    })
1613}
1614#[cfg(feature = "utxo-commitments")]
1615fn deserialize_getfilteredblock(data: &[u8]) -> Result<crate::commons::GetFilteredBlockMessage> {
1616    bincode::deserialize(data).map_err(|e| {
1617        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1618    })
1619}
1620
1621#[cfg(feature = "utxo-commitments")]
1622fn serialize_filteredblock(fb: &crate::commons::FilteredBlockMessage) -> Result<Vec<u8>> {
1623    bincode::serialize(fb).map_err(|e| {
1624        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1625    })
1626}
1627#[cfg(feature = "utxo-commitments")]
1628fn deserialize_filteredblock(data: &[u8]) -> Result<crate::commons::FilteredBlockMessage> {
1629    bincode::deserialize(data).map_err(|e| {
1630        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1631    })
1632}
1633
1634fn serialize_getbanlist(gbl: &crate::commons::GetBanListMessage) -> Result<Vec<u8>> {
1635    bincode::serialize(gbl).map_err(|e| {
1636        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1637    })
1638}
1639fn deserialize_getbanlist(data: &[u8]) -> Result<crate::commons::GetBanListMessage> {
1640    bincode::deserialize(data).map_err(|e| {
1641        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1642    })
1643}
1644
1645fn serialize_banlist(bl: &crate::commons::BanListMessage) -> Result<Vec<u8>> {
1646    bincode::serialize(bl).map_err(|e| {
1647        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1648    })
1649}
1650fn deserialize_banlist(data: &[u8]) -> Result<crate::commons::BanListMessage> {
1651    bincode::deserialize(data).map_err(|e| {
1652        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1653    })
1654}
1655
1656#[cfg(test)]
1657mod blocktxn_count {
1658    use super::deserialize_blocktxn;
1659
1660    fn minimal_tx() -> Vec<u8> {
1661        let mut tx = Vec::new();
1662        tx.extend_from_slice(&1i32.to_le_bytes());
1663        tx.push(0);
1664        tx.push(0);
1665        tx.extend_from_slice(&0u32.to_le_bytes());
1666        tx
1667    }
1668
1669    #[test]
1670    fn twelve_thousand_transactions_decode() {
1671        let one = minimal_tx();
1672        let mut payload = vec![0u8; 32];
1673        payload.push(0xfd);
1674        payload.extend_from_slice(&12_239u16.to_le_bytes());
1675        for _ in 0..12_239 {
1676            payload.extend_from_slice(&one);
1677        }
1678        let msg = deserialize_blocktxn(&payload).expect("12239 transactions fit");
1679        assert_eq!(msg.transactions.len(), 12_239);
1680
1681        let mut over = vec![0u8; 32];
1682        over.push(0xfd);
1683        over.extend_from_slice(&50_001u16.to_le_bytes());
1684        let err = deserialize_blocktxn(&over).unwrap_err();
1685        let text = format!("{err:?}");
1686        assert!(text.contains("too many"), "{text}");
1687    }
1688}