Skip to content

Commit abd72ab

Browse files
authored
reverted transactions.py
1 parent 8ecc820 commit abd72ab

1 file changed

Lines changed: 57 additions & 28 deletions

File tree

bitcoinutils/transactions.py

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import math
1313
import hashlib
1414
import struct
15-
from typing import Optional
15+
from typing import Optional, Union
1616

1717
from bitcoinutils.constants import (
1818
DEFAULT_TX_SEQUENCE,
@@ -141,7 +141,7 @@ def __repr__(self):
141141
return self.__str__()
142142

143143
@staticmethod
144-
def from_raw(txinputrawhex: str, cursor: int = 0, has_segwit: bool = False):
144+
def from_raw(txinputrawhex: Union[str, bytes], cursor: int = 0, has_segwit: bool = False):
145145
"""
146146
Imports a TxInput from a Transaction's hexadecimal data
147147
@@ -154,7 +154,12 @@ def from_raw(txinputrawhex: str, cursor: int = 0, has_segwit: bool = False):
154154
has_segwit : boolean
155155
Is the Tx Input segwit or not
156156
"""
157-
txinputraw = h_to_b(txinputrawhex)
157+
if isinstance(txinputrawhex, str):
158+
txinputraw = h_to_b(txinputrawhex)
159+
elif isinstance(txinputrawhex, bytes):
160+
txinputraw = txinputrawhex
161+
else:
162+
raise TypeError("Input must be a hexadecimal string or bytes")
158163

159164
# Unpack transaction ID (hash) in bytes and output index
160165
txid, vout = struct.unpack_from('<32sI', txinputraw, cursor)
@@ -286,7 +291,7 @@ def to_bytes(self) -> bytes:
286291
return data
287292

288293
@staticmethod
289-
def from_raw(txoutputrawhex: str, cursor: int = 0, has_segwit: bool = False):
294+
def from_raw(txoutputrawhex: Union[str, bytes], cursor: int = 0, has_segwit: bool = False):
290295
"""
291296
Imports a TxOutput from a Transaction's hexadecimal data
292297
@@ -299,7 +304,13 @@ def from_raw(txoutputrawhex: str, cursor: int = 0, has_segwit: bool = False):
299304
has_segwit : boolean
300305
Is the Tx Output segwit or not
301306
"""
302-
txoutputraw = h_to_b(txoutputrawhex)
307+
if isinstance(txoutputrawhex, str):
308+
txoutputraw = h_to_b(txoutputrawhex)
309+
elif isinstance(txoutputrawhex, bytes):
310+
txoutputraw = txoutputrawhex
311+
else:
312+
raise TypeError("Input must be a hexadecimal string or bytes")
313+
303314

304315
# Unpack the amount of the TxOutput directly in bytes
305316
amount_format = "<Q" # Little-endian unsigned long long (8 bytes)
@@ -526,7 +537,7 @@ def __init__(
526537
self.version = version
527538

528539
@staticmethod
529-
def from_raw(rawtxhex: str):
540+
def from_raw(rawtxhex: Union[str, bytes]):
530541
"""
531542
Imports a Transaction from hexadecimal data.
532543
@@ -535,7 +546,13 @@ def from_raw(rawtxhex: str):
535546
rawtxhex : string (hex)
536547
The hexadecimal raw string of the Transaction.
537548
"""
538-
rawtx = h_to_b(rawtxhex)
549+
if isinstance(rawtxhex, str):
550+
rawtx = h_to_b(rawtxhex)
551+
elif isinstance(rawtxhex, bytes):
552+
rawtx = rawtxhex
553+
else:
554+
raise TypeError("Input must be a hexadecimal string or bytes")
555+
539556

540557
# Read version (4 bytes)
541558
version = rawtx[0:4]
@@ -567,9 +584,11 @@ def from_raw(rawtxhex: str):
567584
output, cursor = TxOutput.from_raw(rawtx.hex(), cursor, has_segwit)
568585
outputs.append(output)
569586

570-
# Handle witnesses if SegWit is enabled
587+
# Handle witnesses if SegWit is enabled and if they are present i.e. if
588+
# remaining payload length is greater than last tx field length (locktime)
589+
has_witness_field = True if len(rawtx) - cursor > 4 else False
571590
witnesses = []
572-
if has_segwit:
591+
if has_segwit and has_witness_field:
573592
for _ in range(n_inputs):
574593
n_items, size = parse_compact_size(rawtx[cursor:])
575594
cursor += size
@@ -652,12 +671,13 @@ def get_transaction_digest(
652671
for txin in tmp_tx.inputs:
653672
txin.script_sig = Script([])
654673

655-
# If there are OP_CODESEPARATORs in the script, we need to remove them
656-
# for the signature hash calculation
657-
if "OP_CODESEPARATOR" in script.script:
658-
# Create a new script without OP_CODESEPARATORs
659-
filtered_script_elements = [element for element in script.script if element != "OP_CODESEPARATOR"]
660-
script = Script(filtered_script_elements)
674+
#
675+
# TODO Deal with (delete?) script's OP_CODESEPARATORs, if any
676+
# Very early versions of Bitcoin were using a different design for
677+
# scripts that were flawed. OP_CODESEPARATOR has no purpose currently
678+
# but we could not delete it for compatibility purposes. If it exists
679+
# in a script it needs to be removed.
680+
#
661681

662682
# the temporary transaction's scriptSig needs to be set to the
663683
# scriptPubKey of the UTXO we are trying to spend - this is required to
@@ -843,6 +863,7 @@ def get_transaction_segwit_digest(
843863
return hashlib.sha256(hashlib.sha256(tx_for_signing).digest()).digest()
844864

845865
# TODO Update doc with TAPROOT_SIGHASH_ALL
866+
# clean prints after finishing other sighashes
846867
def get_transaction_taproot_digest(
847868
self,
848869
txin_index: int,
@@ -859,11 +880,11 @@ def get_transaction_taproot_digest(
859880
And: https://github.com/bitcoin/bitcoin/blob/b5f33ac1f82aea290b4653af36ac2ad1bf1cce7b/test/functional/test_framework/script.py
860881
861882
| SIGHASH types (see constants.py):
862-
| TAPROOT_SIGHASH_ALL (0x00) - signs all inputs and outputs (default)
863-
| SIGHASH_ALL (0x01) - signs all inputs and outputs
864-
| SIGHASH_NONE (0x02) - signs all of the inputs
865-
| SIGHASH_SINGLE (0x03) - signs all inputs but only txin_index output
866-
| SIGHASH_ANYONECANPAY (0x80) (only combined with one of the above)
883+
| TAPROOT_SIGHASH_ALL - signs all inputs and outputs (default)
884+
| SIGHASH_ALL - signs all inputs and outputs
885+
| SIGHASH_NONE - signs all of the inputs
886+
| SIGHASH_SINGLE - signs all inputs but only txin_index output
887+
| SIGHASH_ANYONECANPAY (only combined with one of the above)
867888
| - with ALL - signs all outputs but only txin_index input
868889
| - with NONE - signs only the txin_index input
869890
| - with SINGLE - signs txin_index input and output
@@ -886,6 +907,10 @@ def get_transaction_taproot_digest(
886907
The type of the signature hash to be created
887908
"""
888909

910+
# clone transaction to modify without messing up the real transaction
911+
# tmp_tx is not really used for its to_bytes() here
912+
# TODO we could use self directly to access fields
913+
tmp_tx = Transaction.copy(self)
889914

890915
# acquiring the signature type
891916
# sign_all = sig_hash & 0x03 == SIGHASH_ALL
@@ -914,8 +939,9 @@ def get_transaction_taproot_digest(
914939

915940
# Data about the transaction
916941
if not anyone_can_pay:
942+
# print('1')
917943
# the SHA256 of the serialization of all input outpoints
918-
for txin in self.inputs:
944+
for txin in tmp_tx.inputs:
919945
hash_prevouts += h_to_b(txin.txid)[::-1] + struct.pack(
920946
"<I",
921947
txin.txout_index,
@@ -938,13 +964,14 @@ def get_transaction_taproot_digest(
938964
tx_for_signing += hash_script_pubkeys
939965

940966
# the SHA256 of the serialization of all input nSequence
941-
for txin in self.inputs:
967+
for txin in tmp_tx.inputs:
942968
hash_sequences += txin.sequence
943969
hash_sequences = hashlib.sha256(hash_sequences).digest()
944970
tx_for_signing += hash_sequences
945971

946972
if not (sighash_none or sighash_single):
947-
for txout in self.outputs:
973+
# print('2')
974+
for txout in tmp_tx.outputs:
948975
amount_bytes = struct.pack("<Q", txout.amount)
949976
script_bytes = txout.script_pubkey.to_bytes()
950977
hash_outputs += (
@@ -959,7 +986,8 @@ def get_transaction_taproot_digest(
959986
tx_for_signing += bytes([spend_type])
960987

961988
if anyone_can_pay:
962-
txin = self.inputs[txin_index]
989+
# print('3')
990+
txin = tmp_tx.inputs[txin_index]
963991
# convert txid to big-endian first
964992
tx_for_signing += h_to_b(txin.txid)[::-1] + struct.pack(
965993
"<I",
@@ -974,15 +1002,16 @@ def get_transaction_taproot_digest(
9741002

9751003
tx_for_signing += txin.sequence
9761004
else:
1005+
# print('4')
9771006
tx_for_signing += txin_index.to_bytes(4, "little")
9781007

979-
if hasattr(self, 'annex') and self.annex is not None:
980-
annex_data = self.annex[txin_index] if isinstance(self.annex, list) else self.annex
981-
tx_for_signing += prepend_compact_size(h_to_b(annex_data))
1008+
# TODO if annex is present it should be added here
1009+
# length of annex should use compact_size
9821010

9831011
# Data about this output
9841012
if sighash_single:
985-
txout = self.outputs[txin_index]
1013+
# print('5')
1014+
txout = tmp_tx.outputs[txin_index]
9861015
amount_bytes = struct.pack("<Q", txout.amount)
9871016
script_bytes = txout.script_pubkey.to_bytes()
9881017
hash_output = (

0 commit comments

Comments
 (0)