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