github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/vm/contract_checks.go (about)

     1  package vm
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
     9  	"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
    10  	"github.com/nspcc-dev/neo-go/pkg/util/bitfield"
    11  	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
    12  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    13  )
    14  
    15  // MaxMultisigKeys is the maximum number of keys allowed for correct multisig contract.
    16  const MaxMultisigKeys = 1024
    17  
    18  var (
    19  	verifyInteropID   = interopnames.ToID([]byte(interopnames.SystemCryptoCheckSig))
    20  	multisigInteropID = interopnames.ToID([]byte(interopnames.SystemCryptoCheckMultisig))
    21  )
    22  
    23  func getNumOfThingsFromInstr(instr opcode.Opcode, param []byte) (int, bool) {
    24  	var nthings int
    25  
    26  	switch {
    27  	case opcode.PUSH1 <= instr && instr <= opcode.PUSH16:
    28  		nthings = int(instr-opcode.PUSH1) + 1
    29  	case instr <= opcode.PUSHINT256:
    30  		n := bigint.FromBytes(param)
    31  		if !n.IsInt64() || n.Sign() < 0 || n.Int64() > MaxMultisigKeys {
    32  			return 0, false
    33  		}
    34  		nthings = int(n.Int64())
    35  	default:
    36  		return 0, false
    37  	}
    38  	if nthings < 1 || nthings > MaxMultisigKeys {
    39  		return 0, false
    40  	}
    41  	return nthings, true
    42  }
    43  
    44  // IsMultiSigContract checks whether the passed script is a multi-signature
    45  // contract.
    46  func IsMultiSigContract(script []byte) bool {
    47  	_, _, ok := ParseMultiSigContract(script)
    48  	return ok
    49  }
    50  
    51  // ParseMultiSigContract returns the number of signatures and a list of public keys
    52  // from the verification script of the contract.
    53  func ParseMultiSigContract(script []byte) (int, [][]byte, bool) {
    54  	var nsigs, nkeys int
    55  	if len(script) < 42 {
    56  		return nsigs, nil, false
    57  	}
    58  
    59  	ctx := NewContext(script)
    60  	instr, param, err := ctx.Next()
    61  	if err != nil {
    62  		return nsigs, nil, false
    63  	}
    64  	nsigs, ok := getNumOfThingsFromInstr(instr, param)
    65  	if !ok {
    66  		return nsigs, nil, false
    67  	}
    68  	var pubs [][]byte
    69  	for {
    70  		instr, param, err = ctx.Next()
    71  		if err != nil {
    72  			return nsigs, nil, false
    73  		}
    74  		if instr != opcode.PUSHDATA1 {
    75  			break
    76  		}
    77  		if len(param) < 33 {
    78  			return nsigs, nil, false
    79  		}
    80  		pubs = append(pubs, param)
    81  		nkeys++
    82  		if nkeys > MaxMultisigKeys {
    83  			return nsigs, nil, false
    84  		}
    85  	}
    86  	if nkeys < nsigs {
    87  		return nsigs, nil, false
    88  	}
    89  	nkeys2, ok := getNumOfThingsFromInstr(instr, param)
    90  	if !ok {
    91  		return nsigs, nil, false
    92  	}
    93  	if nkeys2 != nkeys {
    94  		return nsigs, nil, false
    95  	}
    96  	instr, param, err = ctx.Next()
    97  	if err != nil || instr != opcode.SYSCALL || binary.LittleEndian.Uint32(param) != multisigInteropID {
    98  		return nsigs, nil, false
    99  	}
   100  	instr, _, err = ctx.Next()
   101  	if err != nil || instr != opcode.RET || ctx.ip != len(script) {
   102  		return nsigs, nil, false
   103  	}
   104  	return nsigs, pubs, true
   105  }
   106  
   107  // IsSignatureContract checks whether the passed script is a signature check
   108  // contract.
   109  func IsSignatureContract(script []byte) bool {
   110  	_, ok := ParseSignatureContract(script)
   111  	return ok
   112  }
   113  
   114  // ParseSignatureContract parses a simple signature contract and returns
   115  // a public key.
   116  func ParseSignatureContract(script []byte) ([]byte, bool) {
   117  	if len(script) != 40 {
   118  		return nil, false
   119  	}
   120  
   121  	// We don't use Context for this simple case, it's more efficient this way.
   122  	if script[0] == byte(opcode.PUSHDATA1) && // PUSHDATA1
   123  		script[1] == 33 && // with a public key parameter
   124  		script[35] == byte(opcode.SYSCALL) && // and a CheckSig SYSCALL.
   125  		binary.LittleEndian.Uint32(script[36:]) == verifyInteropID {
   126  		return script[2:35], true
   127  	}
   128  	return nil, false
   129  }
   130  
   131  // IsStandardContract checks whether the passed script is a signature or
   132  // multi-signature contract.
   133  func IsStandardContract(script []byte) bool {
   134  	return IsSignatureContract(script) || IsMultiSigContract(script)
   135  }
   136  
   137  // IsScriptCorrect checks the script for errors and mask provided for correctness wrt
   138  // instruction boundaries. Normally, it returns nil, but it can return some specific
   139  // error if there is any.
   140  func IsScriptCorrect(script []byte, methods bitfield.Field) error {
   141  	var (
   142  		l      = len(script)
   143  		instrs = bitfield.New(l)
   144  		jumps  = bitfield.New(l)
   145  	)
   146  	ctx := NewContext(script)
   147  	for ctx.nextip < l {
   148  		op, param, err := ctx.Next()
   149  		if err != nil {
   150  			return err
   151  		}
   152  		instrs.Set(ctx.ip)
   153  		switch op {
   154  		case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
   155  			opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
   156  			opcode.CALL, opcode.ENDTRY, opcode.JMPL, opcode.JMPIFL,
   157  			opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
   158  			opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
   159  			opcode.ENDTRYL, opcode.CALLL, opcode.PUSHA:
   160  			off, _, err := calcJumpOffset(ctx, param)
   161  			if err != nil {
   162  				return err
   163  			}
   164  			// `calcJumpOffset` does bounds checking but can return `len(script)`.
   165  			// This check avoids panic in bitset when script length is a multiple of 64.
   166  			if off != len(script) {
   167  				jumps.Set(off)
   168  			}
   169  		case opcode.TRY, opcode.TRYL:
   170  			catchP, finallyP := getTryParams(op, param)
   171  			off, _, err := calcJumpOffset(ctx, catchP)
   172  			if err != nil {
   173  				return err
   174  			}
   175  			if off != len(script) {
   176  				jumps.Set(off)
   177  			}
   178  			off, _, err = calcJumpOffset(ctx, finallyP)
   179  			if err != nil {
   180  				return err
   181  			}
   182  			if off != len(script) {
   183  				jumps.Set(off)
   184  			}
   185  		case opcode.NEWARRAYT, opcode.ISTYPE, opcode.CONVERT:
   186  			typ := stackitem.Type(param[0])
   187  			if !typ.IsValid() {
   188  				return fmt.Errorf("invalid type specification at offset %d", ctx.ip)
   189  			}
   190  			if typ == stackitem.AnyT && op != opcode.NEWARRAYT {
   191  				return fmt.Errorf("using type ANY is incorrect at offset %d", ctx.ip)
   192  			}
   193  		}
   194  	}
   195  	if !jumps.IsSubset(instrs) {
   196  		return errors.New("some jumps are done to wrong offsets (not to instruction boundary)")
   197  	}
   198  	if methods != nil && !methods.IsSubset(instrs) {
   199  		return errors.New("some methods point to wrong offsets (not to instruction boundary)")
   200  	}
   201  	return nil
   202  }