diff --git a/src/Neo/Cryptography/Crypto.cs b/src/Neo/Cryptography/Crypto.cs
index 1b8d46c48e..1ee1cdb5f6 100644
--- a/src/Neo/Cryptography/Crypto.cs
+++ b/src/Neo/Cryptography/Crypto.cs
@@ -13,7 +13,9 @@
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities.Encoders;
using System;
+using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
@@ -24,21 +26,12 @@ namespace Neo.Cryptography
///
public static class Crypto
{
+ private static readonly BigInteger s_prime = new(1,
+ Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"));
+
private static readonly ECDsaCache CacheECDsa = new();
private static readonly bool IsOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
private static readonly ECCurve secP256k1 = ECCurve.CreateFromFriendlyName("secP256k1");
- private static readonly X9ECParameters bouncySecp256k1 = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1");
- private static readonly X9ECParameters bouncySecp256r1 = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256r1");
-
- ///
- /// Holds domain parameters for Secp256r1 elliptic curve.
- ///
- private static readonly ECDomainParameters secp256r1DomainParams = new ECDomainParameters(bouncySecp256r1.Curve, bouncySecp256r1.G, bouncySecp256r1.N, bouncySecp256r1.H);
-
- ///
- /// Holds domain parameters for Secp256k1 elliptic curve.
- ///
- private static readonly ECDomainParameters secp256k1DomainParams = new ECDomainParameters(bouncySecp256k1.Curve, bouncySecp256k1.G, bouncySecp256k1.N, bouncySecp256k1.H);
///
/// Calculates the 160-bit hash value of the specified message.
@@ -86,18 +79,11 @@ public static byte[] Sign(byte[] message, byte[] priKey, ECC.ECCurve ecCurve = n
{
if (hashAlgorithm == HashAlgorithm.Keccak256 || (IsOSX && ecCurve == ECC.ECCurve.Secp256k1))
{
- var domain =
- ecCurve == null || ecCurve == ECC.ECCurve.Secp256r1 ? secp256r1DomainParams :
- ecCurve == ECC.ECCurve.Secp256k1 ? secp256k1DomainParams :
- throw new NotSupportedException(nameof(ecCurve));
var signer = new Org.BouncyCastle.Crypto.Signers.ECDsaSigner();
var privateKey = new BigInteger(1, priKey);
- var priKeyParameters = new ECPrivateKeyParameters(privateKey, domain);
+ var priKeyParameters = new ECPrivateKeyParameters(privateKey, ecCurve.BouncyCastleDomainParams);
signer.Init(true, priKeyParameters);
- var messageHash =
- hashAlgorithm == HashAlgorithm.SHA256 ? message.Sha256() :
- hashAlgorithm == HashAlgorithm.Keccak256 ? message.Keccak256() :
- throw new NotSupportedException(nameof(hashAlgorithm));
+ var messageHash = GetMessageHash(message, hashAlgorithm);
var signature = signer.GenerateSignature(messageHash);
var signatureBytes = new byte[64];
@@ -154,18 +140,10 @@ public static bool VerifySignature(ReadOnlySpan message, ReadOnlySpan message, ReadOnlySpan message, ReadOnlySpan
+ /// Get hash from message.
+ ///
+ /// Original message
+ /// The hash algorithm to be used hash the message, the default is SHA256.
+ /// Hashed message
+ public static byte[] GetMessageHash(byte[] message, HashAlgorithm hashAlgorithm = HashAlgorithm.SHA256)
+ {
+ return hashAlgorithm switch
+ {
+ HashAlgorithm.SHA256 => message.Sha256(),
+ HashAlgorithm.Keccak256 => message.Keccak256(),
+ HashAlgorithm.None => message,
+ _ => throw new NotSupportedException(nameof(hashAlgorithm))
+ };
+ }
+
+ ///
+ /// Get hash from message.
+ ///
+ /// Original message
+ /// The hash algorithm to be used hash the message, the default is SHA256.
+ /// Hashed message
+ public static byte[] GetMessageHash(ReadOnlySpan message, HashAlgorithm hashAlgorithm = HashAlgorithm.SHA256)
+ {
+ return hashAlgorithm switch
+ {
+ HashAlgorithm.SHA256 => message.Sha256(),
+ HashAlgorithm.Keccak256 => message.Keccak256(),
+ HashAlgorithm.None => message.ToArray(),
+ _ => throw new NotSupportedException(nameof(hashAlgorithm))
+ };
+ }
+
+ ///
+ /// Recovers the public key from a signature and message hash.
+ ///
+ /// Signature, either 65 bytes (r[32] || s[32] || v[1]) or
+ /// 64 bytes in “compact” form (r[32] || yParityAndS[32]).
+ /// 32-byte message hash
+ /// The recovered public key
+ /// Thrown if signature or hash is invalid
+ public static ECC.ECPoint ECRecover(byte[] signature, byte[] hash)
+ {
+ if (signature.Length != 65 && signature.Length != 64)
+ throw new ArgumentException("Signature must be 65 or 64 bytes", nameof(signature));
+ if (hash.Length != 32)
+ throw new ArgumentException("Message hash must be 32 bytes", nameof(hash));
+
+ try
+ {
+ // Extract (r, s) and compute integer recId
+ BigInteger r, s;
+ int recId;
+
+ if (signature.Length == 65)
+ {
+ // Format: r[32] || s[32] || v[1]
+ r = new BigInteger(1, [.. signature.Take(32)]);
+ s = new BigInteger(1, [.. signature.Skip(32).Take(32)]);
+
+ // v could be 0..3 or 27..30 (Ethereum style).
+ var v = signature[64];
+ recId = v >= 27 ? v - 27 : v; // normalize
+ if (recId < 0 || recId > 3)
+ throw new ArgumentException("Recovery value must be in [0..3] after normalization.", nameof(signature));
+ }
+ else
+ {
+ // 64 bytes “compact” format: r[32] || yParityAndS[32]
+ // yParity is fused into the top bit of s.
+
+ r = new BigInteger(1, [.. signature.Take(32)]);
+ var yParityAndS = new BigInteger(1, signature.Skip(32).ToArray());
+
+ // Mask out top bit to get s
+ var mask = BigInteger.One.ShiftLeft(255).Subtract(BigInteger.One);
+ s = yParityAndS.And(mask);
+
+ // Extract yParity (0 or 1)
+ var yParity = yParityAndS.TestBit(255);
+
+ // For “compact,” map parity to recId in [0..1].
+ // For typical usage, recId in {0,1} is enough:
+ recId = yParity ? 1 : 0;
+ }
+
+ // Decompose recId into i = recId >> 1 and yBit = recId & 1
+ var iPart = recId >> 1; // usually 0..1
+ var yBit = (recId & 1) == 1;
+
+ // BouncyCastle curve constants
+ var n = ECC.ECCurve.Secp256k1.BouncyCastleCurve.N;
+ var e = new BigInteger(1, hash);
+
+ // eInv = -e mod n
+ var eInv = BigInteger.Zero.Subtract(e).Mod(n);
+ // rInv = (r^-1) mod n
+ var rInv = r.ModInverse(n);
+ // srInv = (s * r^-1) mod n
+ var srInv = rInv.Multiply(s).Mod(n);
+ // eInvrInv = (eInv * r^-1) mod n
+ var eInvrInv = rInv.Multiply(eInv).Mod(n);
+
+ // x = r + iPart * n
+ var x = r.Add(BigInteger.ValueOf(iPart).Multiply(n));
+ // Verify x is within the curve prime
+ if (x.CompareTo(s_prime) >= 0)
+ throw new ArgumentException("x is out of range of the secp256k1 prime.", nameof(signature));
+
+ // Decompress to get R
+ var decompressedRKey = DecompressKey(ECC.ECCurve.Secp256k1.BouncyCastleCurve.Curve, x, yBit);
+ // Check that R is on curve
+ if (!decompressedRKey.Multiply(n).IsInfinity)
+ throw new ArgumentException("R point is not valid on this curve.", nameof(signature));
+
+ // Q = (eInv * G) + (srInv * R)
+ var q = Org.BouncyCastle.Math.EC.ECAlgorithms.SumOfTwoMultiplies(
+ ECC.ECCurve.Secp256k1.BouncyCastleCurve.G, eInvrInv,
+ decompressedRKey, srInv);
+
+ return ECC.ECPoint.FromBytes(q.Normalize().GetEncoded(false), ECC.ECCurve.Secp256k1);
+ }
+ catch (ArgumentException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ throw new ArgumentException("Invalid signature parameters", nameof(signature), ex);
+ }
+ }
+
+ private static Org.BouncyCastle.Math.EC.ECPoint DecompressKey(
+ Org.BouncyCastle.Math.EC.ECCurve curve, BigInteger xBN, bool yBit)
+ {
+ var compEnc = X9IntegerConverter.IntegerToBytes(xBN, 1 + X9IntegerConverter.GetByteLength(curve));
+ compEnc[0] = (byte)(yBit ? 0x03 : 0x02);
+ return curve.DecodePoint(compEnc);
+ }
}
}
diff --git a/src/Neo/Cryptography/ECC/ECCurve.cs b/src/Neo/Cryptography/ECC/ECCurve.cs
index c7b9dbe9bc..df7f8aa8e1 100644
--- a/src/Neo/Cryptography/ECC/ECCurve.cs
+++ b/src/Neo/Cryptography/ECC/ECCurve.cs
@@ -10,6 +10,7 @@
// modifications are permitted.
using Neo.Extensions;
+using Org.BouncyCastle.Crypto.Parameters;
using System.Globalization;
using System.Numerics;
@@ -33,9 +34,14 @@ public class ECCurve
///
public readonly ECPoint G;
+ public readonly Org.BouncyCastle.Asn1.X9.X9ECParameters BouncyCastleCurve;
+ ///
+ /// Holds domain parameters for Secp256r1 elliptic curve.
+ ///
+ public readonly ECDomainParameters BouncyCastleDomainParams;
internal readonly int ExpectedECPointLength;
- private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G)
+ private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G, string curveName)
{
this.Q = Q;
ExpectedECPointLength = ((int)VM.Utility.GetBitLength(Q) + 7) / 8;
@@ -44,6 +50,8 @@ private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G
this.N = N;
Infinity = new ECPoint(null, null, this);
this.G = ECPoint.DecodePoint(G, this);
+ BouncyCastleCurve = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName(curveName);
+ BouncyCastleDomainParams = new ECDomainParameters(BouncyCastleCurve.Curve, BouncyCastleCurve.G, BouncyCastleCurve.N, BouncyCastleCurve.H);
}
///
@@ -55,7 +63,8 @@ private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G
BigInteger.Zero,
7,
BigInteger.Parse("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", NumberStyles.AllowHexSpecifier),
- ("04" + "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" + "483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8").HexToBytes()
+ ("04" + "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" + "483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8").HexToBytes(),
+ "secp256k1"
);
///
@@ -67,7 +76,8 @@ private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G
BigInteger.Parse("00FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", NumberStyles.AllowHexSpecifier),
BigInteger.Parse("005AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", NumberStyles.AllowHexSpecifier),
BigInteger.Parse("00FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", NumberStyles.AllowHexSpecifier),
- ("04" + "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296" + "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5").HexToBytes()
+ ("04" + "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296" + "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5").HexToBytes(),
+ "secp256r1"
);
}
}
diff --git a/src/Neo/Cryptography/HashAlgorithm.cs b/src/Neo/Cryptography/HashAlgorithm.cs
index 361dbd3b64..247ac007a6 100644
--- a/src/Neo/Cryptography/HashAlgorithm.cs
+++ b/src/Neo/Cryptography/HashAlgorithm.cs
@@ -22,5 +22,10 @@ public enum HashAlgorithm : byte
/// The Keccak256 hash algorithm.
///
Keccak256 = 0x01,
+
+ ///
+ /// None
+ ///
+ None = 0xFF
}
}
diff --git a/src/Neo/SmartContract/Native/CryptoLib.cs b/src/Neo/SmartContract/Native/CryptoLib.cs
index 845eabb3c6..9e22c2457d 100644
--- a/src/Neo/SmartContract/Native/CryptoLib.cs
+++ b/src/Neo/SmartContract/Native/CryptoLib.cs
@@ -33,6 +33,34 @@ public sealed partial class CryptoLib : NativeContract
internal CryptoLib() : base() { }
+ ///
+ /// Recovers the public key from a secp256k1 signature in a single byte array format.
+ ///
+ /// The original message that was signed.
+ /// The 65-byte signature in format: r[32] + s[32] + v[1]. 64-bytes for eip-2098, where v must be 27 or 28.
+ /// The hash algorithm to be used hash the message.
+ /// The recovered public key in compressed format, or null if recovery fails.
+ [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 10, Name = "recoverSecp256K1")]
+ public static byte[] RecoverSecp256K1(byte[] message, byte[] signature, HashAlgorithm hashAlgorithm)
+ {
+ // It will be checked in Crypto.ECRecover
+ // if (signature.Length != 65 && signature.Length != 64)
+ // throw new ArgumentException("Signature must be 65 or 64 bytes", nameof(signature));
+
+ try
+ {
+ var messageHash = Crypto.GetMessageHash(message, hashAlgorithm);
+ if (messageHash == null) return null;
+
+ var point = Crypto.ECRecover(signature, messageHash);
+ return point?.EncodePoint(true);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
///
/// Computes the hash value for the specified byte array using the ripemd160 algorithm.
///
diff --git a/tests/Neo.UnitTests/Cryptography/ECC/UT_ECFieldElement.cs b/tests/Neo.UnitTests/Cryptography/ECC/UT_ECFieldElement.cs
index 80a33aad2d..b43b32558a 100644
--- a/tests/Neo.UnitTests/Cryptography/ECC/UT_ECFieldElement.cs
+++ b/tests/Neo.UnitTests/Cryptography/ECC/UT_ECFieldElement.cs
@@ -87,13 +87,15 @@ public void TestSqrt()
ECFieldElement element = new(new BigInteger(100), ECCurve.Secp256k1);
Assert.AreEqual(new ECFieldElement(BigInteger.Parse("115792089237316195423570985008687907853269984665640564039457584007908834671653"), ECCurve.Secp256k1), element.Sqrt());
- ConstructorInfo constructor = typeof(ECCurve).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(BigInteger), typeof(BigInteger), typeof(BigInteger), typeof(BigInteger), typeof(byte[]) }, null);
- ECCurve testCruve = constructor.Invoke(new object[] {
+ var constructor = typeof(ECCurve).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null,
+ [typeof(BigInteger), typeof(BigInteger), typeof(BigInteger), typeof(BigInteger), typeof(byte[]), typeof(string)], null);
+ var testCruve = constructor.Invoke([
BigInteger.Parse("00FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFF0", NumberStyles.AllowHexSpecifier),
BigInteger.Parse("00FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFF00", NumberStyles.AllowHexSpecifier),
BigInteger.Parse("005AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", NumberStyles.AllowHexSpecifier),
BigInteger.Parse("00FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", NumberStyles.AllowHexSpecifier),
- ("04" + "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296" + "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5").HexToBytes() }) as ECCurve;
+ ("04" + "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296" + "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5").HexToBytes(),
+ "secp256k1"]) as ECCurve;
element = new ECFieldElement(new BigInteger(200), testCruve);
Assert.IsNull(element.Sqrt());
}
diff --git a/tests/Neo.UnitTests/Cryptography/UT_Crypto.cs b/tests/Neo.UnitTests/Cryptography/UT_Crypto.cs
index fd14e9f39a..0e286bd330 100644
--- a/tests/Neo.UnitTests/Cryptography/UT_Crypto.cs
+++ b/tests/Neo.UnitTests/Cryptography/UT_Crypto.cs
@@ -11,27 +11,25 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.Cryptography;
-using Neo.Cryptography.ECC;
using Neo.Extensions;
-using Neo.IO;
using Neo.Wallets;
using System;
+using System.Linq;
using System.Security.Cryptography;
+using System.Text;
namespace Neo.UnitTests.Cryptography
{
[TestClass]
public class UT_Crypto
{
- private KeyPair key = null;
- private readonly byte[] message = System.Text.Encoding.Default.GetBytes("HelloWorld");
- private readonly byte[] wrongKey = new byte[33];
- private readonly byte[] wrongKey2 = new byte[36];
+ private KeyPair _key = null;
+ private readonly byte[] _message = Encoding.Default.GetBytes("HelloWorld");
public static KeyPair GenerateKey(int privateKeyLength)
{
- byte[] privateKey = new byte[privateKeyLength];
- using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
+ var privateKey = new byte[privateKeyLength];
+ using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(privateKey);
}
@@ -40,8 +38,8 @@ public static KeyPair GenerateKey(int privateKeyLength)
public static KeyPair GenerateCertainKey(int privateKeyLength)
{
- byte[] privateKey = new byte[privateKeyLength];
- for (int i = 0; i < privateKeyLength; i++)
+ var privateKey = new byte[privateKeyLength];
+ for (var i = 0; i < privateKeyLength; i++)
{
privateKey[i] = (byte)((byte)i % byte.MaxValue);
}
@@ -51,20 +49,23 @@ public static KeyPair GenerateCertainKey(int privateKeyLength)
[TestInitialize]
public void TestSetup()
{
- key = GenerateKey(32);
+ _key = GenerateKey(32);
}
[TestMethod]
public void TestVerifySignature()
{
- byte[] signature = Crypto.Sign(message, key.PrivateKey);
- Assert.IsTrue(Crypto.VerifySignature(message, signature, key.PublicKey));
+ var message = Encoding.Default.GetBytes("HelloWorld");
+ var signature = Crypto.Sign(message, _key.PrivateKey);
+ Assert.IsTrue(Crypto.VerifySignature(message, signature, _key.PublicKey));
+ var wrongKey2 = new byte[36];
+ var wrongKey = new byte[33];
wrongKey[0] = 0x02;
Assert.IsFalse(Crypto.VerifySignature(message, signature, wrongKey, Neo.Cryptography.ECC.ECCurve.Secp256r1));
wrongKey[0] = 0x03;
- for (int i = 1; i < 33; i++) wrongKey[i] = byte.MaxValue;
+ for (var i = 1; i < 33; i++) wrongKey[i] = byte.MaxValue;
Assert.ThrowsException(() => Crypto.VerifySignature(message, signature, wrongKey, Neo.Cryptography.ECC.ECCurve.Secp256r1));
Assert.ThrowsException(() => Crypto.VerifySignature(message, signature, wrongKey2, Neo.Cryptography.ECC.ECCurve.Secp256r1));
@@ -73,23 +74,147 @@ public void TestVerifySignature()
[TestMethod]
public void TestSecp256k1()
{
- byte[] privkey = "7177f0d04c79fa0b8c91fe90c1cf1d44772d1fba6e5eb9b281a22cd3aafb51fe".HexToBytes();
- byte[] message = "2d46a712699bae19a634563d74d04cc2da497b841456da270dccb75ac2f7c4e7".HexToBytes();
+ var privkey = "7177f0d04c79fa0b8c91fe90c1cf1d44772d1fba6e5eb9b281a22cd3aafb51fe".HexToBytes();
+ var message = "2d46a712699bae19a634563d74d04cc2da497b841456da270dccb75ac2f7c4e7".HexToBytes();
var signature = Crypto.Sign(message, privkey, Neo.Cryptography.ECC.ECCurve.Secp256k1);
- byte[] pubKey = "04fd0a8c1ce5ae5570fdd46e7599c16b175bf0ebdfe9c178f1ab848fb16dac74a5d301b0534c7bcf1b3760881f0c420d17084907edd771e1c9c8e941bbf6ff9108".HexToBytes();
+ var pubKey = "04fd0a8c1ce5ae5570fdd46e7599c16b175bf0ebdfe9c178f1ab848fb16dac74a5d301b0534c7bcf1b3760881f0c420d17084907edd771e1c9c8e941bbf6ff9108".HexToBytes();
Assert.IsTrue(Crypto.VerifySignature(message, signature, pubKey, Neo.Cryptography.ECC.ECCurve.Secp256k1));
- message = System.Text.Encoding.Default.GetBytes("world");
+ message = Encoding.Default.GetBytes("world");
signature = Crypto.Sign(message, privkey, Neo.Cryptography.ECC.ECCurve.Secp256k1);
Assert.IsTrue(Crypto.VerifySignature(message, signature, pubKey, Neo.Cryptography.ECC.ECCurve.Secp256k1));
- message = System.Text.Encoding.Default.GetBytes("中文");
+ message = Encoding.Default.GetBytes("中文");
signature = "b8cba1ff42304d74d083e87706058f59cdd4f755b995926d2cd80a734c5a3c37e4583bfd4339ac762c1c91eee3782660a6baf62cd29e407eccd3da3e9de55a02".HexToBytes();
pubKey = "03661b86d54eb3a8e7ea2399e0db36ab65753f95fff661da53ae0121278b881ad0".HexToBytes();
Assert.IsTrue(Crypto.VerifySignature(message, signature, pubKey, Neo.Cryptography.ECC.ECCurve.Secp256k1));
+
+ var messageHash = message.Sha256();
+ // append v to signature
+ signature = [.. signature, .. new byte[] { 27 }];
+ var recover = Crypto.ECRecover(signature, messageHash);
+
+ CollectionAssert.AreEqual(pubKey, recover.ToArray());
+ }
+
+ [TestMethod]
+ public void TestECRecover()
+ {
+ // Test case 1
+ var message1 = "5c868fedb8026979ebd26f1ba07c27eedf4ff6d10443505a96ecaf21ba8c4f0937b3cd23ffdc3dd429d4cd1905fb8dbcceeff1350020e18b58d2ba70887baa3a9b783ad30d3fbf210331cdd7df8d77defa398cdacdfc2e359c7ba4cae46bb74401deb417f8b912a1aa966aeeba9c39c7dd22479ae2b30719dca2f2206c5eb4b7".HexToBytes();
+ var messageHash1 = "5ae8317d34d1e595e3fa7247db80c0af4320cce1116de187f8f7e2e099c0d8d0".HexToBytes();
+ var signature1 = "45c0b7f8c09a9e1f1cea0c25785594427b6bf8f9f878a8af0b1abbb48e16d0920d8becd0c220f67c51217eecfd7184ef0732481c843857e6bc7fc095c4f6b78801".HexToBytes();
+ var expectedPubKey1 = "034a071e8a6e10aada2b8cf39fa3b5fb3400b04e99ea8ae64ceea1a977dbeaf5d5".HexToBytes();
+
+ var recoveredKey1 = Crypto.ECRecover(signature1, messageHash1);
+ CollectionAssert.AreEqual(expectedPubKey1, recoveredKey1.EncodePoint(true));
+
+ // Test case 2
+ var message2 = "17cd4a74d724d55355b6fb2b0759ca095298e3fd1856b87ca1cb2df5409058022736d21be071d820b16dfc441be97fbcea5df787edc886e759475469e2128b22f26b82ca993be6695ab190e673285d561d3b6d42fcc1edd6d12db12dcda0823e9d6079e7bc5ff54cd452dad308d52a15ce9c7edd6ef3dad6a27becd8e001e80f".HexToBytes();
+ var messageHash2 = "586052916fb6f746e1d417766cceffbe1baf95579bab67ad49addaaa6e798862".HexToBytes();
+ var signature2 = "4e0ea79d4a476276e4b067facdec7460d2c98c8a65326a6e5c998fd7c65061140e45aea5034af973410e65cf97651b3f2b976e3fc79c6a93065ed7cb69a2ab5a01".HexToBytes();
+ var expectedPubKey2 = "02dbf1f4092deb3cfd4246b2011f7b24840bc5dbedae02f28471ce5b3bfbf06e71".HexToBytes();
+
+ var recoveredKey2 = Crypto.ECRecover(signature2, messageHash2);
+ CollectionAssert.AreEqual(expectedPubKey2, recoveredKey2.EncodePoint(true));
+
+ // Test case 3 - recovery param 0
+ var message3 = "db0d31717b04802adbbae1997487da8773440923c09b869e12a57c36dda34af11b8897f266cd81c02a762c6b74ea6aaf45aaa3c52867eb8f270f5092a36b498f88b65b2ebda24afe675da6f25379d1e194d093e7a2f66e450568dbdffebff97c4597a00c96a5be9ba26deefcca8761c1354429622c8db269d6a0ec0cc7a8585c".HexToBytes();
+ var messageHash3 = "c36d0ecf4bfd178835c97aae7585f6a87de7dfa23cc927944f99a8d60feff68b".HexToBytes();
+ var signature3 = "f25b86e1d8a11d72475b3ed273b0781c7d7f6f9e1dae0dd5d3ee9b84f3fab89163d9c4e1391de077244583e9a6e3d8e8e1f236a3bf5963735353b93b1a3ba93500".HexToBytes();
+ var expectedPubKey3 = "03414549fd05bfb7803ae507ff86b99becd36f8d66037a7f5ba612792841d42eb9".HexToBytes();
+
+ var recoveredKey3 = Crypto.ECRecover(signature3, messageHash3);
+ CollectionAssert.AreEqual(expectedPubKey3, recoveredKey3.EncodePoint(true));
+
+ // Test invalid cases
+ var invalidSignature = new byte[65];
+ Assert.ThrowsException(() => Crypto.ECRecover(invalidSignature, messageHash1));
+
+ // Test with invalid recovery value
+ var invalidRecoverySignature = signature1.ToArray();
+ invalidRecoverySignature[64] = 29; // Invalid recovery value
+ Assert.ThrowsException(() => Crypto.ECRecover(invalidRecoverySignature, messageHash1));
+
+ // Test with wrong message hash
+ var recoveredWrongHash = Crypto.ECRecover(signature1, messageHash2);
+ CollectionAssert.AreNotEquivalent(expectedPubKey1, recoveredWrongHash.EncodePoint(true));
+ }
+
+ [TestMethod]
+ public void TestERC2098()
+ {
+ // Test from https://eips.ethereum.org/EIPS/eip-2098
+
+ // Private Key: 0x1234567890123456789012345678901234567890123456789012345678901234
+ // Message: "Hello World"
+ // Signature:
+ // r: 0x68a020a209d3d56c46f38cc50a33f704f4a9a10a59377f8dd762ac66910e9b90
+ // s: 0x7e865ad05c4035ab5792787d4a0297a43617ae897930a6fe4d822b8faea52064
+ // v: 27
+
+ var privateKey = "1234567890123456789012345678901234567890123456789012345678901234".HexToBytes();
+ var expectedPubKey1 = (Neo.Cryptography.ECC.ECCurve.Secp256k1.G * privateKey).ToArray();
+
+ Console.WriteLine($"Expected PubKey: {expectedPubKey1.ToHexString()}");
+ var message1 = Encoding.UTF8.GetBytes("Hello World");
+ var messageHash1 = new byte[] { 0x19 }.Concat(Encoding.UTF8.GetBytes($"Ethereum Signed Message:\n{message1.Count()}")).Concat(message1).ToArray().Keccak256();
+ Console.WriteLine($"Message Hash: {Convert.ToHexString(messageHash1)}");
+
+ // Signature values from EIP-2098 test case
+ var r = "68a020a209d3d56c46f38cc50a33f704f4a9a10a59377f8dd762ac66910e9b90".HexToBytes();
+ var s = "7e865ad05c4035ab5792787d4a0297a43617ae897930a6fe4d822b8faea52064".HexToBytes();
+ var signature1 = new byte[65];
+ Array.Copy(r, 0, signature1, 0, 32);
+ Array.Copy(s, 0, signature1, 32, 32);
+ signature1[64] = 27;
+
+ Console.WriteLine($"r: {Convert.ToHexString(signature1.Take(32).ToArray())}");
+ Console.WriteLine($"s: {Convert.ToHexString(signature1.Skip(32).Take(32).ToArray())}");
+ Console.WriteLine($"yParity: {(signature1[32] & 0x80) != 0}");
+
+ var recoveredKey1 = Crypto.ECRecover(signature1, messageHash1);
+
+ // Private Key: 0x1234567890123456789012345678901234567890123456789012345678901234
+ // Message: "It's a small(er) world"
+ // Signature:
+ // r: 0x9328da16089fcba9bececa81663203989f2df5fe1faa6291a45381c81bd17f76
+ // s: 0x139c6d6b623b42da56557e5e734a43dc83345ddfadec52cbe24d0cc64f550793
+ // v: 28
+
+ var sig = "68a020a209d3d56c46f38cc50a33f704f4a9a10a59377f8dd762ac66910e9b90".HexToBytes()
+ .Concat("7e865ad05c4035ab5792787d4a0297a43617ae897930a6fe4d822b8faea52064".HexToBytes())
+ .Concat(new byte[] { 0x1B })
+ .ToArray();
+
+ var pubKey = Crypto.ECRecover(sig, messageHash1);
+
+ Console.WriteLine($"Recovered PubKey: {pubKey.EncodePoint(true).ToHexString()}");
+ Console.WriteLine($"Recovered PubKey: {recoveredKey1.EncodePoint(true).ToHexString()}");
+ Assert.AreEqual(recoveredKey1, recoveredKey1);
+
+ var message2Body = Encoding.UTF8.GetBytes("It's a small(er) world");
+ var message2 = new byte[] { 0x19 }.Concat(Encoding.UTF8.GetBytes($"Ethereum Signed Message:\n{message2Body.Count()}")).Concat(message2Body).ToArray();
+ var messageHash2 = message2.Keccak256();
+ Console.WriteLine($"\nMessage Hash 2: {Convert.ToHexString(messageHash2)}");
+
+ // Second test case from EIP-2098
+ var r2 = "9328da16089fcba9bececa81663203989f2df5fe1faa6291a45381c81bd17f76".HexToBytes();
+ var s2 = "939c6d6b623b42da56557e5e734a43dc83345ddfadec52cbe24d0cc64f550793".HexToBytes();
+ var signature2 = new byte[64];
+ Array.Copy(r2, 0, signature2, 0, 32);
+ Array.Copy(s2, 0, signature2, 32, 32);
+
+ Console.WriteLine($"r: {Convert.ToHexString(signature2.Take(32).ToArray())}");
+ Console.WriteLine($"s: {Convert.ToHexString(signature2.Skip(32).Take(32).ToArray())}");
+ Console.WriteLine($"yParity: {(signature2[32] & 0x80) != 0}");
+
+ var recoveredKey2 = Crypto.ECRecover(signature2, messageHash2);
+ CollectionAssert.AreEqual(expectedPubKey1, recoveredKey2.EncodePoint(true));
+ Assert.IsTrue(Crypto.VerifySignature(message2, signature2, recoveredKey2, Neo.Cryptography.HashAlgorithm.Keccak256));
}
}
}
diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs
index 3096c5a1b1..7efd3b333c 100644
--- a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs
+++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs
@@ -42,7 +42,7 @@ public void TestSetup()
{
{"ContractManagement", """{"id":-1,"updatecounter":0,"hash":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"ContractManagement","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"deploy","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Array","offset":0,"safe":false},{"name":"deploy","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Array","offset":7,"safe":false},{"name":"destroy","parameters":[],"returntype":"Void","offset":14,"safe":false},{"name":"getContract","parameters":[{"name":"hash","type":"Hash160"}],"returntype":"Array","offset":21,"safe":true},{"name":"getContractById","parameters":[{"name":"id","type":"Integer"}],"returntype":"Array","offset":28,"safe":true},{"name":"getContractHashes","parameters":[],"returntype":"InteropInterface","offset":35,"safe":true},{"name":"getMinimumDeploymentFee","parameters":[],"returntype":"Integer","offset":42,"safe":true},{"name":"hasMethod","parameters":[{"name":"hash","type":"Hash160"},{"name":"method","type":"String"},{"name":"pcount","type":"Integer"}],"returntype":"Boolean","offset":49,"safe":true},{"name":"setMinimumDeploymentFee","parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","offset":56,"safe":false},{"name":"update","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Void","offset":63,"safe":false},{"name":"update","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","offset":70,"safe":false}],"events":[{"name":"Deploy","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Update","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Destroy","parameters":[{"name":"Hash","type":"Hash160"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}""" },
{"StdLib", """{"id":-2,"updatecounter":0,"hash":"0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":2681632925},"manifest":{"name":"StdLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"atoi","parameters":[{"name":"value","type":"String"}],"returntype":"Integer","offset":0,"safe":true},{"name":"atoi","parameters":[{"name":"value","type":"String"},{"name":"base","type":"Integer"}],"returntype":"Integer","offset":7,"safe":true},{"name":"base58CheckDecode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":14,"safe":true},{"name":"base58CheckEncode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":21,"safe":true},{"name":"base58Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":28,"safe":true},{"name":"base58Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":35,"safe":true},{"name":"base64Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":42,"safe":true},{"name":"base64Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":49,"safe":true},{"name":"base64UrlDecode","parameters":[{"name":"s","type":"String"}],"returntype":"String","offset":56,"safe":true},{"name":"base64UrlEncode","parameters":[{"name":"data","type":"String"}],"returntype":"String","offset":63,"safe":true},{"name":"deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"Any","offset":70,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"}],"returntype":"String","offset":77,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"},{"name":"base","type":"Integer"}],"returntype":"String","offset":84,"safe":true},{"name":"jsonDeserialize","parameters":[{"name":"json","type":"ByteArray"}],"returntype":"Any","offset":91,"safe":true},{"name":"jsonSerialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":98,"safe":true},{"name":"memoryCompare","parameters":[{"name":"str1","type":"ByteArray"},{"name":"str2","type":"ByteArray"}],"returntype":"Integer","offset":105,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"}],"returntype":"Integer","offset":112,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"}],"returntype":"Integer","offset":119,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"},{"name":"backward","type":"Boolean"}],"returntype":"Integer","offset":126,"safe":true},{"name":"serialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":133,"safe":true},{"name":"strLen","parameters":[{"name":"str","type":"String"}],"returntype":"Integer","offset":140,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"}],"returntype":"Array","offset":147,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"},{"name":"removeEmptyEntries","type":"Boolean"}],"returntype":"Array","offset":154,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""},
- {"CryptoLib", """{"id":-3,"updatecounter":0,"hash":"0x726cb6e0cd8628a1350a611384688911ab75f51b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":3581846399},"manifest":{"name":"CryptoLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"bls12381Add","parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"InteropInterface","offset":0,"safe":true},{"name":"bls12381Deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"InteropInterface","offset":7,"safe":true},{"name":"bls12381Equal","parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"Boolean","offset":14,"safe":true},{"name":"bls12381Mul","parameters":[{"name":"x","type":"InteropInterface"},{"name":"mul","type":"ByteArray"},{"name":"neg","type":"Boolean"}],"returntype":"InteropInterface","offset":21,"safe":true},{"name":"bls12381Pairing","parameters":[{"name":"g1","type":"InteropInterface"},{"name":"g2","type":"InteropInterface"}],"returntype":"InteropInterface","offset":28,"safe":true},{"name":"bls12381Serialize","parameters":[{"name":"g","type":"InteropInterface"}],"returntype":"ByteArray","offset":35,"safe":true},{"name":"keccak256","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":42,"safe":true},{"name":"murmur32","parameters":[{"name":"data","type":"ByteArray"},{"name":"seed","type":"Integer"}],"returntype":"ByteArray","offset":49,"safe":true},{"name":"ripemd160","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":56,"safe":true},{"name":"sha256","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":63,"safe":true},{"name":"verifyWithECDsa","parameters":[{"name":"message","type":"ByteArray"},{"name":"pubkey","type":"ByteArray"},{"name":"signature","type":"ByteArray"},{"name":"curveHash","type":"Integer"}],"returntype":"Boolean","offset":70,"safe":true},{"name":"verifyWithEd25519","parameters":[{"name":"message","type":"ByteArray"},{"name":"publicKey","type":"ByteArray"},{"name":"signature","type":"ByteArray"}],"returntype":"Boolean","offset":77,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""},
+ {"CryptoLib", """{"id":-3,"updatecounter":0,"hash":"0x726cb6e0cd8628a1350a611384688911ab75f51b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQA==","checksum":174904780},"manifest":{"name":"CryptoLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"bls12381Add","parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"InteropInterface","offset":0,"safe":true},{"name":"bls12381Deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"InteropInterface","offset":7,"safe":true},{"name":"bls12381Equal","parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"Boolean","offset":14,"safe":true},{"name":"bls12381Mul","parameters":[{"name":"x","type":"InteropInterface"},{"name":"mul","type":"ByteArray"},{"name":"neg","type":"Boolean"}],"returntype":"InteropInterface","offset":21,"safe":true},{"name":"bls12381Pairing","parameters":[{"name":"g1","type":"InteropInterface"},{"name":"g2","type":"InteropInterface"}],"returntype":"InteropInterface","offset":28,"safe":true},{"name":"bls12381Serialize","parameters":[{"name":"g","type":"InteropInterface"}],"returntype":"ByteArray","offset":35,"safe":true},{"name":"keccak256","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":42,"safe":true},{"name":"murmur32","parameters":[{"name":"data","type":"ByteArray"},{"name":"seed","type":"Integer"}],"returntype":"ByteArray","offset":49,"safe":true},{"name":"recoverSecp256K1","parameters":[{"name":"message","type":"ByteArray"},{"name":"signature","type":"ByteArray"},{"name":"hashAlgorithm","type":"Integer"}],"returntype":"ByteArray","offset":56,"safe":true},{"name":"ripemd160","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":63,"safe":true},{"name":"sha256","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":70,"safe":true},{"name":"verifyWithECDsa","parameters":[{"name":"message","type":"ByteArray"},{"name":"pubkey","type":"ByteArray"},{"name":"signature","type":"ByteArray"},{"name":"curveHash","type":"Integer"}],"returntype":"Boolean","offset":77,"safe":true},{"name":"verifyWithEd25519","parameters":[{"name":"message","type":"ByteArray"},{"name":"publicKey","type":"ByteArray"},{"name":"signature","type":"ByteArray"}],"returntype":"Boolean","offset":84,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""},
{"LedgerContract", """{"id":-4,"updatecounter":0,"hash":"0xda65b600f7124ce6c79950c1772a36403104f2be","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1110259869},"manifest":{"name":"LedgerContract","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"currentHash","parameters":[],"returntype":"Hash256","offset":0,"safe":true},{"name":"currentIndex","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"getBlock","parameters":[{"name":"indexOrHash","type":"ByteArray"}],"returntype":"Array","offset":14,"safe":true},{"name":"getTransaction","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Array","offset":21,"safe":true},{"name":"getTransactionFromBlock","parameters":[{"name":"blockIndexOrHash","type":"ByteArray"},{"name":"txIndex","type":"Integer"}],"returntype":"Array","offset":28,"safe":true},{"name":"getTransactionHeight","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Integer","offset":35,"safe":true},{"name":"getTransactionSigners","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Array","offset":42,"safe":true},{"name":"getTransactionVMState","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Integer","offset":49,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""},
{"NeoToken", """{"id":-5,"updatecounter":0,"hash":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":1991619121},"manifest":{"name":"NeoToken","groups":[],"features":{},"supportedstandards":["NEP-17","NEP-27"],"abi":{"methods":[{"name":"balanceOf","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","offset":0,"safe":true},{"name":"decimals","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"getAccountState","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Array","offset":14,"safe":true},{"name":"getAllCandidates","parameters":[],"returntype":"InteropInterface","offset":21,"safe":true},{"name":"getCandidateVote","parameters":[{"name":"pubKey","type":"PublicKey"}],"returntype":"Integer","offset":28,"safe":true},{"name":"getCandidates","parameters":[],"returntype":"Array","offset":35,"safe":true},{"name":"getCommittee","parameters":[],"returntype":"Array","offset":42,"safe":true},{"name":"getCommitteeAddress","parameters":[],"returntype":"Hash160","offset":49,"safe":true},{"name":"getGasPerBlock","parameters":[],"returntype":"Integer","offset":56,"safe":true},{"name":"getNextBlockValidators","parameters":[],"returntype":"Array","offset":63,"safe":true},{"name":"getRegisterPrice","parameters":[],"returntype":"Integer","offset":70,"safe":true},{"name":"onNEP17Payment","parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","offset":77,"safe":false},{"name":"registerCandidate","parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","offset":84,"safe":false},{"name":"setGasPerBlock","parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Void","offset":91,"safe":false},{"name":"setRegisterPrice","parameters":[{"name":"registerPrice","type":"Integer"}],"returntype":"Void","offset":98,"safe":false},{"name":"symbol","parameters":[],"returntype":"String","offset":105,"safe":true},{"name":"totalSupply","parameters":[],"returntype":"Integer","offset":112,"safe":true},{"name":"transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","offset":119,"safe":false},{"name":"unclaimedGas","parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer","offset":126,"safe":true},{"name":"unregisterCandidate","parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","offset":133,"safe":false},{"name":"vote","parameters":[{"name":"account","type":"Hash160"},{"name":"voteTo","type":"PublicKey"}],"returntype":"Boolean","offset":140,"safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"CandidateStateChanged","parameters":[{"name":"pubkey","type":"PublicKey"},{"name":"registered","type":"Boolean"},{"name":"votes","type":"Integer"}]},{"name":"Vote","parameters":[{"name":"account","type":"Hash160"},{"name":"from","type":"PublicKey"},{"name":"to","type":"PublicKey"},{"name":"amount","type":"Integer"}]},{"name":"CommitteeChanged","parameters":[{"name":"old","type":"Array"},{"name":"new","type":"Array"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""},
{"GasToken", """{"id":-6,"updatecounter":0,"hash":"0xd2a4cff31913016155e38e474a2c06d08be276cf","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":2663858513},"manifest":{"name":"GasToken","groups":[],"features":{},"supportedstandards":["NEP-17"],"abi":{"methods":[{"name":"balanceOf","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","offset":0,"safe":true},{"name":"decimals","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"symbol","parameters":[],"returntype":"String","offset":14,"safe":true},{"name":"totalSupply","parameters":[],"returntype":"Integer","offset":21,"safe":true},{"name":"transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","offset":28,"safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""},