Skip to content

Commit

Permalink
Optimize ECPoint serialization (#3757)
Browse files Browse the repository at this point in the history
* Optimize PubKey

* Rename to ISerializableSpan

* @nan01ab feedback

* clean using
  • Loading branch information
shargon authored Feb 14, 2025
1 parent 6f10d3a commit c1a0582
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 46 deletions.
32 changes: 32 additions & 0 deletions src/Neo.IO/ISerializableSpan.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (C) 2015-2025 The Neo Project.
//
// ISerializableSpan.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using System;

namespace Neo.IO
{
/// <summary>
/// Represents NEO objects that can be serialized.
/// </summary>
public interface ISerializableSpan
{
/// <summary>
/// The size of the object in bytes after serialization.
/// </summary>
int Size { get; }

/// <summary>
/// Gets a ReadOnlySpan that represents the current value
/// Requires keeping the data returned by GetSpan consistent with the data generated by <see cref="ISerializable"/>.
/// </summary>
ReadOnlySpan<byte> GetSpan();
}
}
17 changes: 14 additions & 3 deletions src/Neo/Cryptography/ECC/ECPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
using System;
using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices;

namespace Neo.Cryptography.ECC
{
/// <summary>
/// Represents a (X,Y) coordinate pair for elliptic curve cryptography (ECC) structures.
/// </summary>
public class ECPoint : IComparable<ECPoint>, IEquatable<ECPoint>, ISerializable
public class ECPoint : IComparable<ECPoint>, IEquatable<ECPoint>, ISerializable, ISerializableSpan
{
internal ECFieldElement X, Y;
internal readonly ECCurve Curve;
Expand Down Expand Up @@ -187,10 +188,10 @@ public byte[] EncodePoint(bool commpressed)
{
if (_uncompressedPoint != null) return _uncompressedPoint;
data = new byte[65];
byte[] yBytes = Y.Value.ToByteArray(isUnsigned: true, isBigEndian: true);
var yBytes = Y.Value.ToByteArray(isUnsigned: true, isBigEndian: true);
Buffer.BlockCopy(yBytes, 0, data, 65 - yBytes.Length, yBytes.Length);
}
byte[] xBytes = X.Value.ToByteArray(isUnsigned: true, isBigEndian: true);
var xBytes = X.Value.ToByteArray(isUnsigned: true, isBigEndian: true);
Buffer.BlockCopy(xBytes, 0, data, 33 - xBytes.Length, xBytes.Length);
data[0] = commpressed ? Y.Value.IsEven ? (byte)0x02 : (byte)0x03 : (byte)0x04;
if (commpressed) _compressedPoint = data;
Expand Down Expand Up @@ -350,6 +351,16 @@ void ISerializable.Serialize(BinaryWriter writer)
writer.Write(EncodePoint(true));
}

/// <summary>
/// Gets a ReadOnlySpan that represents the current value.
/// </summary>
/// <returns>A ReadOnlySpan that represents the current value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<byte> GetSpan()
{
return EncodePoint(true).AsSpan();
}

public override string ToString()
{
return EncodePoint(true).ToHexString();
Expand Down
26 changes: 1 addition & 25 deletions src/Neo/SmartContract/KeyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,36 +74,12 @@ public KeyBuilder Add(ReadOnlySpan<byte> key)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public KeyBuilder Add(byte[] key) => Add(key.AsSpan());

/// <summary>
/// Adds part of the key to the builder.
/// </summary>
/// <param name="key">Part of the key represented by a <see cref="UInt160"/>.</param>
/// <returns>A reference to this instance after the add operation has completed.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public KeyBuilder Add(UInt160 key) => Add(key.GetSpan());

/// <summary>
/// Adds part of the key to the builder.
/// </summary>
/// <param name="key">Part of the key represented by a <see cref="UInt256"/>.</param>
/// <returns>A reference to this instance after the add operation has completed.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public KeyBuilder Add(UInt256 key) => Add(key.GetSpan());

/// <summary>
/// Adds part of the key to the builder.
/// </summary>
/// <param name="key">Part of the key.</param>
/// <returns>A reference to this instance after the add operation has completed.</returns>
public KeyBuilder Add(ISerializable key)
{
using (BinaryWriter writer = new(_stream, Utility.StrictUTF8, true))
{
key.Serialize(writer);
writer.Flush();
}
return this;
}
public KeyBuilder Add(ISerializableSpan key) => Add(key.GetSpan());

/// <summary>
/// Adds part of the key to the builder in BigEndian.
Expand Down
2 changes: 1 addition & 1 deletion src/Neo/UInt160.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace Neo
/// Represents a 160-bit unsigned integer.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = 20)]
public class UInt160 : IComparable<UInt160>, IEquatable<UInt160>, ISerializable
public class UInt160 : IComparable<UInt160>, IEquatable<UInt160>, ISerializable, ISerializableSpan
{
/// <summary>
/// The length of <see cref="UInt160"/> values.
Expand Down
2 changes: 1 addition & 1 deletion src/Neo/UInt256.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace Neo
/// Represents a 256-bit unsigned integer.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = 32)]
public class UInt256 : IComparable<UInt256>, IEquatable<UInt256>, ISerializable
public class UInt256 : IComparable<UInt256>, IEquatable<UInt256>, ISerializable, ISerializableSpan
{
/// <summary>
/// The length of <see cref="UInt256"/> values.
Expand Down
2 changes: 1 addition & 1 deletion tests/Neo.Plugins.OracleService.Tests/TestUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static class TestUtils
public static readonly UInt160 MultisigScriptHash = MultisigScript.ToScriptHash();
public static readonly string MultisigAddress = MultisigScriptHash.ToAddress(ProtocolSettings.Default.AddressVersion);

public static StorageKey CreateStorageKey(this NativeContract contract, byte prefix, ISerializable key)
public static StorageKey CreateStorageKey(this NativeContract contract, byte prefix, ISerializableSpan key)
{
var k = new KeyBuilder(contract.Id, prefix);
if (key != null) k = k.Add(key);
Expand Down
10 changes: 7 additions & 3 deletions tests/Neo.UnitTests/Cryptography/ECC/UT_ECPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public void TestEncodePoint()
[TestMethod]
public void TestEquals()
{
ECPoint point = ECCurve.Secp256k1.G;
var point = ECCurve.Secp256k1.G;
Assert.IsTrue(point.Equals(point));
Assert.IsFalse(point.Equals(null));

Expand Down Expand Up @@ -309,11 +309,15 @@ public void TestDeserialize()
[TestMethod]
public void TestSerialize()
{
MemoryStream stream = new MemoryStream();
ECPoint point = new ECPoint(null, null, ECCurve.Secp256k1);
var stream = new MemoryStream();
var point = new ECPoint(null, null, ECCurve.Secp256k1);
ISerializable serializable = point;
serializable.Serialize(new BinaryWriter(stream));
CollectionAssert.AreEqual(new byte[] { 0 }, stream.ToArray());

CollectionAssert.AreEqual(point.GetSpan().ToArray(), stream.ToArray());
point = ECCurve.Secp256r1.G;
CollectionAssert.AreEqual(point.GetSpan().ToArray(), point.ToArray());
}

[TestMethod]
Expand Down
8 changes: 4 additions & 4 deletions tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ public void Test()
Assert.AreEqual("0100000002", key.ToArray().ToHexString());

key = new KeyBuilder(1, 2);
key = key.Add(new byte[] { 3, 4 });
key = key.Add([3, 4]);
Assert.AreEqual("01000000020304", key.ToArray().ToHexString());

key = new KeyBuilder(1, 2);
key = key.Add(new byte[] { 3, 4 });
key = key.Add([3, 4]);
key = key.Add(UInt160.Zero);
Assert.AreEqual("010000000203040000000000000000000000000000000000000000", key.ToArray().ToHexString());

Expand Down Expand Up @@ -91,7 +91,7 @@ public void TestAddUInt()
Assert.AreEqual("0100000002000102030405060708090a0b0c0d0e0f10111213", key.ToArray().ToHexString());

var key2 = new KeyBuilder(1, 2);
key2 = key2.Add((ISerializable)(new UInt160(value)));
key2 = key2.Add((ISerializableSpan)new UInt160(value));

// It must be same before and after optimization.
Assert.AreEqual(key.ToArray().ToHexString(), key2.ToArray().ToHexString());
Expand All @@ -104,7 +104,7 @@ public void TestAddUInt()
Assert.AreEqual("0100000002000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", key.ToArray().ToHexString());

key2 = new KeyBuilder(1, 2);
key2 = key2.Add((ISerializable)(new UInt256(value)));
key2 = key2.Add((ISerializableSpan)new UInt256(value));

// It must be same before and after optimization.
Assert.AreEqual(key.ToArray().ToHexString(), key2.ToArray().ToHexString());
Expand Down
16 changes: 8 additions & 8 deletions tests/Neo.UnitTests/TestUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,23 @@ namespace Neo.UnitTests
{
public static partial class TestUtils
{
public static readonly Random TestRandom = new Random(1337); // use fixed seed for guaranteed determinism
public static readonly Random TestRandom = new(1337); // use fixed seed for guaranteed determinism

public static UInt256 RandomUInt256()
{
byte[] data = new byte[32];
var data = new byte[32];
TestRandom.NextBytes(data);
return new UInt256(data);
}

public static UInt160 RandomUInt160()
{
byte[] data = new byte[20];
var data = new byte[20];
TestRandom.NextBytes(data);
return new UInt160(data);
}

public static StorageKey CreateStorageKey(this NativeContract contract, byte prefix, ISerializable key = null)
public static StorageKey CreateStorageKey(this NativeContract contract, byte prefix, ISerializableSpan key = null)
{
var k = new KeyBuilder(contract.Id, prefix);
if (key != null) k = k.Add(key);
Expand All @@ -54,9 +54,9 @@ public static StorageKey CreateStorageKey(this NativeContract contract, byte pre

public static byte[] GetByteArray(int length, byte firstByte)
{
byte[] array = new byte[length];
var array = new byte[length];
array[0] = firstByte;
for (int i = 1; i < length; i++)
for (var i = 1; i < length; i++)
{
array[i] = 0x20;
}
Expand All @@ -65,7 +65,7 @@ public static byte[] GetByteArray(int length, byte firstByte)

public static NEP6Wallet GenerateTestWallet(string password)
{
JObject wallet = new JObject();
var wallet = new JObject();
wallet["name"] = "noname";
wallet["version"] = new Version("1.0").ToString();
wallet["scrypt"] = new ScryptParameters(2, 1, 1).ToJson();
Expand Down Expand Up @@ -103,7 +103,7 @@ public static void StorageItemAdd(DataCache snapshot, int id, byte[] keyValue, b

public static void FillMemoryPool(DataCache snapshot, NeoSystem system, NEP6Wallet wallet, WalletAccount account)
{
for (int i = 0; i < system.Settings.MemoryPoolMaxTransactions; i++)
for (var i = 0; i < system.Settings.MemoryPoolMaxTransactions; i++)
{
var tx = CreateValidTx(snapshot, wallet, account);
system.MemPool.TryAdd(tx, snapshot);
Expand Down

0 comments on commit c1a0582

Please sign in to comment.