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
1146        .saturating_add(cursor.position() as usize);
1147    if reason_len_u64 > (data.len() as u64).saturating_sub(pos as u64) {
1148        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1149            Cow::Owned("Reject reason truncated".to_string()),
1150        )));
1151    }
1152    let reason_len: usize = reason_len_u64
1153        .try_into()
1154        .map_err(|_| {
1155            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1156                "Reject reason length out of range".to_string(),
1157            )))
1158        })?;
1159    let end = pos
1160        .checked_add(reason_len)
1161        .ok_or_else(|| {
1162            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1163                "Reject reason length out of range".to_string(),
1164            )))
1165        })?;
1166    if end > data.len() {
1167        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1168            Cow::Owned("Reject reason truncated".to_string()),
1169        )));
1170    }
1171    let reason = String::from_utf8_lossy(&data[pos..end]).to_string();
1172    let pos = end;
1173    let extra_data = if data.len() >= pos + 32 {
1174        Some({
1175            let mut h = [0u8; 32];
1176            h.copy_from_slice(&data[pos..pos + 32]);
1177            h
1178        })
1179    } else {
1180        None
1181    };
1182    Ok(crate::network::RejectMessage {
1183        message,
1184        code,
1185        reason,
1186        extra_data,
1187    })
1188}
1189
1190/// BIP152: sendcmpct - 1 byte prefer_cmpct + 8 bytes version (LE)
1191pub fn serialize_sendcmpct(sc: &crate::network::SendCmpctMessage) -> Result<Vec<u8>> {
1192    let mut buf = Vec::with_capacity(9);
1193    buf.push(sc.prefer_cmpct);
1194    buf.extend_from_slice(&sc.version.to_le_bytes());
1195    Ok(buf)
1196}
1197pub fn deserialize_sendcmpct(data: &[u8]) -> Result<crate::network::SendCmpctMessage> {
1198    if data.len() < 9 {
1199        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1200            Cow::Owned("SendCmpct message too short".to_string()),
1201        )));
1202    }
1203    Ok(crate::network::SendCmpctMessage {
1204        prefer_cmpct: data[0],
1205        version: u64::from_le_bytes([
1206            data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
1207        ]),
1208    })
1209}
1210
1211/// BIP152: cmpctblock - header(80) + nonce(8) + shortids(varint+6*count) + prefilled(varint+each: diff_index+tx)
1212pub fn serialize_cmpctblock(cb: &crate::network::CmpctBlockMessage) -> Result<Vec<u8>> {
1213    use crate::varint::write_varint;
1214
1215    let mut buf = Vec::new();
1216    buf.extend_from_slice(&crate::serialization::serialize_block_header(&cb.header));
1217    buf.extend_from_slice(&cb.nonce.to_le_bytes());
1218    write_varint(&mut buf, cb.short_ids.len() as u64)?;
1219    for sid in &cb.short_ids {
1220        buf.extend_from_slice(sid);
1221    }
1222    write_varint(&mut buf, cb.prefilled_txs.len() as u64)?;
1223    let mut last_index = -1i64;
1224    for pt in &cb.prefilled_txs {
1225        let diff = (pt.index as i64) - last_index - 1;
1226        write_varint(&mut buf, diff as u64)?;
1227        last_index = pt.index as i64;
1228        let tx_bytes = match &pt.witness {
1229            Some(wit) if wit.iter().any(|w| !w.is_empty()) => {
1230                crate::serialization::serialize_transaction_with_witness(&pt.tx, wit)
1231            }
1232            _ => crate::serialization::serialize_transaction(&pt.tx),
1233        };
1234        buf.extend_from_slice(&tx_bytes);
1235    }
1236    Ok(buf)
1237}
1238pub fn deserialize_cmpctblock(data: &[u8]) -> Result<crate::network::CmpctBlockMessage> {
1239    use crate::varint::read_varint;
1240    use std::io::Read;
1241
1242    /// BIP152 wire sanity bound (aligns with `getblocktxn` and inventory caps in this module).
1243    const MAX_CMPCT_SHORTIDS: u64 = 50_000;
1244    const MAX_CMPCT_PREFILLED: u64 = 50_000;
1245
1246    if data.len() < 80 + 8 {
1247        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1248            Cow::Owned("CmpctBlock message too short".to_string()),
1249        )));
1250    }
1251    let header = crate::serialization::deserialize_block_header(&data[0..80]).map_err(|e| {
1252        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1253    })?;
1254    let nonce = u64::from_le_bytes([
1255        data[80], data[81], data[82], data[83], data[84], data[85], data[86], data[87],
1256    ]);
1257    let mut cursor = std::io::Cursor::new(&data[88..]);
1258    let shortids_len_u64 = read_varint(&mut cursor)?;
1259    if shortids_len_u64 > MAX_CMPCT_SHORTIDS {
1260        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1261            Cow::Owned("CmpctBlock too many shortids".to_string()),
1262        )));
1263    }
1264    let shortids_len: usize = shortids_len_u64
1265        .try_into()
1266        .map_err(|_| {
1267            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1268                "CmpctBlock shortid count out of range".to_string(),
1269            )))
1270        })?;
1271    let rem_after_count = data.len().saturating_sub(88 + cursor.position() as usize);
1272    let need_shortids = shortids_len
1273        .checked_mul(6)
1274        .ok_or_else(|| {
1275            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1276                "CmpctBlock shortid size overflow".to_string(),
1277            )))
1278        })?;
1279    if need_shortids > rem_after_count {
1280        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1281            Cow::Owned("CmpctBlock shortids truncated".to_string()),
1282        )));
1283    }
1284    let mut short_ids = Vec::with_capacity(shortids_len);
1285    for _ in 0..shortids_len {
1286        let mut sid = [0u8; 6];
1287        cursor.read_exact(&mut sid).map_err(|e| {
1288            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
1289                "CmpctBlock shortid: {e}"
1290            ))))
1291        })?;
1292        short_ids.push(sid);
1293    }
1294    let prefilled_len_u64 = read_varint(&mut cursor)?;
1295    if prefilled_len_u64 > MAX_CMPCT_PREFILLED {
1296        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1297            Cow::Owned("CmpctBlock too many prefilled txs".to_string()),
1298        )));
1299    }
1300    let prefilled_len: usize = prefilled_len_u64
1301        .try_into()
1302        .map_err(|_| {
1303            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1304                "CmpctBlock prefilled count out of range".to_string(),
1305            )))
1306        })?;
1307    let mut prefilled_txs = Vec::with_capacity(prefilled_len);
1308    let mut last_index: i64 = -1;
1309    let mut pos;
1310    for _ in 0..prefilled_len {
1311        let diff = read_varint(&mut cursor)? as i64;
1312        if diff < 0 {
1313            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1314                Cow::Owned("CmpctBlock prefilled index diff invalid".to_string()),
1315            )));
1316        }
1317        let index = last_index
1318            .checked_add(diff)
1319            .and_then(|v| v.checked_add(1))
1320            .ok_or_else(|| {
1321                ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1322                    "CmpctBlock prefilled index overflow".to_string(),
1323                )))
1324            })?;
1325        if index > 0xffff {
1326            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1327                Cow::Owned("CmpctBlock prefilled index too large".to_string()),
1328            )));
1329        }
1330        last_index = index;
1331        pos = cursor.position() as usize;
1332        let slice = &data[88..];
1333        if pos >= slice.len() {
1334            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1335                Cow::Owned("CmpctBlock prefilled tx truncated".to_string()),
1336            )));
1337        }
1338        // Use deserialize_transaction_with_witness: returns actual bytes consumed.
1339        // Core sends prefilled txs with TX_WITH_WITNESS (SegWit); serialize_transaction().len()
1340        // would undercount and corrupt subsequent parsing.
1341        let (tx, witnesses, consumed) = crate::serialization::deserialize_transaction_with_witness(
1342            &slice[pos..],
1343        )
1344        .map_err(|e| {
1345            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1346        })?;
1347        cursor.set_position((pos + consumed) as u64);
1348        let witness = witnesses.iter().any(|w| !w.is_empty()).then_some(witnesses);
1349        prefilled_txs.push(crate::network::PrefilledTransaction {
1350            index: index as u16,
1351            tx,
1352            witness,
1353        });
1354    }
1355    Ok(crate::network::CmpctBlockMessage {
1356        header,
1357        nonce,
1358        short_ids,
1359        prefilled_txs,
1360    })
1361}
1362
1363/// BIP152: getblocktxn - block_hash(32) + indexes(varint count + diff-encoded varints)
1364pub fn serialize_getblocktxn(gbt: &crate::network::GetBlockTxnMessage) -> Result<Vec<u8>> {
1365    use crate::varint::write_varint;
1366
1367    let mut buf = Vec::new();
1368    buf.extend_from_slice(&gbt.block_hash);
1369    write_varint(&mut buf, gbt.indices.len() as u64)?;
1370    let mut last: i64 = -1;
1371    for &idx in &gbt.indices {
1372        let diff = (idx as i64) - last - 1;
1373        if diff < 0 {
1374            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1375                Cow::Owned("GetBlockTxn indices must be strictly increasing".to_string()),
1376            )));
1377        }
1378        write_varint(&mut buf, diff as u64)?;
1379        last = idx as i64;
1380    }
1381    Ok(buf)
1382}
1383pub fn deserialize_getblocktxn(data: &[u8]) -> Result<crate::network::GetBlockTxnMessage> {
1384    use crate::varint::read_varint;
1385
1386    if data.len() < 32 {
1387        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1388            Cow::Owned("GetBlockTxn message too short".to_string()),
1389        )));
1390    }
1391    let mut block_hash = [0u8; 32];
1392    block_hash.copy_from_slice(&data[0..32]);
1393    let mut cursor = std::io::Cursor::new(&data[32..]);
1394    let count = read_varint(&mut cursor)? as usize;
1395    if count > 50000 {
1396        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1397            Cow::Owned("GetBlockTxn too many indices".to_string()),
1398        )));
1399    }
1400    let mut indices = Vec::with_capacity(count);
1401    let mut last: i64 = -1;
1402    for _ in 0..count {
1403        let diff = read_varint(&mut cursor)? as i64;
1404        if diff < 0 {
1405            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1406                Cow::Owned("GetBlockTxn index diff invalid".to_string()),
1407            )));
1408        }
1409        last = last
1410            .checked_add(diff)
1411            .and_then(|v| v.checked_add(1))
1412            .ok_or_else(|| {
1413                ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1414                    "GetBlockTxn index overflow".to_string(),
1415                )))
1416            })?;
1417        if last > 0xffff {
1418            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1419                Cow::Owned("GetBlockTxn index too large".to_string()),
1420            )));
1421        }
1422        indices.push(last as u16);
1423    }
1424    Ok(crate::network::GetBlockTxnMessage {
1425        block_hash,
1426        indices,
1427    })
1428}
1429
1430/// BIP152: blocktxn - block_hash(32) + count(varint) + transactions
1431pub fn serialize_blocktxn(bt: &crate::network::BlockTxnMessage) -> Result<Vec<u8>> {
1432    use crate::varint::write_varint;
1433
1434    let mut buf = Vec::new();
1435    buf.extend_from_slice(&bt.block_hash);
1436    write_varint(&mut buf, bt.transactions.len() as u64)?;
1437    match (&bt.witnesses, bt.transactions.len()) {
1438        (Some(witnesses), len) if witnesses.len() == len => {
1439            for (tx, wit) in bt.transactions.iter().zip(witnesses.iter()) {
1440                buf.extend_from_slice(&crate::serialization::serialize_transaction_with_witness(
1441                    tx, wit,
1442                ));
1443            }
1444        }
1445        _ => {
1446            for tx in &bt.transactions {
1447                buf.extend_from_slice(&crate::serialization::serialize_transaction(tx));
1448            }
1449        }
1450    }
1451    Ok(buf)
1452}
1453pub fn deserialize_blocktxn(data: &[u8]) -> Result<crate::network::BlockTxnMessage> {
1454    use crate::varint::read_varint;
1455
1456    if data.len() < 32 {
1457        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1458            Cow::Owned("BlockTxn 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 > 2000 {
1466        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1467            Cow::Owned("BlockTxn too many transactions".to_string()),
1468        )));
1469    }
1470    let mut transactions = Vec::with_capacity(count);
1471    let mut all_witnesses = Vec::with_capacity(count);
1472    let mut pos = 32 + (cursor.position() as usize);
1473    for _ in 0..count {
1474        if pos >= data.len() {
1475            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1476                Cow::Owned("BlockTxn truncated".to_string()),
1477            )));
1478        }
1479        // BIP152: blocktxn txs use same format as block (TX_WITH_WITNESS). Use
1480        // deserialize_transaction_with_witness for correct bytes consumed.
1481        let (tx, witnesses, consumed) = crate::serialization::deserialize_transaction_with_witness(
1482            &data[pos..],
1483        )
1484        .map_err(|e| {
1485            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1486        })?;
1487        pos += consumed;
1488        transactions.push(tx);
1489        all_witnesses.push(witnesses);
1490    }
1491    let witnesses = all_witnesses
1492        .iter()
1493        .any(|w| w.iter().any(|s| !s.is_empty()))
1494        .then_some(all_witnesses);
1495    Ok(crate::network::BlockTxnMessage {
1496        block_hash,
1497        transactions,
1498        witnesses,
1499    })
1500}
1501
1502#[cfg(feature = "utxo-commitments")]
1503fn serialize_getutxoset(gus: &crate::commons::GetUTXOSetMessage) -> Result<Vec<u8>> {
1504    let mut buf = Vec::with_capacity(40);
1505    buf.extend_from_slice(&gus.height.to_le_bytes());
1506    buf.extend_from_slice(&gus.block_hash);
1507    Ok(buf)
1508}
1509#[cfg(feature = "utxo-commitments")]
1510fn deserialize_getutxoset(data: &[u8]) -> Result<crate::commons::GetUTXOSetMessage> {
1511    if data.len() < 40 {
1512        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1513            Cow::Owned("GetUTXOSet message too short".to_string()),
1514        )));
1515    }
1516    let height = u64::from_le_bytes([
1517        data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1518    ]);
1519    let mut block_hash = [0u8; 32];
1520    block_hash.copy_from_slice(&data[8..40]);
1521    Ok(crate::commons::GetUTXOSetMessage { height, block_hash })
1522}
1523
1524#[cfg(feature = "utxo-commitments")]
1525fn serialize_utxoset(us: &crate::commons::UTXOSetMessage) -> Result<Vec<u8>> {
1526    bincode::serialize(us).map_err(|e| {
1527        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1528    })
1529}
1530#[cfg(feature = "utxo-commitments")]
1531fn deserialize_utxoset(data: &[u8]) -> Result<crate::commons::UTXOSetMessage> {
1532    bincode::deserialize(data).map_err(|e| {
1533        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1534    })
1535}
1536
1537#[cfg(feature = "utxo-commitments")]
1538fn serialize_getfilteredblock(gfb: &crate::commons::GetFilteredBlockMessage) -> Result<Vec<u8>> {
1539    bincode::serialize(gfb).map_err(|e| {
1540        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1541    })
1542}
1543#[cfg(feature = "utxo-commitments")]
1544fn deserialize_getfilteredblock(data: &[u8]) -> Result<crate::commons::GetFilteredBlockMessage> {
1545    bincode::deserialize(data).map_err(|e| {
1546        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1547    })
1548}
1549
1550#[cfg(feature = "utxo-commitments")]
1551fn serialize_filteredblock(fb: &crate::commons::FilteredBlockMessage) -> Result<Vec<u8>> {
1552    bincode::serialize(fb).map_err(|e| {
1553        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1554    })
1555}
1556#[cfg(feature = "utxo-commitments")]
1557fn deserialize_filteredblock(data: &[u8]) -> Result<crate::commons::FilteredBlockMessage> {
1558    bincode::deserialize(data).map_err(|e| {
1559        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1560    })
1561}
1562
1563fn serialize_getbanlist(gbl: &crate::commons::GetBanListMessage) -> Result<Vec<u8>> {
1564    bincode::serialize(gbl).map_err(|e| {
1565        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1566    })
1567}
1568fn deserialize_getbanlist(data: &[u8]) -> Result<crate::commons::GetBanListMessage> {
1569    bincode::deserialize(data).map_err(|e| {
1570        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1571    })
1572}
1573
1574fn serialize_banlist(bl: &crate::commons::BanListMessage) -> Result<Vec<u8>> {
1575    bincode::serialize(bl).map_err(|e| {
1576        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1577    })
1578}
1579fn deserialize_banlist(data: &[u8]) -> Result<crate::commons::BanListMessage> {
1580    bincode::deserialize(data).map_err(|e| {
1581        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1582    })
1583}