Skip to content

Commit

Permalink
Script header (#903)
Browse files Browse the repository at this point in the history
* Script header

* Script header draft

* Clean files

* Typo

* Rename class and magic header

* Clean hash

* UT

* Erik's suggestions

* Change name

* Typo

* Check magic

* 64 to 32

* Nef file only for neo-cli

* Real Checksum

* Clean

* Clean again

* Remove code

* Update neo/SmartContract/NefFile.cs

Co-Authored-By: Igor Machado Coelho <igor.machado@gmail.com>

* Enum to const

* Remove unnecessary change

* Apply Erik's suggestions
  • Loading branch information
shargon authored Jul 29, 2019
1 parent a9f46bc commit f4adfac
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 0 deletions.
75 changes: 75 additions & 0 deletions neo.UnitTests/UT_NefFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.Cryptography;
using Neo.IO;
using Neo.SmartContract;
using System;

namespace Neo.UnitTests
{
[TestClass]
public class UT_NefFile
{
[TestMethod]
public void ParseTest()
{
var file = new NefFile()
{
Compiler = "".PadLeft(32, ' '),
Version = new Version(1, 2, 3, 4),
Script = new byte[] { 0x01, 0x02, 0x03 }
};

file.ScriptHash = file.Script.ToScriptHash();
file.CheckSum = NefFile.ComputeChecksum(file);

var data = file.ToArray();
file = data.AsSerializable<NefFile>();

Assert.AreEqual("".PadLeft(32, ' '), file.Compiler);
Assert.AreEqual(new Version(1, 2, 3, 4), file.Version);
Assert.AreEqual(file.Script.ToScriptHash(), file.ScriptHash);
CollectionAssert.AreEqual(new byte[] { 0x01, 0x02, 0x03 }, file.Script);
}

[TestMethod]
public void LimitTest()
{
var file = new NefFile()
{
Compiler = "".PadLeft(byte.MaxValue, ' '),
Version = new Version(1, 2, 3, 4),
Script = new byte[1024 * 1024],
ScriptHash = new byte[1024 * 1024].ToScriptHash(),
CheckSum = 0
};

// Wrong compiler

Assert.ThrowsException<ArgumentException>(() => file.ToArray());

// Wrong script

file.Compiler = "";
file.Script = new byte[(1024 * 1024) + 1];
file.ScriptHash = file.Script.ToScriptHash();
var data = file.ToArray();

Assert.ThrowsException<FormatException>(() => data.AsSerializable<NefFile>());

// Wrong script hash

file.Script = new byte[1024 * 1024];
data = file.ToArray();

Assert.ThrowsException<FormatException>(() => data.AsSerializable<NefFile>());

// Wrong checksum

file.Script = new byte[1024];
data = file.ToArray();
file.CheckSum = NefFile.ComputeChecksum(file) + 1;

Assert.ThrowsException<FormatException>(() => data.AsSerializable<NefFile>());
}
}
}
129 changes: 129 additions & 0 deletions neo/SmartContract/NefFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using Neo.Cryptography;
using Neo.IO;
using System;
using System.IO;

namespace Neo.SmartContract
{
/// <summary>
/// +------------+-----------+------------------------------------------------------------+
/// | Field | Length | Comment |
/// +------------+-----------+------------------------------------------------------------+
/// | Magic | 4 bytes | Magic header |
/// | Compiler | 32 bytes | Compiler used |
/// | Version | 16 bytes | Compiler version (Mayor, Minor, Build, Version) |
/// | ScriptHash | 20 bytes | ScriptHash for the script |
/// +------------+-----------+------------------------------------------------------------+
/// | Checksum | 4 bytes | Sha256 of the header (CRC) |
/// +------------+-----------+------------------------------------------------------------+
/// | Script | Var bytes | Var bytes for the payload |
/// +------------+-----------+------------------------------------------------------------+
/// </summary>
public class NefFile : ISerializable
{
/// <summary>
/// NEO Executable Format 3 (NEF3)
/// </summary>
private const uint Magic = 0x3346454E;

/// <summary>
/// Compiler
/// </summary>
public string Compiler { get; set; }

/// <summary>
/// Version
/// </summary>
public Version Version { get; set; }

/// <summary>
/// Script Hash
/// </summary>
public UInt160 ScriptHash { get; set; }

/// <summary>
/// Checksum
/// </summary>
public uint CheckSum { get; set; }

/// <summary>
/// Script
/// </summary>
public byte[] Script { get; set; }

private const int HeaderSize =
sizeof(uint) + // Magic
32 + // Compiler
(sizeof(int) * 4) + // Version
UInt160.Length + // ScriptHash
sizeof(uint); // Checksum

public int Size =>
HeaderSize + // Header
Script.GetVarSize(); // Script

public void Serialize(BinaryWriter writer)
{
writer.Write(Magic);
writer.WriteFixedString(Compiler, 32);

// Version
writer.Write(Version.Major);
writer.Write(Version.Minor);
writer.Write(Version.Build);
writer.Write(Version.Revision);

writer.Write(ScriptHash);
writer.Write(CheckSum);
writer.WriteVarBytes(Script ?? new byte[0]);
}

public void Deserialize(BinaryReader reader)
{
if (reader.ReadUInt32() != Magic)
{
throw new FormatException("Wrong magic");
}

Compiler = reader.ReadFixedString(32);
Version = new Version(reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32());
ScriptHash = reader.ReadSerializable<UInt160>();
CheckSum = reader.ReadUInt32();

if (CheckSum != ComputeChecksum(this))
{
throw new FormatException("CRC verification fail");
}

Script = reader.ReadVarBytes(1024 * 1024);

if (Script.ToScriptHash() != ScriptHash)
{
throw new FormatException("ScriptHash is different");
}
}

/// <summary>
/// Compute checksum for a file
/// </summary>
/// <param name="file">File</param>
/// <returns>Return checksum</returns>
public static uint ComputeChecksum(NefFile file)
{
using (var ms = new MemoryStream())
using (var wr = new BinaryWriter(ms))
{
file.Serialize(wr);
wr.Flush();

// Read header without CRC

var buffer = new byte[HeaderSize - sizeof(uint)];
ms.Seek(0, SeekOrigin.Begin);
ms.Read(buffer, 0, buffer.Length);

return BitConverter.ToUInt32(buffer.Sha256(), 0);
}
}
}
}

0 comments on commit f4adfac

Please sign in to comment.