|
40 | 40 | prepend_compact_size, |
41 | 41 | h_to_b, |
42 | 42 | b_to_h, |
| 43 | + parse_compact_size, |
43 | 44 | ) |
44 | 45 |
|
45 | 46 |
|
@@ -142,64 +143,57 @@ def __repr__(self): |
142 | 143 | @staticmethod |
143 | 144 | def from_raw(txinputrawhex: str, cursor: int = 0, has_segwit: bool = False): |
144 | 145 | """ |
145 | | - Imports a TxInput from a Transaction's hexadecimal data |
| 146 | + Imports a TxInput from a Transaction's hexadecimal data using struct for parsing. |
146 | 147 |
|
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. |
155 | 158 | """ |
156 | 159 | txinputraw = h_to_b(txinputrawhex) |
157 | 160 |
|
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) |
160 | 166 |
|
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) |
171 | 169 | 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) |
173 | 174 | cursor += unlocking_script_size |
174 | | - sequence_number = txinputraw[cursor : cursor + 4] |
175 | | - cursor += 4 |
176 | 175 |
|
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) |
190 | 180 |
|
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 |
201 | 193 | ) |
202 | 194 |
|
| 195 | + return tx_input, cursor |
| 196 | + |
203 | 197 | @classmethod |
204 | 198 | def copy(cls, txin: "TxInput") -> "TxInput": |
205 | 199 | """Deep copy of TxInput""" |
@@ -312,24 +306,28 @@ def from_raw(txoutputrawhex: str, cursor: int = 0, has_segwit: bool = False): |
312 | 306 | """ |
313 | 307 | txoutputraw = h_to_b(txoutputrawhex) |
314 | 308 |
|
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) |
318 | 313 |
|
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) |
322 | 316 | 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) |
324 | 321 | 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) |
331 | 327 | ) |
332 | 328 |
|
| 329 | + return tx_output, cursor |
| 330 | + |
333 | 331 | def __str__(self) -> str: |
334 | 332 | return str({"amount": self.amount, "script_pubkey": self.script_pubkey}) |
335 | 333 |
|
@@ -542,71 +540,67 @@ def from_raw(rawtxhex: str): |
542 | 540 | The hexadecimal raw string of the Transaction |
543 | 541 | """ |
544 | 542 | 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 |
545 | 548 |
|
546 | | - # read version |
547 | | - version = rawtx[0:4] |
| 549 | + # Detect and handle SegWit |
548 | 550 | flag = None |
549 | 551 | 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 |
559 | 556 |
|
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) |
561 | 559 | cursor += size |
562 | 560 | inputs = [] |
563 | 561 |
|
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) |
569 | 565 | inputs.append(inp) |
570 | 566 |
|
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) |
575 | 569 | 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) |
582 | 575 | outputs.append(output) |
583 | 576 |
|
| 577 | + # Handle witnesses if SegWit is enabled |
584 | 578 | 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) |
589 | 582 | 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 |
602 | 595 |
|
| 596 | + # Construct and return the Transaction object |
603 | 597 | return Transaction( |
| 598 | + version=version, |
604 | 599 | inputs=inputs, |
605 | 600 | outputs=outputs, |
606 | | - version=version, |
607 | 601 | locktime=locktime, |
608 | 602 | has_segwit=has_segwit, |
609 | | - witnesses=witnesses, |
| 603 | + witnesses=witnesses |
610 | 604 | ) |
611 | 605 |
|
612 | 606 | def __str__(self) -> str: |
|
0 commit comments