diff --git a/src/Neo.VM/Utility.cs b/src/Neo.VM/Utility.cs index 940c98d7d9..32f6f157dd 100644 --- a/src/Neo.VM/Utility.cs +++ b/src/Neo.VM/Utility.cs @@ -16,7 +16,7 @@ namespace Neo.VM { - internal static class Utility + public static class Utility { public static Encoding StrictUTF8 { get; } @@ -34,11 +34,11 @@ public static BigInteger ModInverse(this BigInteger value, BigInteger modulus) BigInteger r = value, old_r = modulus, s = 1, old_s = 0; while (r > 0) { - BigInteger q = old_r / r; + var q = old_r / r; (old_r, r) = (r, old_r % r); (old_s, s) = (s, old_s - q * s); } - BigInteger result = old_s % modulus; + var result = old_s % modulus; if (result < 0) result += modulus; if (!(value * result % modulus).IsOne) throw new InvalidOperationException(); return result; @@ -61,17 +61,33 @@ public static BigInteger Sqrt(this BigInteger value) return z; } -#if !NET5_0_OR_GREATER - static int GetBitLength(this BigInteger i) + /// + /// Gets the number of bits required for shortest two's complement representation of the current instance without the sign bit. + /// + /// The minimum non-negative number of bits in two's complement notation without the sign bit. + /// This method returns 0 if the value of current object is equal to or . For positive integers the return value is equal to the ordinary binary representation string length. + public static long GetBitLength(this BigInteger value) { - byte[] b = i.ToByteArray(); - return (b.Length - 1) * 8 + BitLen(i.Sign > 0 ? b[b.Length - 1] : 255 - b[b.Length - 1]); +#if NET5_0_OR_GREATER + return value.GetBitLength(); +#else + if (value == 0 || value == BigInteger.MinusOne) return 0; + + // Note: This method is imprecise and might not work as expected with integers larger than 256 bits. + var b = value.ToByteArray(); + if (b.Length == 1 || (b.Length == 2 && b[1] == 0)) + { + return BitLen(value.Sign > 0 ? b[0] : (byte)(255 - b[0])); + } + return (b.Length - 1) * 8 + BitLen(value.Sign > 0 ? b[^1] : 255 - b[^1]); +#endif } +#if !NET5_0_OR_GREATER [MethodImpl(MethodImplOptions.AggressiveInlining)] static int BitLen(int w) { - return (w < 1 << 15 ? (w < 1 << 7 + return w < 1 << 15 ? (w < 1 << 7 ? (w < 1 << 3 ? (w < 1 << 1 ? (w < 1 << 0 ? (w < 0 ? 32 : 0) : 1) : (w < 1 << 2 ? 2 : 3)) : (w < 1 << 5 @@ -83,7 +99,7 @@ static int BitLen(int w) ? (w < 1 << 17 ? (w < 1 << 16 ? 16 : 17) : (w < 1 << 18 ? 18 : 19)) : (w < 1 << 21 ? (w < 1 << 20 ? 20 : 21) : (w < 1 << 22 ? 22 : 23))) : (w < 1 << 27 ? (w < 1 << 25 ? (w < 1 << 24 ? 24 : 25) : (w < 1 << 26 ? 26 : 27)) - : (w < 1 << 29 ? (w < 1 << 28 ? 28 : 29) : (w < 1 << 30 ? 30 : 31))))); + : (w < 1 << 29 ? (w < 1 << 28 ? 28 : 29) : (w < 1 << 30 ? 30 : 31)))); } #endif } diff --git a/src/Neo/BigDecimal.cs b/src/Neo/BigDecimal.cs index 1658ea9c8e..05cdf86626 100644 --- a/src/Neo/BigDecimal.cs +++ b/src/Neo/BigDecimal.cs @@ -55,7 +55,7 @@ public BigDecimal(BigInteger value, byte decimals) public unsafe BigDecimal(decimal value) { Span span = stackalloc int[4]; - decimal.GetBits(value, span); + span = decimal.GetBits(value); fixed (int* p = span) { ReadOnlySpan buffer = new(p, 16); @@ -73,7 +73,7 @@ public unsafe BigDecimal(decimal value) public unsafe BigDecimal(decimal value, byte decimals) { Span span = stackalloc int[4]; - decimal.GetBits(value, span); + span = decimal.GetBits(value); fixed (int* p = span) { ReadOnlySpan buffer = new(p, 16); diff --git a/src/Neo/Cryptography/ECC/ECCurve.cs b/src/Neo/Cryptography/ECC/ECCurve.cs index 7da6c5f3b8..b228f7ab31 100644 --- a/src/Neo/Cryptography/ECC/ECCurve.cs +++ b/src/Neo/Cryptography/ECC/ECCurve.cs @@ -37,7 +37,7 @@ public class ECCurve private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G) { this.Q = Q; - this.ExpectedECPointLength = ((int)Q.GetBitLength() + 7) / 8; + this.ExpectedECPointLength = ((int)VM.Utility.GetBitLength(Q) + 7) / 8; this.A = new ECFieldElement(A, this); this.B = new ECFieldElement(B, this); this.N = N; diff --git a/src/Neo/Cryptography/ECC/ECFieldElement.cs b/src/Neo/Cryptography/ECC/ECFieldElement.cs index c00a779315..e08b5bb71f 100644 --- a/src/Neo/Cryptography/ECC/ECFieldElement.cs +++ b/src/Neo/Cryptography/ECC/ECFieldElement.cs @@ -54,7 +54,7 @@ public bool Equals(ECFieldElement other) private static BigInteger[] FastLucasSequence(BigInteger p, BigInteger P, BigInteger Q, BigInteger k) { - int n = (int)k.GetBitLength(); + int n = (int)VM.Utility.GetBitLength(k); int s = k.GetLowestSetBit(); BigInteger Uh = 1; @@ -126,7 +126,7 @@ public ECFieldElement Sqrt() BigInteger P; do { - P = rand.NextBigInteger((int)curve.Q.GetBitLength()); + P = rand.NextBigInteger((int)VM.Utility.GetBitLength(curve.Q)); } while (P >= curve.Q || BigInteger.ModPow(P * P - fourQ, legendreExponent, curve.Q) != qMinusOne); BigInteger[] result = FastLucasSequence(curve.Q, P, Q, k); diff --git a/src/Neo/Cryptography/ECC/ECPoint.cs b/src/Neo/Cryptography/ECC/ECPoint.cs index 6b74232c81..b2d44bf6cf 100644 --- a/src/Neo/Cryptography/ECC/ECPoint.cs +++ b/src/Neo/Cryptography/ECC/ECPoint.cs @@ -237,7 +237,7 @@ public override int GetHashCode() internal static ECPoint Multiply(ECPoint p, BigInteger k) { // floor(log2(k)) - int m = (int)k.GetBitLength(); + int m = (int)VM.Utility.GetBitLength(k); // width of the Window NAF sbyte width; @@ -391,7 +391,7 @@ internal ECPoint Twice() private static sbyte[] WindowNaf(sbyte width, BigInteger k) { - sbyte[] wnaf = new sbyte[k.GetBitLength() + 1]; + sbyte[] wnaf = new sbyte[VM.Utility.GetBitLength(k) + 1]; short pow2wB = (short)(1 << width); int i = 0; int length = 0; diff --git a/src/Neo/Cryptography/Helper.cs b/src/Neo/Cryptography/Helper.cs index 2f0ad0ce35..840b6ebb7b 100644 --- a/src/Neo/Cryptography/Helper.cs +++ b/src/Neo/Cryptography/Helper.cs @@ -18,6 +18,7 @@ using System; using System.Buffers.Binary; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; using static Neo.Helper; @@ -99,7 +100,7 @@ public static byte[] Murmur128(this byte[] value, uint seed) /// The computed hash code. public static byte[] Murmur128(this ReadOnlySpan value, uint seed) { - byte[] buffer = GC.AllocateUninitializedArray(16); + byte[] buffer = new byte[16]; using Murmur128 murmur = new(seed); murmur.TryComputeHash(value, buffer, out _); return buffer; @@ -239,5 +240,29 @@ internal static bool Test(this BloomFilter filter, Transaction tx) return true; return false; } + + /// + /// Rotates the specified value left by the specified number of bits. + /// Similar in behavior to the x86 instruction ROL. + /// + /// The value to rotate. + /// The number of bits to rotate by. + /// Any value outside the range [0..31] is treated as congruent mod 32. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateLeft(uint value, int offset) + => (value << offset) | (value >> (32 - offset)); + + /// + /// Rotates the specified value left by the specified number of bits. + /// Similar in behavior to the x86 instruction ROL. + /// + /// The value to rotate. + /// The number of bits to rotate by. + /// Any value outside the range [0..63] is treated as congruent mod 64. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong RotateLeft(ulong value, int offset) + => (value << offset) | (value >> (64 - offset)); } } diff --git a/src/Neo/Cryptography/Murmur128.cs b/src/Neo/Cryptography/Murmur128.cs index 18e67a91ac..3ee258b4a2 100644 --- a/src/Neo/Cryptography/Murmur128.cs +++ b/src/Neo/Cryptography/Murmur128.cs @@ -63,19 +63,19 @@ protected override void HashCore(ReadOnlySpan source) { ulong k1 = BinaryPrimitives.ReadUInt64LittleEndian(source[i..]); k1 *= c1; - k1 = BitOperations.RotateLeft(k1, r1); + k1 = Helper.RotateLeft(k1, r1); k1 *= c2; H1 ^= k1; - H1 = BitOperations.RotateLeft(H1, 27); + H1 = Helper.RotateLeft(H1, 27); H1 += H2; H1 = H1 * m + n1; ulong k2 = BinaryPrimitives.ReadUInt64LittleEndian(source[(i + 8)..]); k2 *= c2; - k2 = BitOperations.RotateLeft(k2, r2); + k2 = Helper.RotateLeft(k2, r2); k2 *= c1; H2 ^= k2; - H2 = BitOperations.RotateLeft(H2, 31); + H2 = Helper.RotateLeft(H2, 31); H2 += H1; H2 = H2 * m + n2; } @@ -102,14 +102,14 @@ protected override void HashCore(ReadOnlySpan source) case 1: remainingBytesL ^= (ulong)source[alignedLength] << 0; break; } - H2 ^= BitOperations.RotateLeft(remainingBytesH * c2, r2) * c1; - H1 ^= BitOperations.RotateLeft(remainingBytesL * c1, r1) * c2; + H2 ^= Helper.RotateLeft(remainingBytesH * c2, r2) * c1; + H1 ^= Helper.RotateLeft(remainingBytesL * c1, r1) * c2; } } protected override byte[] HashFinal() { - byte[] buffer = GC.AllocateUninitializedArray(sizeof(ulong) * 2); + byte[] buffer = new byte[sizeof(ulong) * 2]; TryHashFinal(buffer, out _); return buffer; } diff --git a/src/Neo/Cryptography/Murmur32.cs b/src/Neo/Cryptography/Murmur32.cs index a48c5d6750..8feaa92d37 100644 --- a/src/Neo/Cryptography/Murmur32.cs +++ b/src/Neo/Cryptography/Murmur32.cs @@ -56,10 +56,10 @@ protected override void HashCore(ReadOnlySpan source) { uint k = BinaryPrimitives.ReadUInt32LittleEndian(source); k *= c1; - k = BitOperations.RotateLeft(k, r1); + k = Helper.RotateLeft(k, r1); k *= c2; hash ^= k; - hash = BitOperations.RotateLeft(hash, r2); + hash = Helper.RotateLeft(hash, r2); hash = hash * m + n; } if (source.Length > 0) @@ -72,7 +72,7 @@ protected override void HashCore(ReadOnlySpan source) case 1: remainingBytes ^= source[0]; break; } remainingBytes *= c1; - remainingBytes = BitOperations.RotateLeft(remainingBytes, r1); + remainingBytes = Helper.RotateLeft(remainingBytes, r1); remainingBytes *= c2; hash ^= remainingBytes; } @@ -80,7 +80,7 @@ protected override void HashCore(ReadOnlySpan source) protected override byte[] HashFinal() { - byte[] buffer = GC.AllocateUninitializedArray(sizeof(uint)); + byte[] buffer = new byte[sizeof(uint)]; TryHashFinal(buffer, out _); return buffer; } diff --git a/src/Neo/IO/Helper.cs b/src/Neo/IO/Helper.cs index 7449dbfab9..2e6d362ee1 100644 --- a/src/Neo/IO/Helper.cs +++ b/src/Neo/IO/Helper.cs @@ -102,7 +102,7 @@ public static ISerializable AsSerializable(this ReadOnlyMemory value, Type public static ReadOnlyMemory CompressLz4(this ReadOnlySpan data) { int maxLength = LZ4Codec.MaximumOutputSize(data.Length); - byte[] buffer = GC.AllocateUninitializedArray(sizeof(uint) + maxLength); + byte[] buffer = new byte[sizeof(uint) + maxLength]; BinaryPrimitives.WriteInt32LittleEndian(buffer, data.Length); int length = LZ4Codec.Encode(data, buffer.AsSpan(sizeof(uint))); return buffer.AsMemory(0, sizeof(uint) + length); @@ -118,7 +118,7 @@ public static byte[] DecompressLz4(this ReadOnlySpan data, int maxOutput) { int length = BinaryPrimitives.ReadInt32LittleEndian(data); if (length < 0 || length > maxOutput) throw new FormatException(); - byte[] result = GC.AllocateUninitializedArray(length); + byte[] result = new byte[length]; if (LZ4Codec.Decode(data[4..], result) != length) throw new FormatException(); return result; diff --git a/src/Neo/Neo.csproj b/src/Neo/Neo.csproj index de628a66de..234fbc62c6 100644 --- a/src/Neo/Neo.csproj +++ b/src/Neo/Neo.csproj @@ -1,7 +1,7 @@ - net7.0 + netstandard2.1;net7.0 true NEO;AntShares;Blockchain;Smart Contract diff --git a/src/Neo/Network/UPnP.cs b/src/Neo/Network/UPnP.cs index a80e6e69fb..a0cd0ce78a 100644 --- a/src/Neo/Network/UPnP.cs +++ b/src/Neo/Network/UPnP.cs @@ -183,8 +183,8 @@ private static XmlDocument SOAPRequest(string url, string soap, string function) request.Headers.Add("Content-Type", "text/xml; charset=\"utf-8\""); request.Content = new StringContent(req); using HttpClient http = new(); - using HttpResponseMessage response = http.Send(request); - using Stream stream = response.EnsureSuccessStatusCode().Content.ReadAsStream(); + using HttpResponseMessage response = http.SendAsync(request).GetAwaiter().GetResult(); + using Stream stream = response.EnsureSuccessStatusCode().Content.ReadAsStreamAsync().GetAwaiter().GetResult(); XmlDocument resp = new() { XmlResolver = null }; resp.Load(stream); return resp; diff --git a/src/Neo/SmartContract/ContractTask.cs b/src/Neo/SmartContract/ContractTask.cs index 523b8b115a..6df3627c37 100644 --- a/src/Neo/SmartContract/ContractTask.cs +++ b/src/Neo/SmartContract/ContractTask.cs @@ -32,19 +32,15 @@ public ContractTask() } protected virtual ContractTaskAwaiter CreateAwaiter() => new(); - public virtual ContractTaskAwaiter GetAwaiter() => awaiter; - public virtual object GetResult() => null; } [AsyncMethodBuilder(typeof(ContractTaskMethodBuilder<>))] class ContractTask : ContractTask { - protected override ContractTaskAwaiter CreateAwaiter() => new(); - - public override ContractTaskAwaiter GetAwaiter() => (ContractTaskAwaiter)base.GetAwaiter(); - - public override object GetResult() => GetAwaiter().GetResult(); + protected override ContractTaskAwaiter CreateAwaiter() => new ContractTaskAwaiter(); + public override ContractTaskAwaiter GetAwaiter() => (ContractTaskAwaiter)base.GetAwaiter(); + public override object GetResult() => ((ContractTaskAwaiter)GetAwaiter()).GetResult(); } } diff --git a/src/Neo/SmartContract/ContractTaskMethodBuilder.cs b/src/Neo/SmartContract/ContractTaskMethodBuilder.cs index 83090bf583..bae1146f25 100644 --- a/src/Neo/SmartContract/ContractTaskMethodBuilder.cs +++ b/src/Neo/SmartContract/ContractTaskMethodBuilder.cs @@ -71,7 +71,7 @@ public void SetException(Exception exception) public void SetResult(T result) { - Task.GetAwaiter().SetResult(result); + ((ContractTaskAwaiter)Task.GetAwaiter()).SetResult(result); } public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) diff --git a/src/Neo/SmartContract/Manifest/ContractMethodDescriptor.cs b/src/Neo/SmartContract/Manifest/ContractMethodDescriptor.cs index 646ee76c46..cc8d0a16d5 100644 --- a/src/Neo/SmartContract/Manifest/ContractMethodDescriptor.cs +++ b/src/Neo/SmartContract/Manifest/ContractMethodDescriptor.cs @@ -73,7 +73,7 @@ public override StackItem ToStackItem(ReferenceCounter referenceCounter) }; if (string.IsNullOrEmpty(descriptor.Name)) throw new FormatException(); _ = descriptor.Parameters.ToDictionary(p => p.Name); - if (!Enum.IsDefined(descriptor.ReturnType)) throw new FormatException(); + if (!Enum.IsDefined(typeof(ContractParameterType), descriptor.ReturnType)) throw new FormatException(); if (descriptor.Offset < 0) throw new FormatException(); return descriptor; } diff --git a/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs b/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs index a393bb02b1..61906558d0 100644 --- a/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs +++ b/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs @@ -57,7 +57,7 @@ public static ContractParameterDefinition FromJson(JObject json) }; if (string.IsNullOrEmpty(parameter.Name)) throw new FormatException(); - if (!Enum.IsDefined(parameter.Type) || parameter.Type == ContractParameterType.Void) + if (!Enum.IsDefined(typeof(ContractParameterType), parameter.Type) || parameter.Type == ContractParameterType.Void) throw new FormatException(); return parameter; } diff --git a/src/Neo/SmartContract/StorageKey.cs b/src/Neo/SmartContract/StorageKey.cs index 21d7a64191..a0136e4456 100644 --- a/src/Neo/SmartContract/StorageKey.cs +++ b/src/Neo/SmartContract/StorageKey.cs @@ -73,7 +73,7 @@ public byte[] ToArray() { if (cache is null) { - cache = GC.AllocateUninitializedArray(sizeof(int) + Key.Length); + cache = new byte[sizeof(int) + Key.Length]; BinaryPrimitives.WriteInt32LittleEndian(cache, Id); Key.CopyTo(cache.AsMemory(sizeof(int))); } diff --git a/tests/Neo.VM.Tests/UT_Utility.cs b/tests/Neo.VM.Tests/UT_Utility.cs index 2d0756e190..38678bcddb 100644 --- a/tests/Neo.VM.Tests/UT_Utility.cs +++ b/tests/Neo.VM.Tests/UT_Utility.cs @@ -32,6 +32,64 @@ public void TestSqrtTest() Assert.AreEqual(new BigInteger(9), new BigInteger(81).Sqrt()); } + private static byte[] GetRandomByteArray(Random random) + { + var byteValue = random.Next(0, 32); + var value = new byte[byteValue]; + + random.NextBytes(value); + return value; + } + + private void VerifyGetBitLength(BigInteger value, long expected) + { + var result = value.GetBitLength(); + Assert.AreEqual(expected, value.GetBitLength(), "Native method has not the expected result"); + Assert.AreEqual(result, Utility.GetBitLength(value), "Result doesn't match"); + } + + [TestMethod] + public void GetBitLengthTest() + { + var random = new Random(); + + // Big Number (net standard didn't work) + VerifyGetBitLength(BigInteger.One << 32 << int.MaxValue, 2147483680); + + // Trivial cases + // sign bit|shortest two's complement + // string w/o sign bit + VerifyGetBitLength(0, 0); // 0| + VerifyGetBitLength(1, 1); // 0|1 + VerifyGetBitLength(-1, 0); // 1| + VerifyGetBitLength(2, 2); // 0|10 + VerifyGetBitLength(-2, 1); // 1|0 + VerifyGetBitLength(3, 2); // 0|11 + VerifyGetBitLength(-3, 2); // 1|01 + VerifyGetBitLength(4, 3); // 0|100 + VerifyGetBitLength(-4, 2); // 1|00 + VerifyGetBitLength(5, 3); // 0|101 + VerifyGetBitLength(-5, 3); // 1|011 + VerifyGetBitLength(6, 3); // 0|110 + VerifyGetBitLength(-6, 3); // 1|010 + VerifyGetBitLength(7, 3); // 0|111 + VerifyGetBitLength(-7, 3); // 1|001 + VerifyGetBitLength(8, 4); // 0|1000 + VerifyGetBitLength(-8, 3); // 1|000 + + // Random cases + for (uint i = 0; i < 1000; i++) + { + var bi = new BigInteger(GetRandomByteArray(random)); + Assert.AreEqual(bi.GetBitLength(), Utility.GetBitLength(bi), message: $"Error comparing: {bi}"); + } + + foreach (var bi in new[] { BigInteger.Zero, BigInteger.One, BigInteger.MinusOne, new BigInteger(ulong.MaxValue), new BigInteger(long.MinValue) }) + { + Assert.AreEqual(bi.GetBitLength(), Utility.GetBitLength(bi), message: $"Error comparing: {bi}"); + } + } + [TestMethod] public void TestModInverseTest() {