-
Notifications
You must be signed in to change notification settings - Fork 81
/
Copy pathcontract_checks.go
202 lines (188 loc) · 5.57 KB
/
contract_checks.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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 keys allowed 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 the number of signatures and a 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 a simple signature contract and returns
// a public key.
func ParseSignatureContract(script []byte) ([]byte, bool) {
if len(script) != 40 {
return nil, false
}
// We don't use Context for this simple case, it's more efficient this way.
if script[0] == byte(opcode.PUSHDATA1) && // PUSHDATA1
script[1] == 33 && // with a public key parameter
script[35] == byte(opcode.SYSCALL) && // and a CheckSig SYSCALL.
binary.LittleEndian.Uint32(script[36:]) == verifyInteropID {
return script[2:35], true
}
return nil, false
}
// 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 the script for errors and mask provided for correctness wrt
// instruction boundaries. Normally, it returns nil, but it 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)
if err != nil {
return err
}
// `calcJumpOffset` does bounds checking but can return `len(script)`.
// This check avoids panic in bitset when script length is a multiple of 64.
if off != len(script) {
jumps.Set(off)
}
case opcode.TRY, opcode.TRYL:
catchP, finallyP := getTryParams(op, param)
off, _, err := calcJumpOffset(ctx, catchP)
if err != nil {
return err
}
if off != len(script) {
jumps.Set(off)
}
off, _, err = calcJumpOffset(ctx, finallyP)
if err != nil {
return err
}
if off != len(script) {
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
}