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