28
28
is_pubkey_compressed ,
29
29
)
30
30
from hathor .transaction .exceptions import (
31
+ CustomSighashModelInvalid ,
31
32
EqualVerifyFailed ,
33
+ InputNotSelectedError ,
34
+ InputsOutputsLimitModelInvalid ,
32
35
InvalidScriptError ,
33
36
InvalidStackData ,
37
+ MaxInputsExceededError ,
38
+ MaxOutputsExceededError ,
34
39
MissingStackItems ,
35
40
OracleChecksigFailed ,
36
41
ScriptError ,
39
44
)
40
45
from hathor .transaction .scripts .execute import Stack , binary_to_int , decode_opn , get_data_value , get_script_op
41
46
from hathor .transaction .scripts .script_context import ScriptContext
47
+ from hathor .transaction .scripts .sighash import InputsOutputsLimit , SighashBitmask
48
+ from hathor .transaction .util import bytes_to_int
42
49
43
50
44
51
class Opcode (IntEnum ):
@@ -72,6 +79,9 @@ class Opcode(IntEnum):
72
79
OP_DATA_GREATERTHAN = 0xC1
73
80
OP_FIND_P2PKH = 0xD0
74
81
OP_DATA_MATCH_VALUE = 0xD1
82
+ OP_SIGHASH_BITMASK = 0xE0
83
+ OP_SIGHASH_RANGE = 0xE1
84
+ OP_MAX_INPUTS_OUTPUTS = 0xE2
75
85
76
86
@classmethod
77
87
def is_pushdata (cls , opcode : int ) -> bool :
@@ -249,7 +259,8 @@ def op_checksig(context: ScriptContext) -> None:
249
259
# pubkey is not compressed public key
250
260
raise ScriptError ('OP_CHECKSIG: pubkey is not a public key' ) from e
251
261
try :
252
- public_key .verify (signature , context .extras .tx .get_sighash_all_data (), ec .ECDSA (hashes .SHA256 ()))
262
+ sighash_data = context .get_tx_sighash_data (context .extras .tx )
263
+ public_key .verify (signature , sighash_data , ec .ECDSA (hashes .SHA256 ()))
253
264
# valid, push true to stack
254
265
context .stack .append (1 )
255
266
except InvalidSignature :
@@ -583,7 +594,7 @@ def op_checkmultisig(context: ScriptContext) -> None:
583
594
while pubkey_index < len (pubkeys ):
584
595
pubkey = pubkeys [pubkey_index ]
585
596
new_stack = [signature , pubkey ]
586
- op_checksig (ScriptContext (stack = new_stack , logs = context .logs , extras = context .extras ))
597
+ op_checksig (ScriptContext (stack = new_stack , logs = context .logs , extras = context .extras , settings = settings ))
587
598
result = new_stack .pop ()
588
599
pubkey_index += 1
589
600
if result == 1 :
@@ -617,6 +628,59 @@ def op_integer(opcode: int, stack: Stack) -> None:
617
628
raise ScriptError (e ) from e
618
629
619
630
631
+ def op_sighash_bitmask (context : ScriptContext ) -> None :
632
+ """Pop two items from the stack, constructing a sighash bitmask and setting it in the script context."""
633
+ if len (context .stack ) < 2 :
634
+ raise MissingStackItems (f'OP_SIGHASH_BITMASK: expected 2 elements on stack, has { len (context .stack )} ' )
635
+
636
+ outputs = context .stack .pop ()
637
+ inputs = context .stack .pop ()
638
+ assert isinstance (inputs , bytes )
639
+ assert isinstance (outputs , bytes )
640
+
641
+ try :
642
+ sighash = SighashBitmask (
643
+ inputs = bytes_to_int (inputs ),
644
+ outputs = bytes_to_int (outputs )
645
+ )
646
+ except Exception as e :
647
+ raise CustomSighashModelInvalid ('Could not construct sighash bitmask.' ) from e
648
+
649
+ if context .extras .input_index not in sighash .get_input_indexes ():
650
+ raise InputNotSelectedError (
651
+ f'Input at index { context .extras .input_index } must select itself when using a custom sighash.'
652
+ )
653
+
654
+ context .set_sighash (sighash )
655
+
656
+
657
+ def op_max_inputs_outputs (context : ScriptContext ) -> None :
658
+ """Pop two items from the stack, constructing an inputs and outputs limit and setting it in the script context."""
659
+ if len (context .stack ) < 2 :
660
+ raise MissingStackItems (f'OP_MAX_INPUTS_OUTPUTS: expected 2 elements on stack, has { len (context .stack )} ' )
661
+
662
+ max_outputs = context .stack .pop ()
663
+ max_inputs = context .stack .pop ()
664
+ assert isinstance (max_inputs , bytes )
665
+ assert isinstance (max_outputs , bytes )
666
+
667
+ try :
668
+ limit = InputsOutputsLimit (
669
+ max_inputs = bytes_to_int (max_inputs ),
670
+ max_outputs = bytes_to_int (max_outputs )
671
+ )
672
+ except Exception as e :
673
+ raise InputsOutputsLimitModelInvalid ("Could not construct inputs and outputs limits." ) from e
674
+
675
+ tx_inputs_len = len (context .extras .tx .inputs )
676
+ if tx_inputs_len > limit .max_inputs :
677
+ raise MaxInputsExceededError (f'Maximum number of inputs exceeded ({ tx_inputs_len } > { limit .max_inputs } ).' )
678
+
679
+ tx_outputs_len = len (context .extras .tx .outputs )
680
+ if tx_outputs_len > limit .max_outputs :
681
+ raise MaxOutputsExceededError (f'Maximum number of outputs exceeded ({ tx_outputs_len } > { limit .max_outputs } ).' )
682
+
683
+
620
684
def execute_op_code (opcode : Opcode , context : ScriptContext ) -> None :
621
685
"""
622
686
Execute a function opcode.
@@ -625,6 +689,8 @@ def execute_op_code(opcode: Opcode, context: ScriptContext) -> None:
625
689
opcode: the opcode to be executed.
626
690
context: the script context to be manipulated.
627
691
"""
692
+ if not is_opcode_valid (opcode ):
693
+ raise ScriptError (f'Opcode "{ opcode .name } " is invalid.' )
628
694
context .logs .append (f'Executing function opcode { opcode .name } ({ hex (opcode .value )} )' )
629
695
match opcode :
630
696
case Opcode .OP_DUP : op_dup (context )
@@ -639,4 +705,26 @@ def execute_op_code(opcode: Opcode, context: ScriptContext) -> None:
639
705
case Opcode .OP_DATA_MATCH_VALUE : op_data_match_value (context )
640
706
case Opcode .OP_CHECKDATASIG : op_checkdatasig (context )
641
707
case Opcode .OP_FIND_P2PKH : op_find_p2pkh (context )
708
+ case Opcode .OP_SIGHASH_BITMASK : op_sighash_bitmask (context )
709
+ case Opcode .OP_MAX_INPUTS_OUTPUTS : op_max_inputs_outputs (context )
642
710
case _: raise ScriptError (f'unknown opcode: { opcode } ' )
711
+
712
+
713
+ def is_opcode_valid (opcode : Opcode ) -> bool :
714
+ """Return whether an opcode is valid, that is, it's currently enabled."""
715
+ valid_opcodes = [
716
+ Opcode .OP_DUP ,
717
+ Opcode .OP_EQUAL ,
718
+ Opcode .OP_EQUALVERIFY ,
719
+ Opcode .OP_CHECKSIG ,
720
+ Opcode .OP_HASH160 ,
721
+ Opcode .OP_GREATERTHAN_TIMESTAMP ,
722
+ Opcode .OP_CHECKMULTISIG ,
723
+ Opcode .OP_DATA_STREQUAL ,
724
+ Opcode .OP_DATA_GREATERTHAN ,
725
+ Opcode .OP_DATA_MATCH_VALUE ,
726
+ Opcode .OP_CHECKDATASIG ,
727
+ Opcode .OP_FIND_P2PKH ,
728
+ ]
729
+
730
+ return opcode in valid_opcodes
0 commit comments