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 }