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 }