mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-29 23:33:37 +00:00
233307aca5
Turns out C# VM doesn't have it since preview2, so our limiting of MaxArraySize in incompatible with it. Removing this limit shouldn't be a problem with the reference counter we have, both APPEND and SETITEM add things to reference counter and we can't exceed MaxStackSize. PACK on the other hand can't get more than MaxStackSize-1 of input elements. Unify NEWSTRUCT with NEWARRAY* and use better integer checks at the same time. Multisig limit is still 1024.
197 lines
5.3 KiB
Go
197 lines
5.3 KiB
Go
package vm
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
|
"github.com/nspcc-dev/neo-go/pkg/util/bitfield"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
)
|
|
|
|
// MaxMultisigKeys is the maximum number of used keys for correct multisig contract.
|
|
const MaxMultisigKeys = 1024
|
|
|
|
var (
|
|
verifyInteropID = interopnames.ToID([]byte(interopnames.SystemCryptoCheckSig))
|
|
multisigInteropID = interopnames.ToID([]byte(interopnames.SystemCryptoCheckMultisig))
|
|
)
|
|
|
|
func getNumOfThingsFromInstr(instr opcode.Opcode, param []byte) (int, bool) {
|
|
var nthings int
|
|
|
|
switch {
|
|
case opcode.PUSH1 <= instr && instr <= opcode.PUSH16:
|
|
nthings = int(instr-opcode.PUSH1) + 1
|
|
case instr <= opcode.PUSHINT256:
|
|
n := bigint.FromBytes(param)
|
|
if !n.IsInt64() || n.Sign() < 0 || n.Int64() > MaxMultisigKeys {
|
|
return 0, false
|
|
}
|
|
nthings = int(n.Int64())
|
|
default:
|
|
return 0, false
|
|
}
|
|
if nthings < 1 || nthings > MaxMultisigKeys {
|
|
return 0, false
|
|
}
|
|
return nthings, true
|
|
}
|
|
|
|
// IsMultiSigContract checks whether the passed script is a multi-signature
|
|
// contract.
|
|
func IsMultiSigContract(script []byte) bool {
|
|
_, _, ok := ParseMultiSigContract(script)
|
|
return ok
|
|
}
|
|
|
|
// ParseMultiSigContract returns number of signatures and list of public keys
|
|
// from the verification script of the contract.
|
|
func ParseMultiSigContract(script []byte) (int, [][]byte, bool) {
|
|
var nsigs, nkeys int
|
|
if len(script) < 42 {
|
|
return nsigs, nil, false
|
|
}
|
|
|
|
ctx := NewContext(script)
|
|
instr, param, err := ctx.Next()
|
|
if err != nil {
|
|
return nsigs, nil, false
|
|
}
|
|
nsigs, ok := getNumOfThingsFromInstr(instr, param)
|
|
if !ok {
|
|
return nsigs, nil, false
|
|
}
|
|
var pubs [][]byte
|
|
for {
|
|
instr, param, err = ctx.Next()
|
|
if err != nil {
|
|
return nsigs, nil, false
|
|
}
|
|
if instr != opcode.PUSHDATA1 {
|
|
break
|
|
}
|
|
if len(param) < 33 {
|
|
return nsigs, nil, false
|
|
}
|
|
pubs = append(pubs, param)
|
|
nkeys++
|
|
if nkeys > MaxMultisigKeys {
|
|
return nsigs, nil, false
|
|
}
|
|
}
|
|
if nkeys < nsigs {
|
|
return nsigs, nil, false
|
|
}
|
|
nkeys2, ok := getNumOfThingsFromInstr(instr, param)
|
|
if !ok {
|
|
return nsigs, nil, false
|
|
}
|
|
if nkeys2 != nkeys {
|
|
return nsigs, nil, false
|
|
}
|
|
instr, param, err = ctx.Next()
|
|
if err != nil || instr != opcode.SYSCALL || binary.LittleEndian.Uint32(param) != multisigInteropID {
|
|
return nsigs, nil, false
|
|
}
|
|
instr, _, err = ctx.Next()
|
|
if err != nil || instr != opcode.RET || ctx.ip != len(script) {
|
|
return nsigs, nil, false
|
|
}
|
|
return nsigs, pubs, true
|
|
}
|
|
|
|
// IsSignatureContract checks whether the passed script is a signature check
|
|
// contract.
|
|
func IsSignatureContract(script []byte) bool {
|
|
_, ok := ParseSignatureContract(script)
|
|
return ok
|
|
}
|
|
|
|
// ParseSignatureContract parses simple signature contract and returns
|
|
// public key.
|
|
func ParseSignatureContract(script []byte) ([]byte, bool) {
|
|
if len(script) != 40 {
|
|
return nil, false
|
|
}
|
|
|
|
ctx := NewContext(script)
|
|
instr, param, err := ctx.Next()
|
|
if err != nil || instr != opcode.PUSHDATA1 || len(param) != 33 {
|
|
return nil, false
|
|
}
|
|
pub := param
|
|
instr, param, err = ctx.Next()
|
|
if err != nil || instr != opcode.SYSCALL || binary.LittleEndian.Uint32(param) != verifyInteropID {
|
|
return nil, false
|
|
}
|
|
return pub, true
|
|
}
|
|
|
|
// IsStandardContract checks whether the passed script is a signature or
|
|
// multi-signature contract.
|
|
func IsStandardContract(script []byte) bool {
|
|
return IsSignatureContract(script) || IsMultiSigContract(script)
|
|
}
|
|
|
|
// IsScriptCorrect checks script for errors and mask provided for correctness wrt
|
|
// instruction boundaries. Normally it returns nil, but can return some specific
|
|
// error if there is any.
|
|
func IsScriptCorrect(script []byte, methods bitfield.Field) error {
|
|
var (
|
|
l = len(script)
|
|
instrs = bitfield.New(l)
|
|
jumps = bitfield.New(l)
|
|
)
|
|
ctx := NewContext(script)
|
|
for ctx.nextip < l {
|
|
op, param, err := ctx.Next()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
instrs.Set(ctx.ip)
|
|
switch op {
|
|
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
|
|
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
|
|
opcode.CALL, opcode.ENDTRY, opcode.JMPL, opcode.JMPIFL,
|
|
opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
|
|
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
|
|
opcode.ENDTRYL, opcode.CALLL, opcode.PUSHA:
|
|
off, _, err := calcJumpOffset(ctx, param) // It does bounds checking.
|
|
if err != nil {
|
|
return err
|
|
}
|
|
jumps.Set(off)
|
|
case opcode.TRY, opcode.TRYL:
|
|
catchP, finallyP := getTryParams(op, param)
|
|
off, _, err := calcJumpOffset(ctx, catchP)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
jumps.Set(off)
|
|
off, _, err = calcJumpOffset(ctx, finallyP)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
jumps.Set(off)
|
|
case opcode.NEWARRAYT, opcode.ISTYPE, opcode.CONVERT:
|
|
typ := stackitem.Type(param[0])
|
|
if !typ.IsValid() {
|
|
return fmt.Errorf("invalid type specification at offset %d", ctx.ip)
|
|
}
|
|
if typ == stackitem.AnyT && op != opcode.NEWARRAYT {
|
|
return fmt.Errorf("using type ANY is incorrect at offset %d", ctx.ip)
|
|
}
|
|
}
|
|
}
|
|
if !jumps.IsSubset(instrs) {
|
|
return errors.New("some jumps are done to wrong offsets (not to instruction boundary)")
|
|
}
|
|
if methods != nil && !methods.IsSubset(instrs) {
|
|
return errors.New("some methods point to wrong offsets (not to instruction boundary)")
|
|
}
|
|
return nil
|
|
}
|