blvm_primitives/serialization/
transaction.rs1use super::varint::{decode_varint, encode_varint};
7use crate::error::{ConsensusError, Result};
8use crate::types::*;
9use std::borrow::Cow;
10
11#[cfg(feature = "production")]
12use smallvec::SmallVec;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum TransactionParseError {
17 InsufficientBytes,
18 InvalidVersion,
19 InvalidInputCount,
20 InvalidOutputCount,
21 InvalidScriptLength,
22 InvalidLockTime,
23}
24
25impl std::fmt::Display for TransactionParseError {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 match self {
28 TransactionParseError::InsufficientBytes => {
29 write!(f, "Insufficient bytes to parse transaction")
30 }
31 TransactionParseError::InvalidVersion => write!(f, "Invalid transaction version"),
32 TransactionParseError::InvalidInputCount => write!(f, "Invalid input count"),
33 TransactionParseError::InvalidOutputCount => write!(f, "Invalid output count"),
34 TransactionParseError::InvalidScriptLength => write!(f, "Invalid script length"),
35 TransactionParseError::InvalidLockTime => write!(f, "Invalid lock time"),
36 }
37 }
38}
39
40impl std::error::Error for TransactionParseError {}
41
42#[inline]
43fn checked_slice_end(offset: usize, len: u64) -> Result<usize> {
44 let len = usize::try_from(len).map_err(|_| {
45 ConsensusError::Serialization(Cow::Owned(
46 TransactionParseError::InvalidScriptLength.to_string(),
47 ))
48 })?;
49 offset.checked_add(len).ok_or_else(|| {
50 ConsensusError::Serialization(Cow::Owned(
51 TransactionParseError::InsufficientBytes.to_string(),
52 ))
53 })
54}
55
56#[inline(always)]
58pub fn serialize_transaction(tx: &Transaction) -> Vec<u8> {
59 let mut result = Vec::new();
60 serialize_transaction_append(&mut result, tx);
61 result
62}
63
64#[inline(always)]
66fn serialize_transaction_append(result: &mut Vec<u8>, tx: &Transaction) {
67 result.extend_from_slice(&(tx.version as i32).to_le_bytes());
68 result.extend_from_slice(&encode_varint(tx.inputs.len() as u64));
69
70 for input in &tx.inputs {
71 result.extend_from_slice(&input.prevout.hash);
72 result.extend_from_slice(&input.prevout.index.to_le_bytes());
73 result.extend_from_slice(&encode_varint(input.script_sig.len() as u64));
74 result.extend_from_slice(&input.script_sig);
75 result.extend_from_slice(&(input.sequence as u32).to_le_bytes());
76 }
77
78 result.extend_from_slice(&encode_varint(tx.outputs.len() as u64));
79
80 for output in &tx.outputs {
81 result.extend_from_slice(&(output.value as u64).to_le_bytes());
82 result.extend_from_slice(&encode_varint(output.script_pubkey.len() as u64));
83 result.extend_from_slice(&output.script_pubkey);
84 }
85
86 result.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
87}
88
89#[inline(always)]
91pub fn serialize_transaction_into(dst: &mut Vec<u8>, tx: &Transaction) -> usize {
92 dst.clear();
93 serialize_transaction_append(dst, tx);
94 dst.len()
95}
96
97pub fn serialize_transaction_with_witness(tx: &Transaction, witnesses: &[Witness]) -> Vec<u8> {
99 assert_eq!(
100 witnesses.len(),
101 tx.inputs.len(),
102 "witness count must match input count"
103 );
104 let mut result = Vec::new();
105 result.extend_from_slice(&(tx.version as i32).to_le_bytes());
106 result.push(0x00);
107 result.push(0x01);
108 result.extend_from_slice(&encode_varint(tx.inputs.len() as u64));
109 for input in &tx.inputs {
110 result.extend_from_slice(&input.prevout.hash);
111 result.extend_from_slice(&input.prevout.index.to_le_bytes());
112 result.extend_from_slice(&encode_varint(input.script_sig.len() as u64));
113 result.extend_from_slice(&input.script_sig);
114 result.extend_from_slice(&(input.sequence as u32).to_le_bytes());
115 }
116 result.extend_from_slice(&encode_varint(tx.outputs.len() as u64));
117 for output in &tx.outputs {
118 result.extend_from_slice(&(output.value as u64).to_le_bytes());
119 result.extend_from_slice(&encode_varint(output.script_pubkey.len() as u64));
120 result.extend_from_slice(&output.script_pubkey);
121 }
122 for witness in witnesses {
123 result.extend_from_slice(&encode_varint(witness.len() as u64));
124 for element in witness {
125 result.extend_from_slice(&encode_varint(element.len() as u64));
126 result.extend_from_slice(element);
127 }
128 }
129 result.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
130 result
131}
132
133pub fn deserialize_transaction(data: &[u8]) -> Result<Transaction> {
135 let mut offset = 0;
136
137 if data.len() < offset + 4 {
138 return Err(ConsensusError::Serialization(Cow::Owned(
139 TransactionParseError::InsufficientBytes.to_string(),
140 )));
141 }
142 let version = i32::from_le_bytes([
143 data[offset],
144 data[offset + 1],
145 data[offset + 2],
146 data[offset + 3],
147 ]) as u64;
148 offset += 4;
149
150 let is_segwit = data.len() >= offset + 2 && data[offset] == 0x00 && data[offset + 1] == 0x01;
151
152 if is_segwit {
153 offset += 2;
154 }
155
156 let (input_count, varint_len) = decode_varint(&data[offset..])?;
157 offset += varint_len;
158
159 if input_count > 1000000 {
160 return Err(ConsensusError::Serialization(Cow::Owned(
161 TransactionParseError::InvalidInputCount.to_string(),
162 )));
163 }
164
165 #[cfg(feature = "production")]
166 let mut inputs = SmallVec::<[TransactionInput; 2]>::new();
167 #[cfg(not(feature = "production"))]
168 let mut inputs = Vec::new();
169
170 for _ in 0..input_count {
171 if data.len() < offset + 36 {
172 return Err(ConsensusError::Serialization(Cow::Owned(
173 TransactionParseError::InsufficientBytes.to_string(),
174 )));
175 }
176 let mut hash = [0u8; 32];
177 hash.copy_from_slice(&data[offset..offset + 32]);
178 offset += 32;
179
180 let index = u32::from_le_bytes([
181 data[offset],
182 data[offset + 1],
183 data[offset + 2],
184 data[offset + 3],
185 ]);
186 offset += 4;
187
188 let (script_len, varint_len) = decode_varint(&data[offset..])?;
189 offset += varint_len;
190
191 let script_sig_end = checked_slice_end(offset, script_len)?;
192 if data.len() < script_sig_end {
193 return Err(ConsensusError::Serialization(Cow::Owned(
194 TransactionParseError::InsufficientBytes.to_string(),
195 )));
196 }
197 let script_sig = data[offset..script_sig_end].to_vec();
198 offset = script_sig_end;
199
200 if data.len() < offset + 4 {
201 return Err(ConsensusError::Serialization(Cow::Owned(
202 TransactionParseError::InsufficientBytes.to_string(),
203 )));
204 }
205 let sequence = u32::from_le_bytes([
206 data[offset],
207 data[offset + 1],
208 data[offset + 2],
209 data[offset + 3],
210 ]) as u64;
211 offset += 4;
212
213 inputs.push(TransactionInput {
214 prevout: OutPoint { hash, index },
215 script_sig,
216 sequence,
217 });
218 }
219
220 let (output_count, varint_len) = decode_varint(&data[offset..])?;
221 offset += varint_len;
222
223 if output_count > 1000000 {
224 return Err(ConsensusError::Serialization(Cow::Owned(
225 TransactionParseError::InvalidOutputCount.to_string(),
226 )));
227 }
228
229 #[cfg(feature = "production")]
230 let mut outputs = SmallVec::<[TransactionOutput; 2]>::new();
231 #[cfg(not(feature = "production"))]
232 let mut outputs = Vec::new();
233
234 for _ in 0..output_count {
235 if data.len() < offset + 8 {
236 return Err(ConsensusError::Serialization(Cow::Owned(
237 TransactionParseError::InsufficientBytes.to_string(),
238 )));
239 }
240 let value = i64::from_le_bytes([
241 data[offset],
242 data[offset + 1],
243 data[offset + 2],
244 data[offset + 3],
245 data[offset + 4],
246 data[offset + 5],
247 data[offset + 6],
248 data[offset + 7],
249 ]);
250 offset += 8;
251
252 let (script_len, varint_len) = decode_varint(&data[offset..])?;
253 offset += varint_len;
254
255 let script_pubkey_end = checked_slice_end(offset, script_len)?;
256 if data.len() < script_pubkey_end {
257 return Err(ConsensusError::Serialization(Cow::Owned(
258 TransactionParseError::InsufficientBytes.to_string(),
259 )));
260 }
261 let script_pubkey = data[offset..script_pubkey_end].to_vec();
262 offset = script_pubkey_end;
263
264 outputs.push(TransactionOutput {
265 value,
266 script_pubkey,
267 });
268 }
269
270 if is_segwit {
271 for _ in 0..input_count {
272 let (stack_count, varint_len) = decode_varint(&data[offset..])?;
273 offset += varint_len;
274 for _ in 0..stack_count {
275 let (item_len, varint_len) = decode_varint(&data[offset..])?;
276 offset += varint_len;
277 let item_end = checked_slice_end(offset, item_len)?;
278 if data.len() < item_end {
279 return Err(ConsensusError::Serialization(Cow::Owned(
280 TransactionParseError::InsufficientBytes.to_string(),
281 )));
282 }
283 offset = item_end;
284 }
285 }
286 }
287
288 if data.len() < offset + 4 {
289 return Err(ConsensusError::Serialization(Cow::Owned(
290 TransactionParseError::InsufficientBytes.to_string(),
291 )));
292 }
293 let lock_time = u32::from_le_bytes([
294 data[offset],
295 data[offset + 1],
296 data[offset + 2],
297 data[offset + 3],
298 ]) as u64;
299
300 Ok(Transaction {
301 version,
302 inputs,
303 outputs,
304 lock_time,
305 })
306}
307
308pub fn deserialize_transaction_with_offset(data: &[u8]) -> Result<(Transaction, usize)> {
310 let (tx, _witnesses, bytes_consumed) = deserialize_transaction_with_witness(data)?;
311 Ok((tx, bytes_consumed))
312}
313
314pub fn deserialize_transaction_with_witness(
316 data: &[u8],
317) -> Result<(Transaction, Vec<Witness>, usize)> {
318 let mut offset = 0;
319
320 if data.len() < offset + 4 {
321 return Err(ConsensusError::Serialization(Cow::Owned(
322 TransactionParseError::InsufficientBytes.to_string(),
323 )));
324 }
325 let version = i32::from_le_bytes([
326 data[offset],
327 data[offset + 1],
328 data[offset + 2],
329 data[offset + 3],
330 ]) as u64;
331 offset += 4;
332
333 let is_segwit = data.len() >= offset + 2 && data[offset] == 0x00 && data[offset + 1] == 0x01;
334
335 if is_segwit {
336 offset += 2;
337 }
338
339 let (input_count, varint_len) = decode_varint(&data[offset..])?;
340 offset += varint_len;
341
342 if input_count > 1000000 {
343 return Err(ConsensusError::Serialization(Cow::Owned(
344 TransactionParseError::InvalidInputCount.to_string(),
345 )));
346 }
347
348 #[cfg(feature = "production")]
349 let mut inputs = SmallVec::<[TransactionInput; 2]>::new();
350 #[cfg(not(feature = "production"))]
351 let mut inputs = Vec::new();
352
353 for _ in 0..input_count {
354 if data.len() < offset + 36 {
355 return Err(ConsensusError::Serialization(Cow::Owned(
356 TransactionParseError::InsufficientBytes.to_string(),
357 )));
358 }
359 let mut hash = [0u8; 32];
360 hash.copy_from_slice(&data[offset..offset + 32]);
361 offset += 32;
362
363 let index = u32::from_le_bytes([
364 data[offset],
365 data[offset + 1],
366 data[offset + 2],
367 data[offset + 3],
368 ]);
369 offset += 4;
370
371 let (script_len, varint_len) = decode_varint(&data[offset..])?;
372 offset += varint_len;
373
374 let script_sig_end = checked_slice_end(offset, script_len)?;
375 if data.len() < script_sig_end {
376 return Err(ConsensusError::Serialization(Cow::Owned(
377 TransactionParseError::InsufficientBytes.to_string(),
378 )));
379 }
380 let script_sig = data[offset..script_sig_end].to_vec();
381 offset = script_sig_end;
382
383 if data.len() < offset + 4 {
384 return Err(ConsensusError::Serialization(Cow::Owned(
385 TransactionParseError::InsufficientBytes.to_string(),
386 )));
387 }
388 let sequence = u32::from_le_bytes([
389 data[offset],
390 data[offset + 1],
391 data[offset + 2],
392 data[offset + 3],
393 ]) as u64;
394 offset += 4;
395
396 inputs.push(TransactionInput {
397 prevout: OutPoint { hash, index },
398 script_sig,
399 sequence,
400 });
401 }
402
403 let (output_count, varint_len) = decode_varint(&data[offset..])?;
404 offset += varint_len;
405
406 if output_count > 1000000 {
407 return Err(ConsensusError::Serialization(Cow::Owned(
408 TransactionParseError::InvalidOutputCount.to_string(),
409 )));
410 }
411
412 #[cfg(feature = "production")]
413 let mut outputs = SmallVec::<[TransactionOutput; 2]>::new();
414 #[cfg(not(feature = "production"))]
415 let mut outputs = Vec::new();
416
417 for _ in 0..output_count {
418 if data.len() < offset + 8 {
419 return Err(ConsensusError::Serialization(Cow::Owned(
420 TransactionParseError::InsufficientBytes.to_string(),
421 )));
422 }
423 let value = i64::from_le_bytes([
424 data[offset],
425 data[offset + 1],
426 data[offset + 2],
427 data[offset + 3],
428 data[offset + 4],
429 data[offset + 5],
430 data[offset + 6],
431 data[offset + 7],
432 ]);
433 offset += 8;
434
435 let (script_len, varint_len) = decode_varint(&data[offset..])?;
436 offset += varint_len;
437
438 let script_pubkey_end = checked_slice_end(offset, script_len)?;
439 if data.len() < script_pubkey_end {
440 return Err(ConsensusError::Serialization(Cow::Owned(
441 TransactionParseError::InsufficientBytes.to_string(),
442 )));
443 }
444 let script_pubkey = data[offset..script_pubkey_end].to_vec();
445 offset = script_pubkey_end;
446
447 outputs.push(TransactionOutput {
448 value,
449 script_pubkey,
450 });
451 }
452
453 let mut all_witnesses: Vec<Witness> = Vec::new();
454 if is_segwit {
455 for _ in 0..input_count {
456 let (stack_count, varint_len) = decode_varint(&data[offset..])?;
457 offset += varint_len;
458
459 let mut witness_stack: Witness = Vec::new();
460 for _ in 0..stack_count {
461 let (item_len, varint_len) = decode_varint(&data[offset..])?;
462 offset += varint_len;
463
464 let item_end = checked_slice_end(offset, item_len)?;
465 if data.len() < item_end {
466 return Err(ConsensusError::Serialization(Cow::Owned(
467 TransactionParseError::InsufficientBytes.to_string(),
468 )));
469 }
470 witness_stack.push(data[offset..item_end].to_vec());
471 offset = item_end;
472 }
473 all_witnesses.push(witness_stack);
474 }
475 } else {
476 for _ in 0..input_count {
477 all_witnesses.push(Vec::new());
478 }
479 }
480
481 if data.len() < offset + 4 {
482 return Err(ConsensusError::Serialization(Cow::Owned(
483 TransactionParseError::InsufficientBytes.to_string(),
484 )));
485 }
486 let lock_time = u32::from_le_bytes([
487 data[offset],
488 data[offset + 1],
489 data[offset + 2],
490 data[offset + 3],
491 ]) as u64;
492 offset += 4;
493
494 let tx = Transaction {
495 version,
496 inputs,
497 outputs,
498 lock_time,
499 };
500
501 Ok((tx, all_witnesses, offset))
502}
503
504#[cfg(test)]
505mod tests {
506 use super::*;
507
508 #[test]
509 fn test_serialize_deserialize_round_trip() {
510 let tx = Transaction {
511 version: 1,
512 inputs: crate::tx_inputs![TransactionInput {
513 prevout: OutPoint {
514 hash: [1; 32],
515 index: 0
516 },
517 script_sig: vec![0x51],
518 sequence: 0xffffffff,
519 }],
520 outputs: crate::tx_outputs![TransactionOutput {
521 value: 5000000000,
522 script_pubkey: vec![0x51],
523 }],
524 lock_time: 0,
525 };
526
527 let serialized = serialize_transaction(&tx);
528 let deserialized = deserialize_transaction(&serialized).unwrap();
529
530 assert_eq!(deserialized.version, tx.version);
531 assert_eq!(deserialized.inputs.len(), tx.inputs.len());
532 assert_eq!(deserialized.outputs.len(), tx.outputs.len());
533 assert_eq!(deserialized.lock_time, tx.lock_time);
534 }
535}