diff --git a/src/Neo/Cryptography/Crypto.cs b/src/Neo/Cryptography/Crypto.cs
index e419d268a2..85ed0411a8 100644
--- a/src/Neo/Cryptography/Crypto.cs
+++ b/src/Neo/Cryptography/Crypto.cs
@@ -28,6 +28,17 @@ public static class Crypto
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.
@@ -50,22 +61,30 @@ public static byte[] Hash256(ReadOnlySpan message)
}
///
- /// Signs the specified message using the ECDSA algorithm.
+ /// Signs the specified message using the ECDSA algorithm and specified hash algorithm.
///
/// The message to be signed.
/// The private key to be used.
/// The curve of the signature, default is .
+ /// The hash algorithm to hash the message, default is SHA256.
/// The ECDSA signature for the specified message.
- public static byte[] Sign(byte[] message, byte[] priKey, ECC.ECCurve ecCurve = null)
+ public static byte[] Sign(byte[] message, byte[] priKey, ECC.ECCurve ecCurve = null, Hasher hasher = Hasher.SHA256)
{
- if (IsOSX && ecCurve == ECC.ECCurve.Secp256k1)
+ if (hasher == Hasher.Keccak256 || (IsOSX && ecCurve == ECC.ECCurve.Secp256k1))
{
- var domain = new ECDomainParameters(bouncySecp256k1.Curve, bouncySecp256k1.G, bouncySecp256k1.N, bouncySecp256k1.H);
+ 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);
signer.Init(true, priKeyParameters);
- var signature = signer.GenerateSignature(message.Sha256());
+ var messageHash =
+ hasher == Hasher.SHA256 ? message.Sha256() :
+ hasher == Hasher.Keccak256 ? message.Keccak256() :
+ throw new NotSupportedException(nameof(hasher));
+ var signature = signer.GenerateSignature(messageHash);
var signatureBytes = new byte[64];
var rBytes = signature[0].ToByteArrayUnsigned();
@@ -87,24 +106,35 @@ public static byte[] Sign(byte[] message, byte[] priKey, ECC.ECCurve ecCurve = n
Curve = curve,
D = priKey,
});
- return ecdsa.SignData(message, HashAlgorithmName.SHA256);
+ var hashAlg =
+ hasher == Hasher.SHA256 ? HashAlgorithmName.SHA256 :
+ throw new NotSupportedException(nameof(hasher));
+ return ecdsa.SignData(message, hashAlg);
}
///
- /// Verifies that a digital signature is appropriate for the provided key and message.
+ /// Verifies that a digital signature is appropriate for the provided key, message and hash algorithm.
///
/// The signed message.
/// The signature to be verified.
/// The public key to be used.
+ /// The hash algorithm to be used to hash the message, the default is SHA256.
/// if the signature is valid; otherwise, .
- public static bool VerifySignature(ReadOnlySpan message, ReadOnlySpan signature, ECC.ECPoint pubkey)
+ public static bool VerifySignature(ReadOnlySpan message, ReadOnlySpan signature, ECC.ECPoint pubkey, Hasher hasher = Hasher.SHA256)
{
if (signature.Length != 64) return false;
- if (IsOSX && pubkey.Curve == ECC.ECCurve.Secp256k1)
+ if (hasher == Hasher.Keccak256 || (IsOSX && pubkey.Curve == ECC.ECCurve.Secp256k1))
{
- var domain = new ECDomainParameters(bouncySecp256k1.Curve, bouncySecp256k1.G, bouncySecp256k1.N, bouncySecp256k1.H);
- var point = bouncySecp256k1.Curve.CreatePoint(
+ var domain =
+ pubkey.Curve == ECC.ECCurve.Secp256r1 ? secp256r1DomainParams :
+ pubkey.Curve == ECC.ECCurve.Secp256k1 ? secp256k1DomainParams :
+ throw new NotSupportedException(nameof(pubkey.Curve));
+ var curve =
+ pubkey.Curve == ECC.ECCurve.Secp256r1 ? bouncySecp256r1.Curve :
+ bouncySecp256k1.Curve;
+
+ var point = curve.CreatePoint(
new BigInteger(pubkey.X.Value.ToString()),
new BigInteger(pubkey.Y.Value.ToString()));
var pubKey = new ECPublicKeyParameters("ECDSA", point, domain);
@@ -115,11 +145,19 @@ public static bool VerifySignature(ReadOnlySpan message, ReadOnlySpan
@@ -153,16 +191,17 @@ public static ECDsa CreateECDsa(ECC.ECPoint pubkey)
}
///
- /// Verifies that a digital signature is appropriate for the provided key and message.
+ /// Verifies that a digital signature is appropriate for the provided key, curve, message and hasher.
///
/// The signed message.
/// The signature to be verified.
/// The public key to be used.
/// The curve to be used by the ECDSA algorithm.
+ /// The hash algorithm to be used hash the message, the default is SHA256.
/// if the signature is valid; otherwise, .
- public static bool VerifySignature(ReadOnlySpan message, ReadOnlySpan signature, ReadOnlySpan pubkey, ECC.ECCurve curve)
+ public static bool VerifySignature(ReadOnlySpan message, ReadOnlySpan signature, ReadOnlySpan pubkey, ECC.ECCurve curve, Hasher hasher = Hasher.SHA256)
{
- return VerifySignature(message, signature, ECC.ECPoint.DecodePoint(pubkey, curve));
+ return VerifySignature(message, signature, ECC.ECPoint.DecodePoint(pubkey, curve), hasher);
}
}
}
diff --git a/src/Neo/Cryptography/Hasher.cs b/src/Neo/Cryptography/Hasher.cs
new file mode 100644
index 0000000000..21c5fef6a8
--- /dev/null
+++ b/src/Neo/Cryptography/Hasher.cs
@@ -0,0 +1,29 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// Hasher.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.
+
+namespace Neo.Cryptography
+{
+ ///
+ /// Represents hash function identifiers supported by ECDSA message signature and verification.
+ ///
+ public enum Hasher : byte
+ {
+ ///
+ /// The SHA256 hash algorithm.
+ ///
+ SHA256 = 0x00,
+
+ ///
+ /// The Keccak256 hash algorithm.
+ ///
+ Keccak256 = 0x01,
+ }
+}
diff --git a/src/Neo/Cryptography/Helper.cs b/src/Neo/Cryptography/Helper.cs
index a087620034..f13fa08118 100644
--- a/src/Neo/Cryptography/Helper.cs
+++ b/src/Neo/Cryptography/Helper.cs
@@ -12,6 +12,7 @@
using Neo.IO;
using Neo.Network.P2P.Payloads;
using Neo.Wallets;
+using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
@@ -153,6 +154,40 @@ public static byte[] Sha256(this Span value)
return Sha256((ReadOnlySpan)value);
}
+ ///
+ /// Computes the hash value for the specified byte array using the keccak256 algorithm.
+ ///
+ /// The input to compute the hash code for.
+ /// The computed hash code.
+ public static byte[] Keccak256(this byte[] value)
+ {
+ KeccakDigest keccak = new(256);
+ keccak.BlockUpdate(value, 0, value.Length);
+ byte[] result = new byte[keccak.GetDigestSize()];
+ keccak.DoFinal(result, 0);
+ return result;
+ }
+
+ ///
+ /// Computes the hash value for the specified byte array using the keccak256 algorithm.
+ ///
+ /// The input to compute the hash code for.
+ /// The computed hash code.
+ public static byte[] Keccak256(this ReadOnlySpan value)
+ {
+ return Keccak256(value.ToArray());
+ }
+
+ ///
+ /// Computes the hash value for the specified byte array using the keccak256 algorithm.
+ ///
+ /// The input to compute the hash code for.
+ /// The computed hash code.
+ public static byte[] Keccak256(this Span value)
+ {
+ return Keccak256(value.ToArray());
+ }
+
public static byte[] AES256Encrypt(this byte[] plainData, byte[] key, byte[] nonce, byte[] associatedData = null)
{
if (nonce.Length != 12) throw new ArgumentOutOfRangeException(nameof(nonce));
diff --git a/src/Neo/SmartContract/Native/CryptoLib.cs b/src/Neo/SmartContract/Native/CryptoLib.cs
index a7b822e39c..6e2876a860 100644
--- a/src/Neo/SmartContract/Native/CryptoLib.cs
+++ b/src/Neo/SmartContract/Native/CryptoLib.cs
@@ -11,7 +11,6 @@
using Neo.Cryptography;
using Neo.Cryptography.ECC;
-using Org.BouncyCastle.Crypto.Digests;
using System;
using System.Collections.Generic;
@@ -22,10 +21,12 @@ namespace Neo.SmartContract.Native
///
public sealed partial class CryptoLib : NativeContract
{
- private static readonly Dictionary curves = new()
+ private static readonly Dictionary s_curves = new()
{
- [NamedCurve.secp256k1] = ECCurve.Secp256k1,
- [NamedCurve.secp256r1] = ECCurve.Secp256r1
+ [NamedCurveHash.secp256k1SHA256] = (ECCurve.Secp256k1, Hasher.SHA256),
+ [NamedCurveHash.secp256r1SHA256] = (ECCurve.Secp256r1, Hasher.SHA256),
+ [NamedCurveHash.secp256k1Keccak256] = (ECCurve.Secp256k1, Hasher.Keccak256),
+ [NamedCurveHash.secp256r1Keccak256] = (ECCurve.Secp256r1, Hasher.Keccak256),
};
internal CryptoLib() : base() { }
@@ -73,11 +74,7 @@ public static byte[] Murmur32(byte[] data, uint seed)
[ContractMethod(Hardfork.HF_Cockatrice, CpuFee = 1 << 15)]
public static byte[] Keccak256(byte[] data)
{
- KeccakDigest keccak = new(256);
- keccak.BlockUpdate(data, 0, data.Length);
- byte[] result = new byte[keccak.GetDigestSize()];
- keccak.DoFinal(result, 0);
- return result;
+ return data.Keccak256();
}
///
@@ -86,14 +83,15 @@ public static byte[] Keccak256(byte[] data)
/// The signed message.
/// The public key to be used.
/// The signature to be verified.
- /// The curve to be used by the ECDSA algorithm.
+ /// A pair of the curve to be used by the ECDSA algorithm and the hasher function to be used to hash message.
/// if the signature is valid; otherwise, .
[ContractMethod(CpuFee = 1 << 15)]
- public static bool VerifyWithECDsa(byte[] message, byte[] pubkey, byte[] signature, NamedCurve curve)
+ public static bool VerifyWithECDsa(byte[] message, byte[] pubkey, byte[] signature, NamedCurveHash curveHash)
{
try
{
- return Crypto.VerifySignature(message, signature, pubkey, curves[curve]);
+ var ch = s_curves[curveHash];
+ return Crypto.VerifySignature(message, signature, pubkey, ch.Curve, ch.Hasher);
}
catch (ArgumentException)
{
diff --git a/src/Neo/SmartContract/Native/NamedCurve.cs b/src/Neo/SmartContract/Native/NamedCurve.cs
index 9e97472cfc..8c7a9107e9 100644
--- a/src/Neo/SmartContract/Native/NamedCurve.cs
+++ b/src/Neo/SmartContract/Native/NamedCurve.cs
@@ -9,24 +9,30 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.
+using System;
+
namespace Neo.SmartContract.Native
{
///
- /// Represents the named curve used in ECDSA.
+ /// Represents the named curve used in ECDSA. This enum is obsolete
+ /// and will be removed in future versions. Please, use an extended instead.
///
///
/// https://tools.ietf.org/html/rfc4492#section-5.1.1
///
+ [Obsolete("NamedCurve enum is obsolete and will be removed in future versions. Please, use an extended NamedCurveHash instead.")]
public enum NamedCurve : byte
{
///
/// The secp256k1 curve.
///
+ [Obsolete("secp256k1 value is obsolete and will be removed in future versions. Please, use NamedCurveHash.secp256k1SHA256 for compatible behaviour.")]
secp256k1 = 22,
///
/// The secp256r1 curve, which known as prime256v1 or nistP-256.
///
+ [Obsolete("secp256r1 value is obsolete and will be removed in future versions. Please, use NamedCurveHash.secp256r1SHA256 for compatible behaviour.")]
secp256r1 = 23
}
}
diff --git a/src/Neo/SmartContract/Native/NamedCurveHash.cs b/src/Neo/SmartContract/Native/NamedCurveHash.cs
new file mode 100644
index 0000000000..8bd63bf874
--- /dev/null
+++ b/src/Neo/SmartContract/Native/NamedCurveHash.cs
@@ -0,0 +1,40 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// NamedCurveHash.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.
+
+namespace Neo.SmartContract.Native
+{
+ ///
+ /// Represents a pair of the named curve used in ECDSA and a hash algorithm used to hash message.
+ /// This is a compatible extension of an obsolete enum.
+ ///
+ public enum NamedCurveHash : byte
+ {
+ ///
+ /// The secp256k1 curve and SHA256 hash algorithm.
+ ///
+ secp256k1SHA256 = 22,
+
+ ///
+ /// The secp256r1 curve, which known as prime256v1 or nistP-256, and SHA256 hash algorithm.
+ ///
+ secp256r1SHA256 = 23,
+
+ ///
+ /// The secp256k1 curve and Keccak256 hash algorithm.
+ ///
+ secp256k1Keccak256 = 122,
+
+ ///
+ /// The secp256r1 curve, which known as prime256v1 or nistP-256, and Keccak256 hash algorithm.
+ ///
+ secp256r1Keccak256 = 123
+ }
+}
diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs
index 6f150cca12..f65e061093 100644
--- a/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs
+++ b/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs
@@ -11,11 +11,20 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Neo.Cryptography;
using Neo.Cryptography.BLS12_381;
+using Neo.Cryptography.ECC;
+using Neo.IO;
+using Neo.Ledger;
+using Neo.Network.P2P;
+using Neo.Network.P2P.Payloads;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using Neo.VM;
using Org.BouncyCastle.Utilities.Encoders;
+using System;
+using System.Collections.Generic;
+using System.Linq;
namespace Neo.UnitTests.SmartContract.Native
{
@@ -427,5 +436,474 @@ public void TestKeccak256_BlankString()
// Assert
Assert.AreEqual(expectedHashHex, outputHashHex, "Keccak256 hash did not match expected value for blank string.");
}
+
+ // TestVerifyWithECDsa_CustomTxWitness_SingleSig builds custom witness verification script for single Koblitz public key
+ // and ensures witness check is passed for the following message:
+ //
+ // keccak256([4-bytes-network-magic-LE, txHash-bytes-BE])
+ //
+ // The proposed witness verification script has 110 bytes length, verification costs 2154270 * 10e-8GAS including Invocation script execution.
+ // The user has to sign the keccak256([4-bytes-network-magic-LE, txHash-bytes-BE]).
+ [TestMethod]
+ public void TestVerifyWithECDsa_CustomTxWitness_SingleSig()
+ {
+ byte[] privkey = "7177f0d04c79fa0b8c91fe90c1cf1d44772d1fba6e5eb9b281a22cd3aafb51fe".HexToBytes();
+ ECPoint pubKey = ECPoint.Parse("04fd0a8c1ce5ae5570fdd46e7599c16b175bf0ebdfe9c178f1ab848fb16dac74a5d301b0534c7bcf1b3760881f0c420d17084907edd771e1c9c8e941bbf6ff9108", ECCurve.Secp256k1);
+
+ // vrf is a builder of witness verification script corresponding to the public key.
+ using ScriptBuilder vrf = new();
+ vrf.EmitPush((byte)NamedCurveHash.secp256k1Keccak256); // push Koblitz curve identifier and Keccak256 hasher.
+ vrf.Emit(OpCode.SWAP); // swap curve identifier with the signature.
+ vrf.EmitPush(pubKey.EncodePoint(true)); // emit the caller's public key.
+
+ // Construct and push the signed message. The signed message is effectively the network-dependent transaction hash,
+ // i.e. msg = [4-network-magic-bytes-LE, tx-hash-BE]
+ // Firstly, retrieve network magic (it's uint32 wrapped into BigInteger and represented as Integer stackitem on stack).
+ vrf.EmitSysCall(ApplicationEngine.System_Runtime_GetNetwork); // push network magic (Integer stackitem), can have 0-5 bytes length serialized.
+
+ // Convert network magic to 4-bytes-length LE byte array representation.
+ vrf.EmitPush(0x100000000); // push 0x100000000.
+ vrf.Emit(OpCode.ADD, // the result is some new number that is 5 bytes at least when serialized, but first 4 bytes are intact network value (LE).
+ OpCode.PUSH4, OpCode.LEFT); // cut the first 4 bytes out of a number that is at least 5 bytes long, the result is 4-bytes-length LE network representation.
+
+ // Retrieve executing transaction hash.
+ vrf.EmitSysCall(ApplicationEngine.System_Runtime_GetScriptContainer); // push the script container (executing transaction, actually).
+ vrf.Emit(OpCode.PUSH0, OpCode.PICKITEM); // pick 0-th transaction item (the transaction hash).
+
+ // Concatenate network magic and transaction hash.
+ vrf.Emit(OpCode.CAT); // this instruction will convert network magic to bytes using BigInteger rules of conversion.
+
+ // Continue construction of 'verifyWithECDsa' call.
+ vrf.Emit(OpCode.PUSH4, OpCode.PACK); // pack arguments for 'verifyWithECDsa' call.
+ EmitAppCallNoArgs(vrf, CryptoLib.CryptoLib.Hash, "verifyWithECDsa", CallFlags.None); // emit the call to 'verifyWithECDsa' itself.
+
+ // Account is a hash of verification script.
+ var vrfScript = vrf.ToArray();
+ var acc = vrfScript.ToScriptHash();
+
+ var tx = new Transaction
+ {
+ Attributes = [],
+ NetworkFee = 1_0000_0000,
+ Nonce = (uint)Environment.TickCount,
+ Script = new byte[Transaction.MaxTransactionSize / 100],
+ Signers = [new Signer { Account = acc }],
+ SystemFee = 0,
+ ValidUntilBlock = 10,
+ Version = 0,
+ Witnesses = []
+ };
+ var tx_signature = Crypto.Sign(tx.GetSignData(TestBlockchain.TheNeoSystem.Settings.Network), privkey, ECCurve.Secp256k1, Hasher.Keccak256);
+
+ // inv is a builder of witness invocation script corresponding to the public key.
+ using ScriptBuilder inv = new();
+ inv.EmitPush(tx_signature); // push signature.
+
+ tx.Witnesses =
+ [
+ new Witness { InvocationScript = inv.ToArray(), VerificationScript = vrfScript }
+ ];
+
+ tx.VerifyStateIndependent(TestProtocolSettings.Default).Should().Be(VerifyResult.Succeed);
+
+ var snapshot = TestBlockchain.GetTestSnapshot();
+
+ // Create fake balance to pay the fees.
+ ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue);
+ _ = NativeContract.GAS.Mint(engine, acc, 5_0000_0000, false);
+ snapshot.Commit();
+
+ var txVrfContext = new TransactionVerificationContext();
+ var conflicts = new List();
+ tx.VerifyStateDependent(TestProtocolSettings.Default, snapshot, txVrfContext, conflicts).Should().Be(VerifyResult.Succeed);
+
+ // The resulting witness verification cost is 2154270 * 10e-8GAS.
+ // The resulting witness Invocation script (66 bytes length):
+ // NEO-VM > loadbase64 DEARoaaEjM/3VulrBDUod7eiZgWQS2iXIM0+I24iyJYmffhosZoQjfnnRymF/7+FaBPb9qvQwxLLSVo9ROlrdFdC
+ // READY: loaded 66 instructions
+ // NEO-VM 0 > ops
+ // INDEX OPCODE PARAMETER
+ // 0 PUSHDATA1 11a1a6848ccff756e96b04352877b7a26605904b689720cd3e236e22c896267df868b19a108df9e7472985ffbf856813dbf6abd0c312cb495a3d44e96b745742 <<
+ //
+ //
+ // The resulting witness verificaiton script (110 bytes):
+ // NEO-VM 0 > loadbase64 ABhQDCEC/QqMHOWuVXD91G51mcFrF1vw69/pwXjxq4SPsW2sdKVBxfug4AMAAAAAAQAAAJ4UjUEtUQgwEM6LFMAfDA92ZXJpZnlXaXRoRUNEc2EMFBv1dasRiWiEE2EKNaEohs3gtmxyQWJ9W1I=
+ // READY: loaded 110 instructions
+ // NEO-VM 0 > pos
+ // Error: No help topic for 'pos'
+ // NEO-VM 0 > ops
+ // INDEX OPCODE PARAMETER
+ // 0 PUSHINT8 122 (7a) <<
+ // 2 SWAP
+ // 3 PUSHDATA1 02fd0a8c1ce5ae5570fdd46e7599c16b175bf0ebdfe9c178f1ab848fb16dac74a5
+ // 38 SYSCALL System.Runtime.GetNetwork (c5fba0e0)
+ // 43 PUSHINT64 4294967296 (0000000001000000)
+ // 52 ADD
+ // 53 PUSH4
+ // 54 LEFT
+ // 55 SYSCALL System.Runtime.GetScriptContainer (2d510830)
+ // 60 PUSH0
+ // 61 PICKITEM
+ // 62 CAT
+ // 63 PUSH4
+ // 64 PACK
+ // 65 PUSH0
+ // 66 PUSHDATA1 766572696679576974684543447361 ("verifyWithECDsa")
+ // 83 PUSHDATA1 1bf575ab1189688413610a35a12886cde0b66c72 ("NNToUmdQBe5n8o53BTzjTFAnSEcpouyy3B", "0x726cb6e0cd8628a1350a611384688911ab75f51b")
+ // 105 SYSCALL System.Contract.Call (627d5b52)
+ }
+
+ // TestVerifyWithECDsa_CustomTxWitness_MultiSig builds custom multisignature witness verification script for Koblitz public keys
+ // and ensures witness check is passed for the M out of N multisignature of message:
+ //
+ // keccak256([4-bytes-network-magic-LE, txHash-bytes-BE])
+ //
+ // The proposed witness verification script has 264 bytes length, verification costs 8390070 * 10e-8GAS including Invocation script execution.
+ // The users have to sign the keccak256([4-bytes-network-magic-LE, txHash-bytes-BE]).
+ [TestMethod]
+ public void TestVerifyWithECDsa_CustomTxWitness_MultiSig()
+ {
+ byte[] privkey1 = "b2dde592bfce654ef03f1ceea452d2b0112e90f9f52099bcd86697a2bd0a2b60".HexToBytes();
+ ECPoint pubKey1 = ECPoint.Parse("040486468683c112125978ffe876245b2006bfe739aca8539b67335079262cb27ad0dedc9e5583f99b61c6f46bf80b97eaec3654b87add0e5bd7106c69922a229d", ECCurve.Secp256k1);
+ byte[] privkey2 = "b9879e26941872ee6c9e6f01045681496d8170ed2cc4a54ce617b39ae1891b3a".HexToBytes();
+ ECPoint pubKey2 = ECPoint.Parse("040d26fc2ad3b1aae20f040b5f83380670f8ef5c2b2ac921ba3bdd79fd0af0525177715fd4370b1012ddd10579698d186ab342c223da3e884ece9cab9b6638c7bb", ECCurve.Secp256k1);
+ byte[] privkey3 = "4e1fe2561a6da01ee030589d504d62b23c26bfd56c5e07dfc9b8b74e4602832a".HexToBytes();
+ ECPoint pubKey3 = ECPoint.Parse("047b4e72ae854b6a0955b3e02d92651ab7fa641a936066776ad438f95bb674a269a63ff98544691663d91a6cfcd215831f01bfb7a226363a6c5c67ef14541dba07", ECCurve.Secp256k1);
+ byte[] privkey4 = "6dfd066bb989d3786043aa5c1f0476215d6f5c44f5fc3392dd15e2599b67a728".HexToBytes();
+ ECPoint pubKey4 = ECPoint.Parse("04b62ac4c8a352a892feceb18d7e2e3a62c8c1ecbaae5523d89d747b0219276e225be2556a137e0e806e4915762d816cdb43f572730d23bb1b1cba750011c4edc6", ECCurve.Secp256k1);
+
+ // Public keys must be sorted, exactly like for standard CreateMultiSigRedeemScript.
+ var keys = new List<(byte[], ECPoint)>()
+ {
+ (privkey1, pubKey1),
+ (privkey2, pubKey2),
+ (privkey3, pubKey3),
+ (privkey4, pubKey4),
+ }.OrderBy(k => k.Item2).ToList();
+
+ // Consider 4 users willing to sign 3/4 multisignature transaction with their Secp256k1 private keys.
+ var m = 3;
+ var n = keys.Count();
+
+ // Must ensure the following conditions are met before verification script construction:
+ n.Should().BeGreaterThan(0);
+ m.Should().BeLessThanOrEqualTo(n);
+ keys.Select(k => k.Item2).Distinct().Count().Should().Be(n);
+
+ // In fact, the following algorithm is implemented via NeoVM instructions:
+ //
+ // func Check(sigs []interop.Signature) bool {
+ // if m != len(sigs) {
+ // return false
+ // }
+ // var pubs []interop.PublicKey = []interop.PublicKey{...}
+ // msg := append(convert.ToBytes(runtime.GetNetwork()), runtime.GetScriptContainer().Hash...)
+ // var sigCnt = 0
+ // var pubCnt = 0
+ // for ; sigCnt < m && pubCnt < n; { // sigs must be sorted by pub
+ // sigCnt += crypto.VerifyWithECDsa(msg, pubs[pubCnt], sigs[sigCnt], crypto.Secp256k1Keccak256)
+ // pubCnt++
+ // }
+ // return sigCnt == m
+ // }
+
+ // vrf is a builder of M out of N multisig witness verification script corresponding to the public keys.
+ using ScriptBuilder vrf = new();
+
+ // Start the same way as regular multisig script.
+ vrf.EmitPush(m); // push m.
+ foreach (var tuple in keys)
+ {
+ vrf.EmitPush(tuple.Item2.EncodePoint(true)); // push public keys in compressed form.
+ }
+ vrf.EmitPush(n); // push n.
+
+ // Initialize slots for local variables. Locals slot scheme:
+ // LOC0 -> sigs
+ // LOC1 -> pubs
+ // LOC2 -> msg (ByteString)
+ // LOC3 -> sigCnt (Integer)
+ // LOC4 -> pubCnt (Integer)
+ // LOC5 -> n
+ // LOC6 -> m
+ vrf.Emit(OpCode.INITSLOT, new ReadOnlySpan([7, 0])); // 7 locals, no args.
+
+ // Store n.
+ vrf.Emit(OpCode.STLOC5);
+
+ // Pack public keys and store at LOC1.
+ vrf.Emit(OpCode.LDLOC5, // load n.
+ OpCode.PACK, OpCode.STLOC1); // pack pubs and store.
+
+ // Store m.
+ vrf.Emit(OpCode.STLOC6);
+
+ // Check the number of signatures is m. Abort the execution if not.
+ vrf.Emit(OpCode.DEPTH); // push the number of signatures onto stack.
+ vrf.Emit(OpCode.LDLOC6); // load m.
+ vrf.Emit(OpCode.JMPEQ, new ReadOnlySpan([0])); // here and below short jumps are sufficient. Offset will be filled later.
+ var sigsLenCheckEndOffset = vrf.Length;
+ vrf.Emit(OpCode.ABORT); // abort the execution if length of the signatures not equal to m.
+
+ // Start the verification itself.
+ var checkStartOffset = vrf.Length;
+
+ // Pack signatures and store at LOC0.
+ vrf.Emit(OpCode.LDLOC6); // load m.
+ vrf.Emit(OpCode.PACK, OpCode.STLOC0);
+
+ // Get message and store it at LOC2.
+ // msg = [4-network-magic-bytes-LE, tx-hash-BE]
+ vrf.EmitSysCall(ApplicationEngine.System_Runtime_GetNetwork); // push network magic (Integer stackitem), can have 0-5 bytes length serialized.
+ // Convert network magic to 4-bytes-length LE byte array representation.
+ vrf.EmitPush(0x100000000); // push 0x100000000.
+ vrf.Emit(OpCode.ADD, // the result is some new number that is 5 bytes at least when serialized, but first 4 bytes are intact network value (LE).
+ OpCode.PUSH4, OpCode.LEFT); // cut the first 4 bytes out of a number that is at least 5 bytes long, the result is 4-bytes-length LE network representation.
+ // Retrieve executing transaction hash.
+ vrf.EmitSysCall(ApplicationEngine.System_Runtime_GetScriptContainer); // push the script container (executing transaction, actually).
+ vrf.Emit(OpCode.PUSH0, OpCode.PICKITEM); // pick 0-th transaction item (the transaction hash).
+ // Concatenate network magic and transaction hash.
+ vrf.Emit(OpCode.CAT); // this instruction will convert network magic to bytes using BigInteger rules of conversion.
+ vrf.Emit(OpCode.STLOC2); // store msg as a local variable #2.
+
+ // Initialize local variables: sigCnt, pubCnt.
+ vrf.Emit(OpCode.PUSH0, OpCode.STLOC3, // initialize sigCnt.
+ OpCode.PUSH0, OpCode.STLOC4); // initialize pubCnt.
+
+ // Loop condition check.
+ var loopStartOffset = vrf.Length;
+ vrf.Emit(OpCode.LDLOC3); // load sigCnt.
+ vrf.Emit(OpCode.LDLOC6); // load m.
+ vrf.Emit(OpCode.GE, // sigCnt >= m
+ OpCode.LDLOC4); // load pubCnt
+ vrf.Emit(OpCode.LDLOC5); // load n.
+ vrf.Emit(OpCode.GE, // pubCnt >= n
+ OpCode.OR); // sigCnt >= m || pubCnt >= n
+ vrf.Emit(OpCode.JMPIF, new ReadOnlySpan([0])); // jump to the end of the script if (sigCnt >= m || pubCnt >= n).
+ var loopConditionOffset = vrf.Length;
+
+ // Loop start. Prepare arguments and call CryptoLib's verifyWithECDsa.
+ vrf.EmitPush((byte)NamedCurveHash.secp256k1Keccak256); // push Koblitz curve identifier and Keccak256 hasher.
+ vrf.Emit(OpCode.LDLOC0, // load signatures.
+ OpCode.LDLOC3, // load sigCnt.
+ OpCode.PICKITEM, // pick signature at index sigCnt.
+ OpCode.LDLOC1, // load pubs.
+ OpCode.LDLOC4, // load pubCnt.
+ OpCode.PICKITEM, // pick pub at index pubCnt.
+ OpCode.LDLOC2, // load msg.
+ OpCode.PUSH4, OpCode.PACK); // pack 4 arguments for 'verifyWithECDsa' call.
+ EmitAppCallNoArgs(vrf, CryptoLib.CryptoLib.Hash, "verifyWithECDsa", CallFlags.None); // emit the call to 'verifyWithECDsa' itself.
+
+ // Update loop variables.
+ vrf.Emit(OpCode.LDLOC3, OpCode.ADD, OpCode.STLOC3, // increment sigCnt if signature is valid.
+ OpCode.LDLOC4, OpCode.INC, OpCode.STLOC4); // increment pubCnt.
+
+ // End of the loop.
+ vrf.Emit(OpCode.JMP, new ReadOnlySpan([0])); // jump to the start of cycle.
+ var loopEndOffset = vrf.Length;
+ // Return condition: the number of valid signatures should be equal to m.
+ var progRetOffset = vrf.Length;
+ vrf.Emit(OpCode.LDLOC3); // load sigCnt.
+ vrf.Emit(OpCode.LDLOC6); // load m.
+ vrf.Emit(OpCode.NUMEQUAL); // push m == sigCnt.
+
+ var vrfScript = vrf.ToArray();
+
+ // Set JMP* instructions offsets. "-1" is for short JMP parameter offset. JMP parameters
+ // are relative offsets.
+ vrfScript[sigsLenCheckEndOffset - 1] = (byte)(checkStartOffset - sigsLenCheckEndOffset + 2);
+ vrfScript[loopEndOffset - 1] = (byte)(loopStartOffset - loopEndOffset + 2);
+ vrfScript[loopConditionOffset - 1] = (byte)(progRetOffset - loopConditionOffset + 2);
+
+ // Account is a hash of verification script.
+ var acc = vrfScript.ToScriptHash();
+
+ var tx = new Transaction
+ {
+ Attributes = [],
+ NetworkFee = 1_0000_0000,
+ Nonce = (uint)Environment.TickCount,
+ Script = new byte[Transaction.MaxTransactionSize / 100],
+ Signers = [new Signer { Account = acc }],
+ SystemFee = 0,
+ ValidUntilBlock = 10,
+ Version = 0,
+ Witnesses = []
+ };
+ // inv is a builder of witness invocation script corresponding to the public key.
+ using ScriptBuilder inv = new();
+ for (var i = 0; i < n; i++)
+ {
+ if (i == 1) // Skip one key since we need only 3 signatures.
+ continue;
+ var sig = Crypto.Sign(tx.GetSignData(TestBlockchain.TheNeoSystem.Settings.Network), keys[i].Item1, ECCurve.Secp256k1, Hasher.Keccak256);
+ inv.EmitPush(sig);
+ }
+
+ tx.Witnesses =
+ [
+ new Witness { InvocationScript = inv.ToArray(), VerificationScript = vrfScript }
+ ];
+
+ tx.VerifyStateIndependent(TestProtocolSettings.Default).Should().Be(VerifyResult.Succeed);
+
+ var snapshot = TestBlockchain.GetTestSnapshot();
+
+ // Create fake balance to pay the fees.
+ ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue);
+ _ = NativeContract.GAS.Mint(engine, acc, 5_0000_0000, false);
+ snapshot.Commit();
+
+ // Check that witness verification passes.
+ var txVrfContext = new TransactionVerificationContext();
+ var conflicts = new List();
+ tx.VerifyStateDependent(TestProtocolSettings.Default, snapshot, txVrfContext, conflicts).Should().Be(VerifyResult.Succeed);
+
+ // The resulting witness verification cost for 3/4 multisig is 8389470 * 10e-8GAS. Cost depends on M/N.
+ // The resulting witness Invocation script (198 bytes for 3 signatures):
+ // NEO-VM 0 > loadbase64 DEDM23XByPvDK9XRAHRhfGH7/Mp5jdaci3/GpTZ3D9SZx2Zw89tAaOtmQSIutXbCxRQA1kSeUD4AteJGoNXFhFzIDECgeHoey0rYdlFyTVfDJSsuS+VwzC5OtYGCVR2V/MttmLXWA/FWZH/MjmU0obgQXa9zoBxqYQUUJKefivZFxVcTDEAZT6L6ZFybeXbm8+RlVNS7KshusT54d2ImQ6vFvxETphhJOwcQ0yNL6qJKsrLAKAnzicY4az3ct0G35mI17/gQ
+ // READY: loaded 198 instructions
+ // NEO-VM 0 > ops
+ // INDEX OPCODE PARAMETER
+ // 0 PUSHDATA1 ccdb75c1c8fbc32bd5d10074617c61fbfcca798dd69c8b7fc6a536770fd499c76670f3db4068eb6641222eb576c2c51400d6449e503e00b5e246a0d5c5845cc8 <<
+ // 66 PUSHDATA1 a0787a1ecb4ad87651724d57c3252b2e4be570cc2e4eb58182551d95fccb6d98b5d603f156647fcc8e6534a1b8105daf73a01c6a61051424a79f8af645c55713
+ // 132 PUSHDATA1 194fa2fa645c9b7976e6f3e46554d4bb2ac86eb13e7877622643abc5bf1113a618493b0710d3234beaa24ab2b2c02809f389c6386b3ddcb741b7e66235eff810
+ //
+ //
+ // Resulting witness verification script (266 bytes for 3/4 multisig):
+ // NEO-VM 0 > loadbase64 EwwhAwSGRoaDwRISWXj/6HYkWyAGv+c5rKhTm2czUHkmLLJ6DCEDDSb8KtOxquIPBAtfgzgGcPjvXCsqySG6O915/QrwUlEMIQN7TnKuhUtqCVWz4C2SZRq3+mQak2Bmd2rUOPlbtnSiaQwhArYqxMijUqiS/s6xjX4uOmLIwey6rlUj2J10ewIZJ24iFFcHAHVtwHF2Q24oAzhuwHBBxfug4AMAAAAAAQAAAJ4UjUEtUQgwEM6LchBzEHRrbrhsbbiSJEIAGGhrzmlszmoUwB8MD3ZlcmlmeVdpdGhFQ0RzYQwUG/V1qxGJaIQTYQo1oSiGzeC2bHJBYn1bUmuec2ycdCK5a26z
+ // READY: loaded 264 instructions
+ // NEO-VM 0 > ops
+ // INDEX OPCODE PARAMETER
+ // 0 PUSH3 <<
+ // 1 PUSHDATA1 030486468683c112125978ffe876245b2006bfe739aca8539b67335079262cb27a
+ // 36 PUSHDATA1 030d26fc2ad3b1aae20f040b5f83380670f8ef5c2b2ac921ba3bdd79fd0af05251
+ // 71 PUSHDATA1 037b4e72ae854b6a0955b3e02d92651ab7fa641a936066776ad438f95bb674a269
+ // 106 PUSHDATA1 02b62ac4c8a352a892feceb18d7e2e3a62c8c1ecbaae5523d89d747b0219276e22
+ // 141 PUSH4
+ // 142 INITSLOT 7 local, 0 arg
+ // 145 STLOC5
+ // 146 LDLOC5
+ // 147 PACK
+ // 148 STLOC1
+ // 149 STLOC6
+ // 150 DEPTH
+ // 151 LDLOC6
+ // 152 JMPEQ 155 (3/03)
+ // 154 ABORT
+ // 155 LDLOC6
+ // 156 PACK
+ // 157 STLOC0
+ // 158 SYSCALL System.Runtime.GetNetwork (c5fba0e0)
+ // 163 PUSHINT64 4294967296 (0000000001000000)
+ // 172 ADD
+ // 173 PUSH4
+ // 174 LEFT
+ // 175 SYSCALL System.Runtime.GetScriptContainer (2d510830)
+ // 180 PUSH0
+ // 181 PICKITEM
+ // 182 CAT
+ // 183 STLOC2
+ // 184 PUSH0
+ // 185 STLOC3
+ // 186 PUSH0
+ // 187 STLOC4
+ // 188 LDLOC3
+ // 189 LDLOC6
+ // 190 GE
+ // 191 LDLOC4
+ // 192 LDLOC5
+ // 193 GE
+ // 194 OR
+ // 195 JMPIF 261 (66/42)
+ // 197 PUSHINT8 122 (7a)
+ // 199 LDLOC0
+ // 200 LDLOC3
+ // 201 PICKITEM
+ // 202 LDLOC1
+ // 203 LDLOC4
+ // 204 PICKITEM
+ // 205 LDLOC2
+ // 206 PUSH4
+ // 207 PACK
+ // 208 PUSH0
+ // 209 PUSHDATA1 766572696679576974684543447361 ("verifyWithECDsa")
+ // 226 PUSHDATA1 1bf575ab1189688413610a35a12886cde0b66c72 ("NNToUmdQBe5n8o53BTzjTFAnSEcpouyy3B", "0x726cb6e0cd8628a1350a611384688911ab75f51b")
+ // 248 SYSCALL System.Contract.Call (627d5b52)
+ // 253 LDLOC3
+ // 254 ADD
+ // 255 STLOC3
+ // 256 LDLOC4
+ // 257 INC
+ // 258 STLOC4
+ // 259 JMP 188 (-71/b9)
+ // 261 LDLOC3
+ // 262 LDLOC6
+ // 263 NUMEQUAL
+ }
+
+ // EmitAppCallNoArgs is a helper method that emits all parameters of System.Contract.Call interop
+ // except the method arguments.
+ private static ScriptBuilder EmitAppCallNoArgs(ScriptBuilder builder, UInt160 contractHash, string method, CallFlags f)
+ {
+ builder.EmitPush((byte)f);
+ builder.EmitPush(method);
+ builder.EmitPush(contractHash);
+ builder.EmitSysCall(ApplicationEngine.System_Contract_Call);
+ return builder;
+ }
+
+ [TestMethod]
+ public void TestVerifyWithECDsa()
+ {
+ byte[] privR1 = "6e63fda41e9e3aba9bb5696d58a75731f044a9bdc48fe546da571543b2fa460e".HexToBytes();
+ ECPoint pubR1 = ECPoint.Parse("04cae768e1cf58d50260cab808da8d6d83d5d3ab91eac41cdce577ce5862d736413643bdecd6d21c3b66f122ab080f9219204b10aa8bbceb86c1896974768648f3", ECCurve.Secp256r1);
+ byte[] privK1 = "0b5fb3a050385196b327be7d86cbce6e40a04c8832445af83ad19c82103b3ed9".HexToBytes();
+ ECPoint pubK1 = ECPoint.Parse("04b6363b353c3ee1620c5af58594458aa00abf43a6d134d7c4cb2d901dc0f474fd74c94740bd7169aa0b1ef7bc657e824b1d7f4283c547e7ec18c8576acf84418a", ECCurve.Secp256k1);
+ byte[] message = System.Text.Encoding.Default.GetBytes("HelloWorld");
+
+ // secp256r1 + SHA256
+ byte[] signature = Crypto.Sign(message, privR1, ECCurve.Secp256r1, Hasher.SHA256);
+ Crypto.VerifySignature(message, signature, pubR1).Should().BeTrue(); // SHA256 hash is used by default.
+ CallVerifyWithECDsa(message, pubR1, signature, NamedCurveHash.secp256r1SHA256).Should().Be(true);
+
+ // secp256r1 + Keccak256
+ signature = Crypto.Sign(message, privR1, ECCurve.Secp256r1, Hasher.Keccak256);
+ Crypto.VerifySignature(message, signature, pubR1, Hasher.Keccak256).Should().BeTrue();
+ CallVerifyWithECDsa(message, pubR1, signature, NamedCurveHash.secp256r1Keccak256).Should().Be(true);
+
+ // secp256k1 + SHA256
+ signature = Crypto.Sign(message, privK1, ECCurve.Secp256k1, Hasher.SHA256);
+ Crypto.VerifySignature(message, signature, pubK1).Should().BeTrue(); // SHA256 hash is used by default.
+ CallVerifyWithECDsa(message, pubK1, signature, NamedCurveHash.secp256k1SHA256).Should().Be(true);
+
+ // secp256k1 + Keccak256
+ signature = Crypto.Sign(message, privK1, ECCurve.Secp256k1, Hasher.Keccak256);
+ Crypto.VerifySignature(message, signature, pubK1, Hasher.Keccak256).Should().BeTrue();
+ CallVerifyWithECDsa(message, pubK1, signature, NamedCurveHash.secp256k1Keccak256).Should().Be(true);
+ }
+
+ private bool CallVerifyWithECDsa(byte[] message, ECPoint pub, byte[] signature, NamedCurveHash curveHash)
+ {
+ var snapshot = TestBlockchain.GetTestSnapshot();
+ using (ScriptBuilder script = new())
+ {
+ script.EmitPush((int)curveHash);
+ script.EmitPush(signature);
+ script.EmitPush(pub.EncodePoint(true));
+ script.EmitPush(message);
+ script.EmitPush(4);
+ script.Emit(OpCode.PACK);
+ script.EmitPush(CallFlags.All);
+ script.EmitPush("verifyWithECDsa");
+ script.EmitPush(NativeContract.CryptoLib.Hash);
+ script.EmitSysCall(ApplicationEngine.System_Contract_Call);
+
+ using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings);
+ engine.LoadScript(script.ToArray());
+ Assert.AreEqual(VMState.HALT, engine.Execute());
+ return engine.ResultStack.Pop().GetBoolean();
+ }
+ }
}
}
diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs
index 181ff139ec..d7be3b4dcd 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":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":1991619121},"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":"deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"Any","offset":56,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"}],"returntype":"String","offset":63,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"},{"name":"base","type":"Integer"}],"returntype":"String","offset":70,"safe":true},{"name":"jsonDeserialize","parameters":[{"name":"json","type":"ByteArray"}],"returntype":"Any","offset":77,"safe":true},{"name":"jsonSerialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":84,"safe":true},{"name":"memoryCompare","parameters":[{"name":"str1","type":"ByteArray"},{"name":"str2","type":"ByteArray"}],"returntype":"Integer","offset":91,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"}],"returntype":"Integer","offset":98,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"}],"returntype":"Integer","offset":105,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"},{"name":"backward","type":"Boolean"}],"returntype":"Integer","offset":112,"safe":true},{"name":"serialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":119,"safe":true},{"name":"strLen","parameters":[{"name":"str","type":"String"}],"returntype":"Integer","offset":126,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"}],"returntype":"Array","offset":133,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"},{"name":"removeEmptyEntries","type":"Boolean"}],"returntype":"Array","offset":140,"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":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"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":"curve","type":"Integer"}],"returntype":"Boolean","offset":70,"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":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"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}],"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":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1325686241},"manifest":{"name":"NeoToken","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":"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":"registerCandidate","parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","offset":77,"safe":false},{"name":"setGasPerBlock","parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Void","offset":84,"safe":false},{"name":"setRegisterPrice","parameters":[{"name":"registerPrice","type":"Integer"}],"returntype":"Void","offset":91,"safe":false},{"name":"symbol","parameters":[],"returntype":"String","offset":98,"safe":true},{"name":"totalSupply","parameters":[],"returntype":"Integer","offset":105,"safe":true},{"name":"transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","offset":112,"safe":false},{"name":"unclaimedGas","parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer","offset":119,"safe":true},{"name":"unregisterCandidate","parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","offset":126,"safe":false},{"name":"vote","parameters":[{"name":"account","type":"Hash160"},{"name":"voteTo","type":"PublicKey"}],"returntype":"Boolean","offset":133,"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}}"""},