Skip to content

Commit 1f43192

Browse files
committed
parse compact size used
1 parent b702233 commit 1f43192

File tree

1 file changed

+64
-78
lines changed

1 file changed

+64
-78
lines changed

bitcoinutils/transactions.py

Lines changed: 64 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -143,53 +143,48 @@ def __repr__(self):
143143
@staticmethod
144144
def from_raw(txinputrawhex: str, cursor: int = 0, has_segwit: bool = False):
145145
"""
146-
Imports a TxInput from a Transaction's hexadecimal data using struct for parsing.
146+
Imports a TxInput from a Transaction's hexadecimal data
147147
148-
Args:
149-
txinputrawhex (str): The hexadecimal raw string of the Transaction.
150-
cursor (int): The position at which the algorithm will start to read the data.
151-
has_segwit (bool): Indicates if the Tx Input is SegWit enabled.
152-
153-
Returns:
154-
tuple: (TxInput object, new cursor position)
155-
156-
Raises:
157-
Exception: If the transaction hash or script is malformed.
148+
Attributes
149+
----------
150+
txinputrawhex : string (hex)
151+
The hexadecimal raw string of the Transaction
152+
cursor : int
153+
The cursor of which the algorithm will start to read the data
154+
has_segwit : boolean
155+
Is the Tx Input segwit or not
158156
"""
159157
txinputraw = h_to_b(txinputrawhex)
160158

161-
# Unpack transaction ID (hash) and output index
162-
txid_format = "<32s4s"
163-
txid, vout = struct.unpack_from(txid_format, txinputraw, cursor)
164-
txid = txid[::-1].hex() # Reverse to match usual hexadecimal order
165-
cursor += 36 # Advance cursor by 32 bytes for txid and 4 bytes for vout
159+
# Unpack transaction ID (hash) in bytes and output index
160+
txid, vout = struct.unpack_from('<32sI', txinputraw, cursor)
161+
txid = txid[::-1] # Reverse to match usual hexadecimal order
162+
cursor += 36 # 32 bytes for txid and 4 bytes for vout
166163

167164
# Read the unlocking script size using parse_compact_size
168-
unlocking_script_size, size = vi_to_int(txinputraw[cursor : cursor + 8])
165+
unlocking_script_size, size = parse_compact_size(txinputraw[cursor:])
169166
cursor += size
170167

171-
# Read the unlocking script, keeping it in bytes
172-
script_format = f"{unlocking_script_size}s"
173-
unlocking_script, = struct.unpack_from(script_format, txinputraw, cursor)
168+
# Read the unlocking script in bytes
169+
unlocking_script = struct.unpack_from(f'{unlocking_script_size}s', txinputraw, cursor)[0]
174170
cursor += unlocking_script_size
175171

176-
# Read the sequence number, maintaining byte format
177-
sequence_format = "<4s"
178-
sequence, = struct.unpack_from(sequence_format, txinputraw, cursor)
172+
# Read the sequence number in bytes
173+
sequence, = struct.unpack_from('<4s', txinputraw, cursor)
179174
cursor += 4
180175

181-
# If coinbase input, handle differently
182-
if txid == 64 * '0':
176+
# If coinbase input (utxo will be all zeros), handle script differently
177+
if txid.hex() == '00' * 32:
183178
script_sig = Script([unlocking_script.hex()]) # Treat as single element for coinbase
184179
else:
185180
script_sig = Script.from_raw(unlocking_script.hex(), has_segwit=has_segwit)
186181

187182
# Create the TxInput instance
188183
tx_input = TxInput(
189-
txid=txid,
190-
txout_index=int.from_bytes(vout, 'little'), # Convert vout from bytes to integer when needed
184+
txid=txid.hex(),
185+
txout_index=vout,
191186
script_sig=script_sig,
192-
sequence=sequence # Keep sequence as bytes
187+
sequence=sequence
193188
)
194189

195190
return tx_input, cursor
@@ -293,43 +288,42 @@ def to_bytes(self) -> bytes:
293288
@staticmethod
294289
def from_raw(txoutputrawhex: str, cursor: int = 0, has_segwit: bool = False):
295290
"""
296-
Imports a TxOutput from a Transaction's hexadecimal data using struct for parsing.
291+
Imports a TxOutput from a Transaction's hexadecimal data
297292
298-
Args:
299-
txoutputrawhex (str): The hexadecimal raw string of the Transaction.
300-
cursor (int): The position at which the algorithm will start to read the data.
301-
has_segwit (bool): Indicates if the Tx Output is SegWit enabled.
302-
303-
Returns:
304-
tuple: (TxOutput object, new cursor position)
305-
306-
Raises:
307-
Exception: If the amount or script is malformed.
293+
Attributes
294+
----------
295+
txoutputrawhex : string (hex)
296+
The hexadecimal raw string of the Transaction
297+
cursor : int
298+
The cursor of which the algorithm will start to read the data
299+
has_segwit : boolean
300+
Is the Tx Output segwit or not
308301
"""
309302
txoutputraw = h_to_b(txoutputrawhex)
310303

311-
# Unpack the output value (amount) keeping it in bytes
312-
amount_format = "<8s" # Little-endian unsigned long long (8 bytes)
313-
amount_bytes, = struct.unpack_from(amount_format, txoutputraw, cursor)
304+
# Unpack the amount of the TxOutput directly in bytes
305+
amount_format = "<Q" # Little-endian unsigned long long (8 bytes)
306+
amount, = struct.unpack_from(amount_format, txoutputraw, cursor)
314307
cursor += struct.calcsize(amount_format)
315308

316-
# Read the locking script size using parse_compact_size, assuming it returns bytes length in int
317-
lock_script_size, size = vi_to_int(txoutputraw[cursor : cursor + 9])
309+
# Read the locking script size using parse_compact_size
310+
lock_script_size, size = parse_compact_size(txoutputraw[cursor:])
318311
cursor += size
319312

320-
# Read the locking script, maintaining it in bytes
313+
# Read the locking script
321314
script_format = f"{lock_script_size}s"
322315
lock_script, = struct.unpack_from(script_format, txoutputraw, cursor)
323316
cursor += lock_script_size
324317

325318
# Create the TxOutput instance
326319
tx_output = TxOutput(
327-
amount=int.from_bytes(amount_bytes, 'little'), # Convert amount from bytes to integer when needed
320+
amount=amount,
328321
script_pubkey=Script.from_raw(lock_script.hex(), has_segwit=has_segwit)
329322
)
330323

331324
return tx_output, cursor
332325

326+
333327
def __str__(self) -> str:
334328
return str({"amount": self.amount, "script_pubkey": self.script_pubkey})
335329

@@ -534,34 +528,27 @@ def __init__(
534528
@staticmethod
535529
def from_raw(rawtxhex: str):
536530
"""
537-
Imports a Transaction from hexadecimal data using struct for parsing.
538-
539-
Args:
540-
rawtxhex (str): The hexadecimal raw string of the Transaction.
531+
Imports a Transaction from hexadecimal data.
541532
542-
Returns:
543-
Transaction: A fully parsed Transaction object.
544-
545-
Raises:
546-
Exception: If the transaction data is malformed.
533+
Attributes
534+
----------
535+
rawtxhex : string (hex)
536+
The hexadecimal raw string of the Transaction.
547537
"""
548538
rawtx = h_to_b(rawtxhex)
549-
cursor = 0
550539

551-
# Unpack version (4 bytes) and keep it in bytes
552-
version, = struct.unpack_from('<4s', rawtx, cursor)
553-
cursor += 4
540+
# Read version (4 bytes)
541+
version = rawtx[0:4]
542+
cursor = 4
554543

555544
# Detect and handle SegWit
556-
flag = None
557545
has_segwit = False
558-
if rawtx[cursor] == 0x00 and rawtx[cursor + 1] == 0x01:
559-
flag = rawtx[cursor + 1]
546+
if rawtx[cursor:cursor + 2] == b'\x00\x01':
560547
has_segwit = True
561-
cursor += 2 # Skip past the marker and flag bytes
548+
cursor += 2 # Skipping past the marker and flag bytes
562549

563550
# Read the number of inputs
564-
n_inputs, size = vi_to_int(rawtx[cursor : cursor + 9])
551+
n_inputs, size = parse_compact_size(rawtx[cursor:])
565552
cursor += size
566553
inputs = []
567554

@@ -570,8 +557,8 @@ def from_raw(rawtxhex: str):
570557
inp, cursor = TxInput.from_raw(rawtx.hex(), cursor, has_segwit)
571558
inputs.append(inp)
572559

573-
# Read the number of outputs
574-
n_outputs, size = vi_to_int(rawtx[cursor : cursor + 9])
560+
# Read the number of outputs using parse_compact_size
561+
n_outputs, size = parse_compact_size(rawtx[cursor:])
575562
cursor += size
576563
outputs = []
577564

@@ -584,32 +571,31 @@ def from_raw(rawtxhex: str):
584571
witnesses = []
585572
if has_segwit:
586573
for _ in range(n_inputs):
587-
n_items, size = vi_to_int(rawtx[cursor : cursor + 9])
574+
n_items, size = parse_compact_size(rawtx[cursor:])
588575
cursor += size
589-
witness_stack = []
590-
for __ in range(n_items):
591-
item_size, size = vi_to_int(rawtx[cursor : cursor + 9])
576+
witnesses_tmp = []
577+
for _ in range(n_items):
578+
item_size, size = parse_compact_size(rawtx[cursor:])
592579
cursor += size
593580
witness_data = rawtx[cursor:cursor + item_size]
594581
cursor += item_size
595-
witness_stack.append(witness_data.hex())
596-
witnesses.append(TxWitnessInput(stack=witness_stack))
582+
witnesses_tmp.append(witness_data.hex())
583+
if witnesses_tmp:
584+
witnesses.append(TxWitnessInput(stack=witnesses_tmp))
597585

598-
# Unpack locktime (4 bytes) and keep it in bytes
599-
locktime, = struct.unpack_from('<4s', rawtx, cursor)
600-
cursor += 4
586+
# Read locktime (4 bytes)
587+
locktime = rawtx[cursor:cursor + 4]
601588

602-
# Construct and return the Transaction object
589+
#Returning the Transaction object
603590
return Transaction(
604591
inputs=inputs,
605592
outputs=outputs,
606593
version=version,
607594
locktime=locktime,
608595
has_segwit=has_segwit,
609-
witnesses=witnesses
596+
witnesses=witnesses,
610597
)
611598

612-
613599
def __str__(self) -> str:
614600
return str(
615601
{

0 commit comments

Comments
 (0)