Skip to content

Commit b702233

Browse files
committed
all parsing in bytes
1 parent e26d502 commit b702233

2 files changed

Lines changed: 50 additions & 43 deletions

File tree

bitcoinutils/transactions.py

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -159,37 +159,37 @@ def from_raw(txinputrawhex: str, cursor: int = 0, has_segwit: bool = False):
159159
txinputraw = h_to_b(txinputrawhex)
160160

161161
# Unpack transaction ID (hash) and output index
162-
txid_format = "<32sI"
162+
txid_format = "<32s4s"
163163
txid, vout = struct.unpack_from(txid_format, txinputraw, cursor)
164164
txid = txid[::-1].hex() # Reverse to match usual hexadecimal order
165-
cursor += struct.calcsize(txid_format)
165+
cursor += 36 # Advance cursor by 32 bytes for txid and 4 bytes for vout
166166

167167
# Read the unlocking script size using parse_compact_size
168-
unlocking_script_size, size = parse_compact_size(txinputraw, cursor)
168+
unlocking_script_size, size = vi_to_int(txinputraw[cursor : cursor + 8])
169169
cursor += size
170170

171-
# Read the unlocking script
171+
# Read the unlocking script, keeping it in bytes
172172
script_format = f"{unlocking_script_size}s"
173173
unlocking_script, = struct.unpack_from(script_format, txinputraw, cursor)
174174
cursor += unlocking_script_size
175175

176-
# Read the sequence number
177-
sequence_format = "<I"
176+
# Read the sequence number, maintaining byte format
177+
sequence_format = "<4s"
178178
sequence, = struct.unpack_from(sequence_format, txinputraw, cursor)
179-
cursor += struct.calcsize(sequence_format)
179+
cursor += 4
180180

181181
# If coinbase input, handle differently
182-
if txid == 64 * "0":
182+
if txid == 64 * '0':
183183
script_sig = Script([unlocking_script.hex()]) # Treat as single element for coinbase
184184
else:
185185
script_sig = Script.from_raw(unlocking_script.hex(), has_segwit=has_segwit)
186186

187187
# Create the TxInput instance
188188
tx_input = TxInput(
189189
txid=txid,
190-
txout_index=vout,
190+
txout_index=int.from_bytes(vout, 'little'), # Convert vout from bytes to integer when needed
191191
script_sig=script_sig,
192-
sequence=sequence
192+
sequence=sequence # Keep sequence as bytes
193193
)
194194

195195
return tx_input, cursor
@@ -293,36 +293,38 @@ def to_bytes(self) -> bytes:
293293
@staticmethod
294294
def from_raw(txoutputrawhex: str, cursor: int = 0, has_segwit: bool = False):
295295
"""
296-
Imports a TxOutput from a Transaction's hexadecimal data
296+
Imports a TxOutput from a Transaction's hexadecimal data using struct for parsing.
297297
298-
Attributes
299-
----------
300-
txoutputrawhex : string (hex)
301-
The hexadecimal raw string of the Transaction
302-
cursor : int
303-
The cursor of which the algorithm will start to read the data
304-
has_segwit : boolean
305-
Is the Tx Output segwit or not
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.
306308
"""
307309
txoutputraw = h_to_b(txoutputrawhex)
308310

309-
# Unpack the output value (amount)
310-
amount_format = "<Q" # Little-endian unsigned long long (8 bytes)
311-
amount, = struct.unpack_from(amount_format, txoutputraw, cursor)
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)
312314
cursor += struct.calcsize(amount_format)
313315

314-
# Read the locking script size using parse_compact_size
315-
lock_script_size, size = parse_compact_size(txoutputraw, cursor)
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])
316318
cursor += size
317319

318-
# Read the locking script
320+
# Read the locking script, maintaining it in bytes
319321
script_format = f"{lock_script_size}s"
320322
lock_script, = struct.unpack_from(script_format, txoutputraw, cursor)
321323
cursor += lock_script_size
322324

323325
# Create the TxOutput instance
324326
tx_output = TxOutput(
325-
amount=amount,
327+
amount=int.from_bytes(amount_bytes, 'little'), # Convert amount from bytes to integer when needed
326328
script_pubkey=Script.from_raw(lock_script.hex(), has_segwit=has_segwit)
327329
)
328330

@@ -532,18 +534,22 @@ def __init__(
532534
@staticmethod
533535
def from_raw(rawtxhex: str):
534536
"""
535-
Imports a Transaction from hexadecimal data
537+
Imports a Transaction from hexadecimal data using struct for parsing.
536538
537-
Attributes
538-
----------
539-
rawtxhex : string (hex)
540-
The hexadecimal raw string of the Transaction
539+
Args:
540+
rawtxhex (str): The hexadecimal raw string of the Transaction.
541+
542+
Returns:
543+
Transaction: A fully parsed Transaction object.
544+
545+
Raises:
546+
Exception: If the transaction data is malformed.
541547
"""
542548
rawtx = h_to_b(rawtxhex)
543549
cursor = 0
544550

545-
# Unpack version (4 bytes)
546-
version, = struct.unpack_from('<I', rawtx, cursor)
551+
# Unpack version (4 bytes) and keep it in bytes
552+
version, = struct.unpack_from('<4s', rawtx, cursor)
547553
cursor += 4
548554

549555
# Detect and handle SegWit
@@ -555,54 +561,55 @@ def from_raw(rawtxhex: str):
555561
cursor += 2 # Skip past the marker and flag bytes
556562

557563
# Read the number of inputs
558-
n_inputs, size = parse_compact_size(rawtx, cursor)
564+
n_inputs, size = vi_to_int(rawtx[cursor : cursor + 9])
559565
cursor += size
560566
inputs = []
561567

562568
# Read inputs
563569
for _ in range(n_inputs):
564-
inp, cursor = TxInput.from_raw(rawtxhex, cursor, has_segwit)
570+
inp, cursor = TxInput.from_raw(rawtx.hex(), cursor, has_segwit)
565571
inputs.append(inp)
566572

567573
# Read the number of outputs
568-
n_outputs, size = parse_compact_size(rawtx, cursor)
574+
n_outputs, size = vi_to_int(rawtx[cursor : cursor + 9])
569575
cursor += size
570576
outputs = []
571577

572578
# Read outputs
573579
for _ in range(n_outputs):
574-
output, cursor = TxOutput.from_raw(rawtxhex, cursor, has_segwit)
580+
output, cursor = TxOutput.from_raw(rawtx.hex(), cursor, has_segwit)
575581
outputs.append(output)
576582

577583
# Handle witnesses if SegWit is enabled
578584
witnesses = []
579585
if has_segwit:
580586
for _ in range(n_inputs):
581-
n_items, size = parse_compact_size(rawtx, cursor)
587+
n_items, size = vi_to_int(rawtx[cursor : cursor + 9])
582588
cursor += size
583589
witness_stack = []
584590
for __ in range(n_items):
585-
item_size, size = parse_compact_size(rawtx, cursor)
591+
item_size, size = vi_to_int(rawtx[cursor : cursor + 9])
586592
cursor += size
587593
witness_data = rawtx[cursor:cursor + item_size]
588594
cursor += item_size
589595
witness_stack.append(witness_data.hex())
590596
witnesses.append(TxWitnessInput(stack=witness_stack))
591597

592-
# Unpack locktime (4 bytes)
593-
locktime, = struct.unpack_from('<I', rawtx, cursor)
598+
# Unpack locktime (4 bytes) and keep it in bytes
599+
locktime, = struct.unpack_from('<4s', rawtx, cursor)
594600
cursor += 4
595601

596602
# Construct and return the Transaction object
597603
return Transaction(
598-
version=version,
599604
inputs=inputs,
600605
outputs=outputs,
606+
version=version,
601607
locktime=locktime,
602608
has_segwit=has_segwit,
603609
witnesses=witnesses
604610
)
605611

612+
606613
def __str__(self) -> str:
607614
return str(
608615
{

bitcoinutils/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ def parse_compact_size(data: bytes) -> tuple:
220220
return (struct.unpack('<I', data[1:5])[0], 5)
221221
elif first_byte == 0xff:
222222
return (struct.unpack('<Q', data[1:9])[0], 9)
223-
223+
224224
def get_transaction_length(data: bytes) -> int:
225225
"""
226226
Return length of a transaction, including handling for SegWit transactions.

0 commit comments

Comments
 (0)