diff --git a/Substrate.NetApi.Test/Keys/Ed25519Tests.cs b/Substrate.NetApi.Test/Keys/Ed25519Tests.cs index beb6354..a9dc9ef 100644 --- a/Substrate.NetApi.Test/Keys/Ed25519Tests.cs +++ b/Substrate.NetApi.Test/Keys/Ed25519Tests.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; using Substrate.NetApi.Model.Types; using Substrate.NetApi.Model.Types.Base; @@ -133,7 +134,7 @@ public void Ed25519SignatureTestComparePolkadotJs(string polkadotJsSignature) [Test] [TestCase("0xd2baabb61bcd0026e797136cb0938d55e3c3ea8825c163eb3d1738b3c79af8e8f4953ba4767dc5477202756d3fba97bc50fc3ac8355ff5acfba88a36311f2f0f")] - public void AccountEd25519SignatureTestComparePolkadotJs(string polkadotJsSignature) + public async Task AccountEd25519SignatureTestComparePolkadotJsAsync(string polkadotJsSignature) { var rawSeed = "0x70f93a75dbc6ad5b0c051210704a00a9937732d0c360792b0fea24efb8ea8465"; @@ -146,12 +147,14 @@ public void AccountEd25519SignatureTestComparePolkadotJs(string polkadotJsSignat // According to https://github.com/polkadot-js/apps/blob/master/packages/page-signing/src/Sign.tsx#L93 var messageBytes = WrapMessage.Wrap(message); - var signature = account.Sign(messageBytes); - var singatureHexString = Utils.Bytes2HexString(signature); + var signature1 = await Task.Run(() => account.Sign(messageBytes)); + var signature2 = await account.SignAsync(messageBytes); - // SIGn C#: 0x679FA7BC8B2A7C40B5ECD50CA041E961DB8971D2B454DB7DE64E543B3C1892A6D3F223DDA01C66B9878C149CFCC8B86ECF2B20F11F7610596F51479405776907 + Assert.IsTrue(signature1.SequenceEqual(signature2)); + + Assert.True(account.Verify(signature1, account.Bytes, messageBytes)); + Assert.True(account.Verify(signature2, account.Bytes, messageBytes)); - // SIGn PolkaJS:0xd2baabb61bcd0026e797136cb0938d55e3c3ea8825c163eb3d1738b3c79af8e8f4953ba4767dc5477202756d3fba97bc50fc3ac8355ff5acfba88a36311f2f0f Assert.True(account.Verify(Utils.HexToByteArray(polkadotJsSignature), account.Bytes, messageBytes)); } } diff --git a/Substrate.NetApi.Test/Keys/Sr25519Tests.cs b/Substrate.NetApi.Test/Keys/Sr25519Tests.cs index 9f69c96..90b8a3a 100644 --- a/Substrate.NetApi.Test/Keys/Sr25519Tests.cs +++ b/Substrate.NetApi.Test/Keys/Sr25519Tests.cs @@ -4,6 +4,8 @@ using NUnit.Framework; using Schnorrkel.Keys; using Substrate.NetApi.Model.Types; +using System.Threading.Tasks; +using System.Linq; namespace Substrate.NetApi.Test.Keys { @@ -88,7 +90,7 @@ public void Sr25519SignatureTestComparePolkadotJs(string polkadotJsSignature) [Test] [TestCase("0x5c42ac4e2d55b8e59d9b255af370de03fe177f5545eecbbd784531cb2eb1f2553e0e2b91656f99fae930eb6ff8ac1a3eca4e19d307ecb39832a479a478a8608a")] - public void AccountSr25519SignatureTestComparePolkadotJs(string polkadotJsSignature) + public async Task AccountSr25519SignatureTestComparePolkadotJsAsync(string polkadotJsSignature) { var miniSecretAlice = new MiniSecret(Utils.HexToByteArray("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a"), ExpandMode.Ed25519); @@ -97,13 +99,14 @@ public void AccountSr25519SignatureTestComparePolkadotJs(string polkadotJsSignat var message = "I test this signature!"; var messageBytes = WrapMessage.Wrap(message); - var simpleSign = account.Sign(messageBytes); - var singatureHexString = Utils.Bytes2HexString(simpleSign); - // SIGn C#: 0x2A6346A8707A9929B65167C448F719FE977F2EE04D2CB250685C98C79CCBF2458901F9B386D08422D9102FBD8BF7CFECDF7605F4CDC5FA8D121E2E9730F9098C + var signature1 = await Task.Run(() => account.Sign(messageBytes)); + var signature2 = await account.SignAsync(messageBytes); - // SIGn PolkaJS:0x5c42ac4e2d55b8e59d9b255af370de03fe177f5545eecbbd784531cb2eb1f2553e0e2b91656f99fae930eb6ff8ac1a3eca4e19d307ecb39832a479a478a8608a - var simpleSign2 = Utils.HexToByteArray(polkadotJsSignature); - Assert.True(account.Verify(simpleSign2, account.Bytes, messageBytes)); + Assert.True(account.Verify(signature1, account.Bytes, messageBytes)); + Assert.True(account.Verify(signature2, account.Bytes, messageBytes)); + + var signature3 = Utils.HexToByteArray(polkadotJsSignature); + Assert.True(account.Verify(signature3, account.Bytes, messageBytes)); } } } \ No newline at end of file diff --git a/Substrate.NetApi/Model/Extrinsics/Extrinsic.cs b/Substrate.NetApi/Model/Extrinsics/Extrinsic.cs index 877d6de..fd9d3cc 100644 --- a/Substrate.NetApi/Model/Extrinsics/Extrinsic.cs +++ b/Substrate.NetApi/Model/Extrinsics/Extrinsic.cs @@ -55,21 +55,23 @@ public class Extrinsic /// /// The string. /// - public Extrinsic(string str, ChargeType chargeType) : this(Utils.HexToByteArray(str).AsMemory(), chargeType) + public Extrinsic(string str, ChargeType chargeType) + : this(Utils.HexToByteArray(str).AsMemory(), chargeType) { } /// /// Initializes a new instance of the class. /// - /// The memory. + /// + /// internal Extrinsic(Memory memory, ChargeType chargeType) { int p = 0; int m; // length - var length = CompactInteger.Decode(memory.ToArray(), ref p); + _ = CompactInteger.Decode(memory.ToArray(), ref p); // signature version m = 1; @@ -83,7 +85,7 @@ internal Extrinsic(Memory memory, ChargeType chargeType) { // start bytes m = 1; - var _startBytes = memory.Slice(p, m).ToArray()[0]; + _ = memory.Slice(p, m).ToArray()[0]; p += m; // sender public key diff --git a/Substrate.NetApi/Model/Extrinsics/Payload.cs b/Substrate.NetApi/Model/Extrinsics/Payload.cs index 5199b10..6a1b84c 100644 --- a/Substrate.NetApi/Model/Extrinsics/Payload.cs +++ b/Substrate.NetApi/Model/Extrinsics/Payload.cs @@ -1,15 +1,22 @@ -using System.Linq; +using Substrate.NetApi.Model.Types; +using System.Linq; namespace Substrate.NetApi.Model.Extrinsics { /// /// Payload /// - public class Payload + public class Payload : IEncodable { - private readonly Method _call; - - private readonly SignedExtensions _signedExtension; + /// + /// The call + /// + public Method Call { get; } + + /// + /// Signed extension + /// + public SignedExtensions SignedExtension { get; } /// /// Initializes a new instance of the class. @@ -18,17 +25,25 @@ public class Payload /// The signed extensions. public Payload(Method call, SignedExtensions signedExtensions) { - _call = call; - _signedExtension = signedExtensions; + Call = call; + SignedExtension = signedExtensions; } /// - /// Encodes this instance. + /// Encodes this instance, returns the encoded bytes. Additionally, if + /// the encoded bytes are longer than 256 bytes, they are hashed using `blake2_256`. /// /// public byte[] Encode() { - byte[] bytes = _call.Encode().Concat(_signedExtension.Encode()).ToArray(); + byte[] bytes = Call.Encode().Concat(SignedExtension.Encode()).ToArray(); + + // Payloads longer than 256 bytes are going to be `blake2_256`-hashed. + if (bytes.Length > 256) + { + bytes = HashExtension.Blake2(bytes, 256); + } + return bytes; } } diff --git a/Substrate.NetApi/Model/Extrinsics/SignedExtensions.cs b/Substrate.NetApi/Model/Extrinsics/SignedExtensions.cs index b9a3870..bc6bc5c 100644 --- a/Substrate.NetApi/Model/Extrinsics/SignedExtensions.cs +++ b/Substrate.NetApi/Model/Extrinsics/SignedExtensions.cs @@ -8,19 +8,40 @@ namespace Substrate.NetApi.Model.Extrinsics /// public class SignedExtensions { - private readonly uint _specVersion; + /// + /// Specification Version + /// + public uint SpecVersion { get; } - private readonly uint _txVersion; + /// + /// Transaction Version + /// + public uint TxVersion { get; } - private readonly Hash _genesis; + /// + /// Genesis Hash + /// + public Hash Genesis { get; } - private readonly Hash _startEra; + /// + /// Start Era + /// + public Hash StartEra { get; } - private readonly Era _mortality; + /// + /// Mortality + /// + public Era Mortality { get; } - private readonly CompactInteger _nonce; + /// + /// Nonce + /// + public CompactInteger Nonce { get; } - private readonly ChargeType _charge; + /// + /// Charge + /// + public ChargeType Charge { get; } /// /// Initializes a new instance of the class. @@ -31,16 +52,16 @@ public class SignedExtensions /// The start era. /// The mortality. /// The nonce. - /// The charge transaction payment. + /// The charge transaction payment. public SignedExtensions(uint specVersion, uint txVersion, Hash genesis, Hash startEra, Era mortality, CompactInteger nonce, ChargeType charge) { - _specVersion = specVersion; - _txVersion = txVersion; - _genesis = genesis; - _startEra = startEra; - _mortality = mortality; - _nonce = nonce; - _charge = charge; + SpecVersion = specVersion; + TxVersion = txVersion; + Genesis = genesis; + StartEra = startEra; + Mortality = mortality; + Nonce = nonce; + Charge = charge; } /// @@ -52,13 +73,13 @@ public byte[] GetExtra() var bytes = new List(); // CheckMortality - bytes.AddRange(_mortality.Encode()); + bytes.AddRange(Mortality.Encode()); // CheckNonce - bytes.AddRange(_nonce.Encode()); + bytes.AddRange(Nonce.Encode()); // ChargeType - bytes.AddRange(_charge.Encode()); + bytes.AddRange(Charge.Encode()); return bytes.ToArray(); } @@ -72,16 +93,16 @@ public byte[] GetAdditionalSigned() var bytes = new List(); // CheckSpecVersion - bytes.AddRange(Utils.Value2Bytes(_specVersion)); + bytes.AddRange(Utils.Value2Bytes(SpecVersion)); // CheckTxVersion - bytes.AddRange(Utils.Value2Bytes(_txVersion)); + bytes.AddRange(Utils.Value2Bytes(TxVersion)); // CheckGenesis - bytes.AddRange(_genesis.Bytes); + bytes.AddRange(Genesis.Bytes); // CheckMortality, Additional Blockhash check. Immortal = genesis_hash, Mortal = logic - bytes.AddRange(_startEra.Bytes); + bytes.AddRange(StartEra.Bytes); return bytes.ToArray(); } diff --git a/Substrate.NetApi/Model/Extrinsics/UnCheckedExtrinsic.cs b/Substrate.NetApi/Model/Extrinsics/UnCheckedExtrinsic.cs index f2d77ef..2175740 100644 --- a/Substrate.NetApi/Model/Extrinsics/UnCheckedExtrinsic.cs +++ b/Substrate.NetApi/Model/Extrinsics/UnCheckedExtrinsic.cs @@ -6,11 +6,20 @@ namespace Substrate.NetApi.Model.Extrinsics { + /// + /// Unchecked Extrinsic + /// public class UnCheckedExtrinsic : Extrinsic { - private readonly Hash _genesis; + /// + /// Genesis Hash + /// + private Hash Genesis { get; } - private readonly Hash _startEra; + /// + /// Start Era + /// + private Hash StartEra { get; } /// /// Initializes a new instance of the class. @@ -26,8 +35,8 @@ public class UnCheckedExtrinsic : Extrinsic public UnCheckedExtrinsic(bool signed, Account account, Method method, Era era, CompactInteger nonce, ChargeType charge, Hash genesis, Hash startEra) : base(signed, account, nonce, method, era, charge) { - _genesis = genesis; - _startEra = startEra; + Genesis = genesis; + StartEra = startEra; } /// @@ -37,7 +46,7 @@ public UnCheckedExtrinsic(bool signed, Account account, Method method, Era era, /// public Payload GetPayload(RuntimeVersion runtime) { - return new Payload(Method, new SignedExtensions(runtime.SpecVersion, runtime.TransactionVersion, _genesis, _startEra, Era, Nonce, Charge)); + return new Payload(Method, new SignedExtensions(runtime.SpecVersion, runtime.TransactionVersion, Genesis, StartEra, Era, Nonce, Charge)); } /// @@ -50,11 +59,11 @@ public void AddPayloadSignature(byte[] signature) } /// - /// Encodes this instance. + /// Encode this instance, returns the encoded bytes. /// /// - /// Missing payload signature for signed transaction. - public byte[] Encode() + /// + public new byte[] Encode() { if (Signed && Signature == null) { diff --git a/Substrate.NetApi/Model/Types/Account.cs b/Substrate.NetApi/Model/Types/Account.cs index a08a67d..9bcd701 100644 --- a/Substrate.NetApi/Model/Types/Account.cs +++ b/Substrate.NetApi/Model/Types/Account.cs @@ -1,24 +1,31 @@ using Chaos.NaCl; using Newtonsoft.Json; using Schnorrkel; +using Substrate.NetApi.Model.Extrinsics; using Substrate.NetApi.Model.Types.Base; using System; using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; namespace Substrate.NetApi.Model.Types { /// - /// Represents a key type. + /// Enum KeyType represents the type of cryptographic keys used in digital signatures. /// public enum KeyType { /// - /// Ed25519 + /// Ed25519: Elliptic Curve Digital Signature Algorithm using SHA-512 and Curve25519. + /// Preferred for its balance of security and performance, suitable for scenarios + /// requiring fast signature verification. Commonly used in secure communication, + /// authentication, and blockchain applications. /// Ed25519, /// - /// Sr25519 + /// Sr25519: Schnorr signature scheme using SHA-512 and Curve25519, implemented in Schnorrkel. + /// Offers advantages in complex cryptographic constructions and potentially better performance. + /// Frequently used in decentralized systems and advanced cryptographic protocols. /// Sr25519 } @@ -29,20 +36,27 @@ public enum KeyType public interface IAccount { /// - /// Sign the specified message. + /// Asynchronouslys sign the specified message. /// - /// + /// The message bytes. /// - byte[] Sign(byte[] message); + Task SignAsync(byte[] message); /// - /// Verifies a signature from this account. + /// Asynchronouslys sign the specified payload. /// - /// - /// - /// + /// The payload. /// - bool Verify(byte[] signature, byte[] publicKey, byte[] message); + Task SignPayloadAsync(Payload payload); + + /// + /// Asynchronouslys verifies a signature from this account. + /// + /// The signature to verify. + /// The public key to verify the signature with. + /// The message to verify the signature with. + /// + Task VerifyAsync(byte[] signature, byte[] publicKey, byte[] message); } /// @@ -106,6 +120,12 @@ public void Create(KeyType keyType, byte[] publicKey) Create(keyType, null, publicKey); } + /// + public override void CreateFromJson(string str) + { + throw new NotSupportedException("CreateFromJson is not supported for Account."); + } + /// /// Builds the specified key type. /// @@ -120,12 +140,18 @@ public static Account Build(KeyType keyType, byte[] privateKey, byte[] publicKey return account; } + /// + public virtual async Task SignAsync(byte[] message) + { + return await Task.Run(() => Sign(message)); + } + /// /// Signs the specified message. /// /// /// - /// + /// ) public byte[] Sign(byte[] message) { switch (KeyType) @@ -141,6 +167,12 @@ public byte[] Sign(byte[] message) } } + /// + public virtual async Task SignPayloadAsync(Payload payload) + { + return await SignAsync(payload.Encode()); + } + /// /// Verifies a signature from this account. /// @@ -174,5 +206,11 @@ public bool Verify(byte[] signature, byte[] publicKey, byte[] message) throw new NotSupportedException($"Unknown key type found '{KeyType}'."); } } + + /// + public async Task VerifyAsync(byte[] signature, byte[] publicKey, byte[] message) + { + return await Task.Run(() => Verify(signature, publicKey, message)); + } } } \ No newline at end of file diff --git a/Substrate.NetApi/Model/Types/Base/BaseType.cs b/Substrate.NetApi/Model/Types/Base/BaseType.cs index d39c2c4..89ff712 100644 --- a/Substrate.NetApi/Model/Types/Base/BaseType.cs +++ b/Substrate.NetApi/Model/Types/Base/BaseType.cs @@ -56,13 +56,13 @@ public virtual void Create(byte[] byteArray) } /// - /// Create from a json string + /// Create from JSON is used to deserialize in GenericTypeConverters, to automatically convert scale encoded JSON Rust types to C# types. /// /// public virtual void CreateFromJson(string str) => Create(Utils.HexToByteArray(str)); /// - /// New + /// Create a new instance of the type, this uses the default constructor. /// /// public IType New() => this; diff --git a/Substrate.NetApi/RequestGenerator.cs b/Substrate.NetApi/RequestGenerator.cs index 01a4873..473761d 100644 --- a/Substrate.NetApi/RequestGenerator.cs +++ b/Substrate.NetApi/RequestGenerator.cs @@ -8,6 +8,7 @@ using Substrate.NetApi.Model.Types.Base; using Chaos.NaCl; using Schnorrkel; +using System.Threading.Tasks; namespace Substrate.NetApi { @@ -17,15 +18,15 @@ namespace Substrate.NetApi public class RequestGenerator { /// - /// Create a request for a storage call, for generated code. + /// Get the storage key. /// - /// - /// - /// - /// - /// - /// + /// The module name. + /// The item name. + /// The storage type. + /// The hashers. + /// The keys. /// + /// public static string GetStorage(string module, string item, Storage.Type type, Storage.Hasher[] hashers = null, IType[] keys = null) { var keybytes = GetStorageKeyBytesHash(module, item); @@ -70,13 +71,13 @@ public static byte[] GetStorageKeyBytesHash(string module, string item) /// The method. /// The era. /// The nonce. - /// The tip. + /// The charge. /// The genesis. /// The start era. /// The runtime. /// /// signed, account, method, era, nonce, tip, genesis, startEra - public static UnCheckedExtrinsic SubmitExtrinsic(bool signed, Account account, Method method, Era era, + public static async Task SubmitExtrinsicAsync(bool signed, Account account, Method method, Era era, uint nonce, ChargeType charge, Hash genesis, Hash startEra, RuntimeVersion runtime) { var uncheckedExtrinsic = @@ -87,13 +88,10 @@ public static UnCheckedExtrinsic SubmitExtrinsic(bool signed, Account account, M return uncheckedExtrinsic; } - var payload = uncheckedExtrinsic.GetPayload(runtime).Encode(); + Payload payload = uncheckedExtrinsic.GetPayload(runtime); - /// Payloads longer than 256 bytes are going to be `blake2_256`-hashed. - if (payload.Length > 256) payload = HashExtension.Blake2(payload, 256); - - /// sign payload with the - byte[] signature = account.Sign(payload); + // sign payload with the, Payloads longer than 256 bytes are going to be `blake2_256`-hashed. + byte[] signature = await account.SignPayloadAsync(payload); uncheckedExtrinsic.AddPayloadSignature(signature); diff --git a/Substrate.NetApi/Substrate.NetApi.csproj b/Substrate.NetApi/Substrate.NetApi.csproj index 67e2ed8..95739a0 100644 --- a/Substrate.NetApi/Substrate.NetApi.csproj +++ b/Substrate.NetApi/Substrate.NetApi.csproj @@ -3,7 +3,7 @@ Substrate.NET.API netstandard2.0;netstandard2.1;net6.0 - 0.9.15 + 0.9.16 Substrate Gaming Substrate Gaming true diff --git a/Substrate.NetApi/SubstrateClient.cs b/Substrate.NetApi/SubstrateClient.cs index e0e98f7..b55dacb 100644 --- a/Substrate.NetApi/SubstrateClient.cs +++ b/Substrate.NetApi/SubstrateClient.cs @@ -87,10 +87,16 @@ public SubstrateClient(Uri uri, ChargeType chargeType, bool bypassRemoteCertific /// Information describing the meta. public MetaData MetaData { get; private set; } - /// Gets or sets information describing the runtime version. - /// Information describing the runtime version. + /// + /// Network runtime version + /// public RuntimeVersion RuntimeVersion { get; private set; } + /// + /// Network propoerties + /// + public Properties Properties { get; private set; } + /// Gets or sets the genesis hash. /// The genesis hash. public Hash GenesisHash { get; private set; } @@ -141,31 +147,43 @@ public bool SetJsonRPCTraceLevel(SourceLevels sourceLevels) return true; } - /// Connects an asynchronous. - /// 19.09.2020. - /// An asynchronous result. + /// + /// Asynchronously connects to the node. + /// + /// public async Task ConnectAsync() { await ConnectAsync(true, CancellationToken.None); } - /// Connects an asynchronous. - /// 19.09.2020. - /// An asynchronous result. + /// + /// Asynchronously connects to the node. + /// + /// Cancellation token. + /// public async Task ConnectAsync(CancellationToken token) { await ConnectAsync(true, token); } + /// + /// Asynchronously connects to the node. + /// + /// Parse metadata on connect. + /// Cancellation token. + /// public async Task ConnectAsync(bool useMetaData, CancellationToken token) { await ConnectAsync(useMetaData, true, token); } - /// Connects an asynchronous. - /// 19.09.2020. - /// A token that allows processing to be cancelled. - /// An asynchronous result. + /// + /// Asynchronously connects to the node. + /// + /// Parse metadata on connect. + /// Get blocknumber and runtime information from standard susbtrate node. + /// Cancellation token. + /// public async Task ConnectAsync(bool useMetaData, bool standardSubstrate, CancellationToken token) { if (_socket != null && _socket.State == WebSocketState.Open) @@ -186,7 +204,6 @@ public async Task ConnectAsync(bool useMetaData, bool standardSubstrate, Cancell _socket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => true; #endif } - } _connectTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); @@ -235,6 +252,9 @@ public async Task ConnectAsync(bool useMetaData, bool standardSubstrate, Cancell RuntimeVersion = await State.GetRuntimeVersionAsync(token); Logger.Debug("Runtime version parsed."); + + Properties = await System.PropertiesAsync(token); + Logger.Debug("Properties parsed."); } //_jsonRpc.TraceSource.Switch.Level = SourceLevels.All; @@ -359,7 +379,9 @@ public async Task GetExtrinsicParametersAsync(Method method, era = Era.Create(lifeTime, finalizedHeader.Number.Value); } - return RequestGenerator.SubmitExtrinsic(signed, account, method, era, nonce, charge, GenesisHash, startEra, RuntimeVersion); + var uncheckedExtrinsic = await RequestGenerator.SubmitExtrinsicAsync(signed, account, method, era, nonce, charge, GenesisHash, startEra, RuntimeVersion); ; + + return uncheckedExtrinsic; } ///