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 let sequence = u32::from_le_bytes([
201 data[offset],
202 data[offset + 1],
203 data[offset + 2],
204 data[offset + 3],
205 ]) as u64;
206 offset += 4;
207
208 inputs.push(TransactionInput {
209 prevout: OutPoint { hash, index },
210 script_sig,
211 sequence,
212 });
213 }
214
215 let (output_count, varint_len) = decode_varint(&data[offset..])?;
216 offset += varint_len;
217
218 if output_count > 1000000 {
219 return Err(ConsensusError::Serialization(Cow::Owned(
220 TransactionParseError::InvalidOutputCount.to_string(),
221 )));
222 }
223
224 #[cfg(feature = "production")]
225 let mut outputs = SmallVec::<[TransactionOutput; 2]>::new();
226 #[cfg(not(feature = "production"))]
227 let mut outputs = Vec::new();
228
229 for _ in 0..output_count {
230 if data.len() < offset + 8 {
231 return Err(ConsensusError::Serialization(Cow::Owned(
232 TransactionParseError::InsufficientBytes.to_string(),
233 )));
234 }
235 let value = i64::from_le_bytes([
236 data[offset],
237 data[offset + 1],
238 data[offset + 2],
239 data[offset + 3],
240 data[offset + 4],
241 data[offset + 5],
242 data[offset + 6],
243 data[offset + 7],
244 ]);
245 offset += 8;
246
247 let (script_len, varint_len) = decode_varint(&data[offset..])?;
248 offset += varint_len;
249
250 let script_pubkey_end = checked_slice_end(offset, script_len)?;
251 if data.len() < script_pubkey_end {
252 return Err(ConsensusError::Serialization(Cow::Owned(
253 TransactionParseError::InsufficientBytes.to_string(),
254 )));
255 }
256 let script_pubkey = data[offset..script_pubkey_end].to_vec();
257 offset = script_pubkey_end;
258
259 outputs.push(TransactionOutput {
260 value,
261 script_pubkey,
262 });
263 }
264
265 if is_segwit {
266 for _ in 0..input_count {
267 let (stack_count, varint_len) = decode_varint(&data[offset..])?;
268 offset += varint_len;
269 for _ in 0..stack_count {
270 let (item_len, varint_len) = decode_varint(&data[offset..])?;
271 offset += varint_len;
272 let item_end = checked_slice_end(offset, item_len)?;
273 if data.len() < item_end {
274 return Err(ConsensusError::Serialization(Cow::Owned(
275 TransactionParseError::InsufficientBytes.to_string(),
276 )));
277 }
278 offset = item_end;
279 }
280 }
281 }
282
283 if data.len() < offset + 4 {
284 return Err(ConsensusError::Serialization(Cow::Owned(
285 TransactionParseError::InsufficientBytes.to_string(),
286 )));
287 }
288 let lock_time = u32::from_le_bytes([
289 data[offset],
290 data[offset + 1],
291 data[offset + 2],
292 data[offset + 3],
293 ]) as u64;
294
295 Ok(Transaction {
296 version,
297 inputs,
298 outputs,
299 lock_time,
300 })
301}
302
303pub fn deserialize_transaction_with_offset(data: &[u8]) -> Result<(Transaction, usize)> {
305 let (tx, _witnesses, bytes_consumed) = deserialize_transaction_with_witness(data)?;
306 Ok((tx, bytes_consumed))
307}
308
309pub fn deserialize_transaction_with_witness(
311 data: &[u8],
312) -> Result<(Transaction, Vec<Witness>, usize)> {
313 let mut offset = 0;
314
315 if data.len() < offset + 4 {
316 return Err(ConsensusError::Serialization(Cow::Owned(
317 TransactionParseError::InsufficientBytes.to_string(),
318 )));
319 }
320 let version = i32::from_le_bytes([
321 data[offset],
322 data[offset + 1],
323 data[offset + 2],
324 data[offset + 3],
325 ]) as u64;
326 offset += 4;
327
328 let is_segwit = data.len() >= offset + 2 && data[offset] == 0x00 && data[offset + 1] == 0x01;
329
330 if is_segwit {
331 offset += 2;
332 }
333
334 let (input_count, varint_len) = decode_varint(&data[offset..])?;
335 offset += varint_len;
336
337 if input_count > 1000000 {
338 return Err(ConsensusError::Serialization(Cow::Owned(
339 TransactionParseError::InvalidInputCount.to_string(),
340 )));
341 }
342
343 #[cfg(feature = "production")]
344 let mut inputs = SmallVec::<[TransactionInput; 2]>::new();
345 #[cfg(not(feature = "production"))]
346 let mut inputs = Vec::new();
347
348 for _ in 0..input_count {
349 if data.len() < offset + 36 {
350 return Err(ConsensusError::Serialization(Cow::Owned(
351 TransactionParseError::InsufficientBytes.to_string(),
352 )));
353 }
354 let mut hash = [0u8; 32];
355 hash.copy_from_slice(&data[offset..offset + 32]);
356 offset += 32;
357
358 let index = u32::from_le_bytes([
359 data[offset],
360 data[offset + 1],
361 data[offset + 2],
362 data[offset + 3],
363 ]);
364 offset += 4;
365
366 let (script_len, varint_len) = decode_varint(&data[offset..])?;
367 offset += varint_len;
368
369 let script_sig_end = checked_slice_end(offset, script_len)?;
370 if data.len() < script_sig_end {
371 return Err(ConsensusError::Serialization(Cow::Owned(
372 TransactionParseError::InsufficientBytes.to_string(),
373 )));
374 }
375 let script_sig = data[offset..script_sig_end].to_vec();
376 offset = script_sig_end;
377
378 let sequence = u32::from_le_bytes([
379 data[offset],
380 data[offset + 1],
381 data[offset + 2],
382 data[offset + 3],
383 ]) as u64;
384 offset += 4;
385
386 inputs.push(TransactionInput {
387 prevout: OutPoint { hash, index },
388 script_sig,
389 sequence,
390 });
391 }
392
393 let (output_count, varint_len) = decode_varint(&data[offset..])?;
394 offset += varint_len;
395
396 if output_count > 1000000 {
397 return Err(ConsensusError::Serialization(Cow::Owned(
398 TransactionParseError::InvalidOutputCount.to_string(),
399 )));
400 }
401
402 #[cfg(feature = "production")]
403 let mut outputs = SmallVec::<[TransactionOutput; 2]>::new();
404 #[cfg(not(feature = "production"))]
405 let mut outputs = Vec::new();
406
407 for _ in 0..output_count {
408 if data.len() < offset + 8 {
409 return Err(ConsensusError::Serialization(Cow::Owned(
410 TransactionParseError::InsufficientBytes.to_string(),
411 )));
412 }
413 let value = i64::from_le_bytes([
414 data[offset],
415 data[offset + 1],
416 data[offset + 2],
417 data[offset + 3],
418 data[offset + 4],
419 data[offset + 5],
420 data[offset + 6],
421 data[offset + 7],
422 ]);
423 offset += 8;
424
425 let (script_len, varint_len) = decode_varint(&data[offset..])?;
426 offset += varint_len;
427
428 let script_pubkey_end = checked_slice_end(offset, script_len)?;
429 if data.len() < script_pubkey_end {
430 return Err(ConsensusError::Serialization(Cow::Owned(
431 TransactionParseError::InsufficientBytes.to_string(),
432 )));
433 }
434 let script_pubkey = data[offset..script_pubkey_end].to_vec();
435 offset = script_pubkey_end;
436
437 outputs.push(TransactionOutput {
438 value,
439 script_pubkey,
440 });
441 }
442
443 let mut all_witnesses: Vec<Witness> = Vec::new();
444 if is_segwit {
445 for _ in 0..input_count {
446 let (stack_count, varint_len) = decode_varint(&data[offset..])?;
447 offset += varint_len;
448
449 let mut witness_stack: Witness = Vec::new();
450 for _ in 0..stack_count {
451 let (item_len, varint_len) = decode_varint(&data[offset..])?;
452 offset += varint_len;
453
454 let item_end = checked_slice_end(offset, item_len)?;
455 if data.len() < item_end {
456 return Err(ConsensusError::Serialization(Cow::Owned(
457 TransactionParseError::InsufficientBytes.to_string(),
458 )));
459 }
460 witness_stack.push(data[offset..item_end].to_vec());
461 offset = item_end;
462 }
463 all_witnesses.push(witness_stack);
464 }
465 } else {
466 for _ in 0..input_count {
467 all_witnesses.push(Vec::new());
468 }
469 }
470
471 if data.len() < offset + 4 {
472 return Err(ConsensusError::Serialization(Cow::Owned(
473 TransactionParseError::InsufficientBytes.to_string(),
474 )));
475 }
476 let lock_time = u32::from_le_bytes([
477 data[offset],
478 data[offset + 1],
479 data[offset + 2],
480 data[offset + 3],
481 ]) as u64;
482 offset += 4;
483
484 let tx = Transaction {
485 version,
486 inputs,
487 outputs,
488 lock_time,
489 };
490
491 Ok((tx, all_witnesses, offset))
492}
493
494#[cfg(test)]
495mod tests {
496 use super::*;
497
498 #[test]
499 fn test_serialize_deserialize_round_trip() {
500 let tx = Transaction {
501 version: 1,
502 inputs: crate::tx_inputs![TransactionInput {
503 prevout: OutPoint {
504 hash: [1; 32],
505 index: 0
506 },
507 script_sig: vec![0x51],
508 sequence: 0xffffffff,
509 }],
510 outputs: crate::tx_outputs![TransactionOutput {
511 value: 5000000000,
512 script_pubkey: vec![0x51],
513 }],
514 lock_time: 0,
515 };
516
517 let serialized = serialize_transaction(&tx);
518 let deserialized = deserialize_transaction(&serialized).unwrap();
519
520 assert_eq!(deserialized.version, tx.version);
521 assert_eq!(deserialized.inputs.len(), tx.inputs.len());
522 assert_eq!(deserialized.outputs.len(), tx.outputs.len());
523 assert_eq!(deserialized.lock_time, tx.lock_time);
524 }
525}