neoneo-go/pkg/smartcontract/nef/nef.go
2021-09-24 00:19:37 +03:00

174 lines
5.3 KiB
Go

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 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 File header.
type Header struct {
Magic uint32 `json:"magic"`
Compiler string `json:"compiler"`
}
// NewFile returns new NEF3 file with 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 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 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 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 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 byte array with 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 NEF File deserialized from 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
}