Skip to content

Commit 2ebc6df

Browse files
committed
Updated transaction.py's from_raw to use struct
Merge branch 'guptamukund22-code'
2 parents 7b5911c + e26d502 commit 2ebc6df

1 file changed

Lines changed: 99 additions & 105 deletions

File tree

bitcoinutils/transactions.py

Lines changed: 99 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
prepend_compact_size,
4141
h_to_b,
4242
b_to_h,
43+
parse_compact_size,
4344
)
4445

4546

@@ -142,64 +143,57 @@ def __repr__(self):
142143
@staticmethod
143144
def from_raw(txinputrawhex: str, cursor: int = 0, has_segwit: bool = False):
144145
"""
145-
Imports a TxInput from a Transaction's hexadecimal data
146+
Imports a TxInput from a Transaction's hexadecimal data using struct for parsing.
146147
147-
Attributes
148-
----------
149-
txinputrawhex : string (hex)
150-
The hexadecimal raw string of the Transaction
151-
cursor : int
152-
The cursor of which the algorithm will start to read the data
153-
has_segwit : boolean
154-
Is the Tx Input segwit or not
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.
155158
"""
156159
txinputraw = h_to_b(txinputrawhex)
157160

158-
# read the 32 bytes of TxInput ID
159-
inp_hash = txinputraw[cursor : cursor + 32][::-1]
161+
# Unpack transaction ID (hash) and output index
162+
txid_format = "<32sI"
163+
txid, vout = struct.unpack_from(txid_format, txinputraw, cursor)
164+
txid = txid[::-1].hex() # Reverse to match usual hexadecimal order
165+
cursor += struct.calcsize(txid_format)
160166

161-
if not len(inp_hash):
162-
raise Exception(
163-
"Input transaction hash not found. Probably malformed raw transaction"
164-
)
165-
output_n = txinputraw[cursor + 32 : cursor + 36][::-1]
166-
cursor += 36
167-
168-
# read the size (bytes length) of the integer representing the size of
169-
# the Script's raw data and the size of the Script's raw data
170-
unlocking_script_size, size = vi_to_int(txinputraw[cursor : cursor + 8])
167+
# Read the unlocking script size using parse_compact_size
168+
unlocking_script_size, size = parse_compact_size(txinputraw, cursor)
171169
cursor += size
172-
unlocking_script = txinputraw[cursor : cursor + unlocking_script_size]
170+
171+
# Read the unlocking script
172+
script_format = f"{unlocking_script_size}s"
173+
unlocking_script, = struct.unpack_from(script_format, txinputraw, cursor)
173174
cursor += unlocking_script_size
174-
sequence_number = txinputraw[cursor : cursor + 4]
175-
cursor += 4
176175

177-
# check if coinbase input (utxo will be all zeros). If it is then do not
178-
# parse the script_sig; just pass it as one element (the string miners provided,
179-
# typically includes the extra nonce, miner pool, etc.)
180-
if inp_hash.hex() == 64 * "0":
181-
return (
182-
TxInput(
183-
txid=inp_hash.hex(),
184-
txout_index=int(output_n.hex(), 16),
185-
script_sig=Script([unlocking_script.hex()]),
186-
sequence=sequence_number,
187-
),
188-
cursor,
189-
)
176+
# Read the sequence number
177+
sequence_format = "<I"
178+
sequence, = struct.unpack_from(sequence_format, txinputraw, cursor)
179+
cursor += struct.calcsize(sequence_format)
190180

191-
return (
192-
TxInput(
193-
txid=inp_hash.hex(),
194-
txout_index=int(output_n.hex(), 16),
195-
script_sig=Script.from_raw(
196-
unlocking_script.hex(), has_segwit=has_segwit
197-
),
198-
sequence=sequence_number,
199-
),
200-
cursor,
181+
# If coinbase input, handle differently
182+
if txid == 64 * "0":
183+
script_sig = Script([unlocking_script.hex()]) # Treat as single element for coinbase
184+
else:
185+
script_sig = Script.from_raw(unlocking_script.hex(), has_segwit=has_segwit)
186+
187+
# Create the TxInput instance
188+
tx_input = TxInput(
189+
txid=txid,
190+
txout_index=vout,
191+
script_sig=script_sig,
192+
sequence=sequence
201193
)
202194

195+
return tx_input, cursor
196+
203197
@classmethod
204198
def copy(cls, txin: "TxInput") -> "TxInput":
205199
"""Deep copy of TxInput"""
@@ -312,24 +306,28 @@ def from_raw(txoutputrawhex: str, cursor: int = 0, has_segwit: bool = False):
312306
"""
313307
txoutputraw = h_to_b(txoutputrawhex)
314308

315-
# read the amount of the TxOutput
316-
value = int.from_bytes(txoutputraw[cursor : cursor + 8][::-1], "big")
317-
cursor += 8
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)
312+
cursor += struct.calcsize(amount_format)
318313

319-
# read the size (bytes length) of the integer representing the size of the
320-
# locking Script's raw data and the size of the locking Script's raw data
321-
lock_script_size, size = vi_to_int(txoutputraw[cursor : cursor + 9])
314+
# Read the locking script size using parse_compact_size
315+
lock_script_size, size = parse_compact_size(txoutputraw, cursor)
322316
cursor += size
323-
lock_script = txoutputraw[cursor : cursor + lock_script_size]
317+
318+
# Read the locking script
319+
script_format = f"{lock_script_size}s"
320+
lock_script, = struct.unpack_from(script_format, txoutputraw, cursor)
324321
cursor += lock_script_size
325-
return (
326-
TxOutput(
327-
amount=value,
328-
script_pubkey=Script.from_raw(lock_script.hex(), has_segwit=has_segwit),
329-
),
330-
cursor,
322+
323+
# Create the TxOutput instance
324+
tx_output = TxOutput(
325+
amount=amount,
326+
script_pubkey=Script.from_raw(lock_script.hex(), has_segwit=has_segwit)
331327
)
332328

329+
return tx_output, cursor
330+
333331
def __str__(self) -> str:
334332
return str({"amount": self.amount, "script_pubkey": self.script_pubkey})
335333

@@ -542,71 +540,67 @@ def from_raw(rawtxhex: str):
542540
The hexadecimal raw string of the Transaction
543541
"""
544542
rawtx = h_to_b(rawtxhex)
543+
cursor = 0
544+
545+
# Unpack version (4 bytes)
546+
version, = struct.unpack_from('<I', rawtx, cursor)
547+
cursor += 4
545548

546-
# read version
547-
version = rawtx[0:4]
549+
# Detect and handle SegWit
548550
flag = None
549551
has_segwit = False
550-
cursor = 4
551-
if rawtx[4:5] == b"\0":
552-
flag = rawtx[5:6]
553-
if flag == b"\1":
554-
has_segwit = True
555-
cursor += 2
556-
557-
# read the size (bytes length) of the integer representing the size of
558-
# the inputs number and the inputs number
552+
if rawtx[cursor] == 0x00 and rawtx[cursor + 1] == 0x01:
553+
flag = rawtx[cursor + 1]
554+
has_segwit = True
555+
cursor += 2 # Skip past the marker and flag bytes
559556

560-
n_inputs, size = vi_to_int(rawtx[cursor : cursor + 9])
557+
# Read the number of inputs
558+
n_inputs, size = parse_compact_size(rawtx, cursor)
561559
cursor += size
562560
inputs = []
563561

564-
# iterate n_inputs times to read the inputs from raw
565-
for index in range(0, n_inputs):
566-
inp, cursor = TxInput.from_raw(
567-
rawtxhex, cursor=cursor, has_segwit=has_segwit
568-
)
562+
# Read inputs
563+
for _ in range(n_inputs):
564+
inp, cursor = TxInput.from_raw(rawtxhex, cursor, has_segwit)
569565
inputs.append(inp)
570566

571-
outputs = []
572-
# read the size (bytes length) of the integer representing the size
573-
# of the outputs number and the the outputs number
574-
n_outputs, size = vi_to_int(rawtx[cursor : cursor + 9])
567+
# Read the number of outputs
568+
n_outputs, size = parse_compact_size(rawtx, cursor)
575569
cursor += size
576-
output_total = 0
577-
# iterate n_outputs times to read the inputs from raw
578-
for index in range(0, n_outputs):
579-
output, cursor = TxOutput.from_raw(
580-
rawtxhex, cursor=cursor, has_segwit=has_segwit
581-
)
570+
outputs = []
571+
572+
# Read outputs
573+
for _ in range(n_outputs):
574+
output, cursor = TxOutput.from_raw(rawtxhex, cursor, has_segwit)
582575
outputs.append(output)
583576

577+
# Handle witnesses if SegWit is enabled
584578
witnesses = []
585-
if has_segwit is True:
586-
# iterate to read the witnesses for every input
587-
for n in range(0, len(inputs)):
588-
n_items, size = vi_to_int(rawtx[cursor : cursor + 9])
579+
if has_segwit:
580+
for _ in range(n_inputs):
581+
n_items, size = parse_compact_size(rawtx, cursor)
589582
cursor += size
590-
witnesses_tmp: list[str] = []
591-
for m in range(0, n_items):
592-
witness = b"\0"
593-
item_size, size = vi_to_int(rawtx[cursor : cursor + 9])
594-
if item_size:
595-
witness = rawtx[cursor + size : cursor + item_size + size]
596-
cursor += item_size + size
597-
witnesses_tmp.append(witness.hex())
598-
if witnesses_tmp:
599-
witnesses.append(TxWitnessInput(stack=witnesses_tmp))
600-
601-
locktime = rawtx[cursor : cursor + 4]
583+
witness_stack = []
584+
for __ in range(n_items):
585+
item_size, size = parse_compact_size(rawtx, cursor)
586+
cursor += size
587+
witness_data = rawtx[cursor:cursor + item_size]
588+
cursor += item_size
589+
witness_stack.append(witness_data.hex())
590+
witnesses.append(TxWitnessInput(stack=witness_stack))
591+
592+
# Unpack locktime (4 bytes)
593+
locktime, = struct.unpack_from('<I', rawtx, cursor)
594+
cursor += 4
602595

596+
# Construct and return the Transaction object
603597
return Transaction(
598+
version=version,
604599
inputs=inputs,
605600
outputs=outputs,
606-
version=version,
607601
locktime=locktime,
608602
has_segwit=has_segwit,
609-
witnesses=witnesses,
603+
witnesses=witnesses
610604
)
611605

612606
def __str__(self) -> str:

0 commit comments

Comments
 (0)