-
Notifications
You must be signed in to change notification settings - Fork 78
/
nef.go
174 lines (159 loc) · 5.34 KB
/
nef.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
package nef
import (
"bytes"
"encoding/binary"
"errors"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/io"
)
// NEO Executable Format 3 (NEF3)
// Standard: https://github.com/neo-project/proposals/pull/121/files
// Implementation: https://github.com/neo-project/neo/blob/v3.0.0-preview2/src/neo/SmartContract/NefFile.cs#L8
// +------------+-----------+------------------------------------------------------------+
// | Field | Length | Comment |
// +------------+-----------+------------------------------------------------------------+
// | Magic | 4 bytes | Magic header |
// | Compiler | 64 bytes | Compiler used and it's version |
// | Source | Var bytes | Source file URL. |
// +------------+-----------+------------------------------------------------------------+
// | Reserved | 1 byte | Reserved for extensions. Must be 0. |
// | Tokens | Var array | List of method tokens |
// | Reserved | 2-bytes | Reserved for extensions. Must be 0. |
// | Script | Var bytes | Var bytes for the payload |
// +------------+-----------+------------------------------------------------------------+
// | Checksum | 4 bytes | First four bytes of double SHA256 hash of the header |
// +------------+-----------+------------------------------------------------------------+
const (
// Magic is a magic File header constant.
Magic uint32 = 0x3346454E
// MaxScriptLength is the maximum allowed contract script length.
MaxScriptLength = 512 * 1024
// MaxSourceURLLength is the maximum allowed source URL length.
MaxSourceURLLength = 256
// compilerFieldSize is the length of `Compiler` File header field in bytes.
compilerFieldSize = 64
)
// File represents a compiled contract file structure according to the NEF3 standard.
type File struct {
Header
Source string `json:"source"`
Tokens []MethodToken `json:"tokens"`
Script []byte `json:"script"`
Checksum uint32 `json:"checksum"`
}
// Header represents a File header.
type Header struct {
Magic uint32 `json:"magic"`
Compiler string `json:"compiler"`
}
// NewFile returns a new NEF3 file with the script specified.
func NewFile(script []byte) (*File, error) {
file := &File{
Header: Header{
Magic: Magic,
Compiler: "neo-go-" + config.Version,
},
Tokens: []MethodToken{},
Script: script,
}
if len(file.Compiler) > compilerFieldSize {
return nil, errors.New("too long compiler field")
}
file.Checksum = file.CalculateChecksum()
return file, nil
}
// EncodeBinary implements the io.Serializable interface.
func (h *Header) EncodeBinary(w *io.BinWriter) {
w.WriteU32LE(h.Magic)
if len(h.Compiler) > compilerFieldSize {
w.Err = errors.New("invalid compiler name length")
return
}
var b = make([]byte, compilerFieldSize)
copy(b, []byte(h.Compiler))
w.WriteBytes(b)
}
// DecodeBinary implements the io.Serializable interface.
func (h *Header) DecodeBinary(r *io.BinReader) {
h.Magic = r.ReadU32LE()
if h.Magic != Magic {
r.Err = errors.New("invalid Magic")
return
}
buf := make([]byte, compilerFieldSize)
r.ReadBytes(buf)
buf = bytes.TrimRightFunc(buf, func(r rune) bool {
return r == 0
})
h.Compiler = string(buf)
}
// CalculateChecksum returns first 4 bytes of double-SHA256(Header) converted to uint32.
func (n *File) CalculateChecksum() uint32 {
bb, err := n.Bytes()
if err != nil {
panic(err)
}
return binary.LittleEndian.Uint32(hash.Checksum(bb[:len(bb)-4]))
}
// EncodeBinary implements the io.Serializable interface.
func (n *File) EncodeBinary(w *io.BinWriter) {
n.Header.EncodeBinary(w)
if len(n.Source) > MaxSourceURLLength {
w.Err = errors.New("source url too long")
return
}
w.WriteString(n.Source)
w.WriteB(0)
w.WriteArray(n.Tokens)
w.WriteU16LE(0)
w.WriteVarBytes(n.Script)
w.WriteU32LE(n.Checksum)
}
var errInvalidReserved = errors.New("reserved bytes must be 0")
// DecodeBinary implements the io.Serializable interface.
func (n *File) DecodeBinary(r *io.BinReader) {
n.Header.DecodeBinary(r)
n.Source = r.ReadString(MaxSourceURLLength)
reservedB := r.ReadB()
if r.Err == nil && reservedB != 0 {
r.Err = errInvalidReserved
return
}
r.ReadArray(&n.Tokens)
reserved := r.ReadU16LE()
if r.Err == nil && reserved != 0 {
r.Err = errInvalidReserved
return
}
n.Script = r.ReadVarBytes(MaxScriptLength)
if r.Err == nil && len(n.Script) == 0 {
r.Err = errors.New("empty script")
return
}
n.Checksum = r.ReadU32LE()
checksum := n.CalculateChecksum()
if r.Err == nil && checksum != n.Checksum {
r.Err = errors.New("checksum verification failure")
return
}
}
// Bytes returns a byte array with a serialized NEF File.
func (n File) Bytes() ([]byte, error) {
buf := io.NewBufBinWriter()
n.EncodeBinary(buf.BinWriter)
if buf.Err != nil {
return nil, buf.Err
}
return buf.Bytes(), nil
}
// FileFromBytes returns a NEF File deserialized from the given bytes.
func FileFromBytes(source []byte) (File, error) {
result := File{}
r := io.NewBinReaderFromBuf(source)
result.DecodeBinary(r)
if r.Err != nil {
return result, r.Err
}
return result, nil
}