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