github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/smartcontract/nef/nef.go (about)

     1  package nef
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  
     9  	"github.com/nspcc-dev/neo-go/pkg/config"
    10  	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
    11  	"github.com/nspcc-dev/neo-go/pkg/io"
    12  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    13  )
    14  
    15  // NEO Executable Format 3 (NEF3)
    16  // Standard: https://github.com/neo-project/proposals/pull/121/files
    17  // Implementation: https://github.com/neo-project/neo/blob/v3.0.0-preview2/src/neo/SmartContract/NefFile.cs#L8
    18  // +------------+-----------+------------------------------------------------------------+
    19  // |   Field    |  Length   |                          Comment                           |
    20  // +------------+-----------+------------------------------------------------------------+
    21  // | Magic      | 4 bytes   | Magic header                                               |
    22  // | Compiler   | 64 bytes  | Compiler used and it's version                             |
    23  // | Source     | Var bytes | Source file URL.                                           |
    24  // +------------+-----------+------------------------------------------------------------+
    25  // | Reserved   | 1 byte    | Reserved for extensions. Must be 0.                        |
    26  // | Tokens     | Var array | List of method tokens                                      |
    27  // | Reserved   | 2-bytes   | Reserved for extensions. Must be 0.                        |
    28  // | Script     | Var bytes | Var bytes for the payload                                  |
    29  // +------------+-----------+------------------------------------------------------------+
    30  // | Checksum   | 4 bytes   | First four bytes of double SHA256 hash of the header       |
    31  // +------------+-----------+------------------------------------------------------------+
    32  
    33  const (
    34  	// Magic is a magic File header constant.
    35  	Magic uint32 = 0x3346454E
    36  	// MaxSourceURLLength is the maximum allowed source URL length.
    37  	MaxSourceURLLength = 256
    38  	// compilerFieldSize is the length of `Compiler` File header field in bytes.
    39  	compilerFieldSize = 64
    40  )
    41  
    42  // File represents a compiled contract file structure according to the NEF3 standard.
    43  type File struct {
    44  	Header
    45  	Source   string        `json:"source"`
    46  	Tokens   []MethodToken `json:"tokens"`
    47  	Script   []byte        `json:"script"`
    48  	Checksum uint32        `json:"checksum"`
    49  }
    50  
    51  // Header represents a File header.
    52  type Header struct {
    53  	Magic    uint32 `json:"magic"`
    54  	Compiler string `json:"compiler"`
    55  }
    56  
    57  // NewFile returns a new NEF3 file with the script specified.
    58  func NewFile(script []byte) (*File, error) {
    59  	file := &File{
    60  		Header: Header{
    61  			Magic:    Magic,
    62  			Compiler: "neo-go-" + config.Version,
    63  		},
    64  		Tokens: []MethodToken{},
    65  		Script: script,
    66  	}
    67  	if len(file.Compiler) > compilerFieldSize {
    68  		return nil, errors.New("too long compiler field")
    69  	}
    70  	file.Checksum = file.CalculateChecksum()
    71  	return file, nil
    72  }
    73  
    74  // EncodeBinary implements the io.Serializable interface.
    75  func (h *Header) EncodeBinary(w *io.BinWriter) {
    76  	w.WriteU32LE(h.Magic)
    77  	if len(h.Compiler) > compilerFieldSize {
    78  		w.Err = errors.New("invalid compiler name length")
    79  		return
    80  	}
    81  	var b = make([]byte, compilerFieldSize)
    82  	copy(b, []byte(h.Compiler))
    83  	w.WriteBytes(b)
    84  }
    85  
    86  // DecodeBinary implements the io.Serializable interface.
    87  func (h *Header) DecodeBinary(r *io.BinReader) {
    88  	h.Magic = r.ReadU32LE()
    89  	if h.Magic != Magic {
    90  		r.Err = errors.New("invalid Magic")
    91  		return
    92  	}
    93  	buf := make([]byte, compilerFieldSize)
    94  	r.ReadBytes(buf)
    95  	buf = bytes.TrimRightFunc(buf, func(r rune) bool {
    96  		return r == 0
    97  	})
    98  	h.Compiler = string(buf)
    99  }
   100  
   101  // CalculateChecksum returns first 4 bytes of double-SHA256(Header) converted to uint32.
   102  // CalculateChecksum doesn't perform the resulting serialized NEF size check, and return
   103  // the checksum irrespectively to the size limit constraint. It's a caller's duty to check
   104  // the resulting NEF size.
   105  func (n *File) CalculateChecksum() uint32 {
   106  	bb, err := n.BytesLong()
   107  	if err != nil {
   108  		panic(err)
   109  	}
   110  	return binary.LittleEndian.Uint32(hash.Checksum(bb[:len(bb)-4]))
   111  }
   112  
   113  // EncodeBinary implements the io.Serializable interface.
   114  func (n *File) EncodeBinary(w *io.BinWriter) {
   115  	n.Header.EncodeBinary(w)
   116  	if len(n.Source) > MaxSourceURLLength {
   117  		w.Err = errors.New("source url too long")
   118  		return
   119  	}
   120  	w.WriteString(n.Source)
   121  	w.WriteB(0)
   122  	w.WriteArray(n.Tokens)
   123  	w.WriteU16LE(0)
   124  	w.WriteVarBytes(n.Script)
   125  	w.WriteU32LE(n.Checksum)
   126  }
   127  
   128  var errInvalidReserved = errors.New("reserved bytes must be 0")
   129  
   130  // DecodeBinary implements the io.Serializable interface.
   131  func (n *File) DecodeBinary(r *io.BinReader) {
   132  	n.Header.DecodeBinary(r)
   133  	n.Source = r.ReadString(MaxSourceURLLength)
   134  	reservedB := r.ReadB()
   135  	if r.Err == nil && reservedB != 0 {
   136  		r.Err = errInvalidReserved
   137  		return
   138  	}
   139  	r.ReadArray(&n.Tokens)
   140  	reserved := r.ReadU16LE()
   141  	if r.Err == nil && reserved != 0 {
   142  		r.Err = errInvalidReserved
   143  		return
   144  	}
   145  	n.Script = r.ReadVarBytes(stackitem.MaxSize)
   146  	if r.Err == nil && len(n.Script) == 0 {
   147  		r.Err = errors.New("empty script")
   148  		return
   149  	}
   150  	n.Checksum = r.ReadU32LE()
   151  	checksum := n.CalculateChecksum()
   152  	if r.Err == nil && checksum != n.Checksum {
   153  		r.Err = errors.New("checksum verification failure")
   154  		return
   155  	}
   156  }
   157  
   158  // Bytes returns a byte array with a serialized NEF File. It performs the
   159  // resulting NEF file size check and returns an error if serialized slice length
   160  // exceeds [stackitem.MaxSize].
   161  func (n File) Bytes() ([]byte, error) {
   162  	return n.bytes(true)
   163  }
   164  
   165  // BytesLong returns a byte array with a serialized NEF File. It performs no
   166  // resulting slice check.
   167  func (n File) BytesLong() ([]byte, error) {
   168  	return n.bytes(false)
   169  }
   170  
   171  // bytes returns the serialized NEF File representation and performs the resulting
   172  // byte array size check if needed.
   173  func (n File) bytes(checkSize bool) ([]byte, error) {
   174  	buf := io.NewBufBinWriter()
   175  	n.EncodeBinary(buf.BinWriter)
   176  	if buf.Err != nil {
   177  		return nil, buf.Err
   178  	}
   179  	res := buf.Bytes()
   180  	if checkSize && len(res) > stackitem.MaxSize {
   181  		return nil, fmt.Errorf("serialized NEF size exceeds VM stackitem limits: %d bytes is allowed at max, got %d", stackitem.MaxSize, len(res))
   182  	}
   183  	return res, nil
   184  }
   185  
   186  // FileFromBytes returns a NEF File deserialized from the given bytes.
   187  func FileFromBytes(source []byte) (File, error) {
   188  	result := File{}
   189  	if len(source) > stackitem.MaxSize {
   190  		return result, fmt.Errorf("invalid NEF file size: expected %d at max, got %d", stackitem.MaxSize, len(source))
   191  	}
   192  	r := io.NewBinReaderFromBuf(source)
   193  	result.DecodeBinary(r)
   194  	if r.Err != nil {
   195  		return result, r.Err
   196  	}
   197  	return result, nil
   198  }