diff --git a/Substrate.NetApi.Test/Extensions/BytesExtensionTest.cs b/Substrate.NetApi.Test/Extensions/BytesExtensionTest.cs
new file mode 100644
index 0000000..7485c14
--- /dev/null
+++ b/Substrate.NetApi.Test/Extensions/BytesExtensionTest.cs
@@ -0,0 +1,62 @@
+using NUnit.Framework;
+using Substrate.NetApi.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Substrate.NetApi.Test.Extensions
+{
+ public class BytesExtensionTest
+ {
+ [Test]
+ public void BytesFixLength_WhenNoChangeNeeded_ReturnsOriginalArray()
+ {
+ byte[] originalBytes = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+ int bitLength = originalBytes.Length * 8;
+
+ byte[] result = originalBytes.BytesFixLength(bitLength);
+
+ Assert.That(originalBytes, Is.EqualTo(result));
+ }
+
+ [Test]
+ public void BytesFixLength_WhenShorter_WithAtStart_True_PrependsZeros()
+ {
+ byte[] originalBytes = new byte[] { 0x01, 0x02 };
+ int bitLength = 32; // 4 bytes
+ bool atStart = true;
+
+ byte[] result = originalBytes.BytesFixLength(bitLength, atStart);
+
+ byte[] expected = new byte[] { 0x01, 0x02, 0x00, 0x00 };
+ Assert.That(expected, Is.EqualTo(result));
+ }
+
+ [Test]
+ public void BytesFixLength_WhenShorter_WithAtStart_False_AppendsZeros()
+ {
+ byte[] originalBytes = new byte[] { 0x01, 0x02 };
+ int bitLength = 32; // 4 bytes
+ bool atStart = false;
+
+ byte[] result = originalBytes.BytesFixLength(bitLength, atStart);
+
+ byte[] expected = new byte[] { 0x00, 0x00, 0x01, 0x02 };
+ Assert.That(expected, Is.EqualTo(result));
+ }
+
+ [Test]
+ public void BytesFixLength_WhenLonger_TruncatesArray()
+ {
+ byte[] originalBytes = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 };
+ int bitLength = 24; // 3 bytes
+
+ byte[] result = originalBytes.BytesFixLength(bitLength);
+
+ byte[] expected = new byte[] { 0x01, 0x02, 0x03 };
+ Assert.That(expected, Is.EqualTo(result));
+ }
+ }
+}
diff --git a/Substrate.NetApi.Test/Extensions/HexadecimalValidatorTests.cs b/Substrate.NetApi.Test/Extensions/HexadecimalValidatorTests.cs
new file mode 100644
index 0000000..bbd6d01
--- /dev/null
+++ b/Substrate.NetApi.Test/Extensions/HexadecimalValidatorTests.cs
@@ -0,0 +1,31 @@
+using NUnit.Framework;
+using Substrate.NetApi.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Substrate.NetApi.Test.Extensions
+{
+ public class HexadecimalValidatorTests
+ {
+ [Test]
+ [TestCase("", false)]
+ [TestCase("1234567890ABCDEF", true)]
+ [TestCase("0x1234567890ABCDEF", true)]
+ [TestCase("0x1234567890ABCDEF", true)]
+ [TestCase("1A2F3D", true)]
+ [TestCase("abcdef", true)]
+ [TestCase("0Xabcdef", true)]
+ [TestCase("12G", false)]
+ [TestCase("1X2", false)]
+ [TestCase(" ", false)]
+ [TestCase(null, false)]
+ public void IsHexadecimal_ValidatingHexadecimalStrings(string input, bool expected)
+ {
+ bool result = input.IsHex();
+ Assert.That(expected, Is.EqualTo(result));
+ }
+ }
+}
diff --git a/Substrate.NetApi/Extensions/BytesExtension.cs b/Substrate.NetApi/Extensions/BytesExtension.cs
new file mode 100644
index 0000000..f4e37a8
--- /dev/null
+++ b/Substrate.NetApi/Extensions/BytesExtension.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Substrate.NetApi.Extensions
+{
+ public static class BytesExtension
+ {
+ private static readonly RandomNumberGenerator RandomGenerator = RandomNumberGenerator.Create();
+
+ ///
+ /// Load a byte array with random bytes
+ ///
+ ///
+ ///
+ public static byte[] Populate(this byte[] data)
+ {
+ RandomGenerator.GetBytes(data);
+ return data;
+ }
+
+ public static byte[] BytesFixLength(this byte[] value, int bitLength = -1, bool atStart = false)
+ {
+ int byteLength = (bitLength == -1) ? value.Length : (int)Math.Ceiling(bitLength / 8.0);
+
+ if (value.Length == byteLength)
+ {
+ return value;
+ }
+
+ if (value.Length > byteLength)
+ {
+ return value.Take(byteLength).ToArray();
+ }
+
+ byte[] result = new byte[byteLength];
+ int copyIndex = atStart ? 0 : byteLength - value.Length;
+
+ Array.Copy(value, 0, result, copyIndex, value.Length);
+
+ return result;
+ }
+ }
+}
diff --git a/Substrate.NetApi/Extensions/StringExtension.cs b/Substrate.NetApi/Extensions/StringExtension.cs
new file mode 100644
index 0000000..4c8148d
--- /dev/null
+++ b/Substrate.NetApi/Extensions/StringExtension.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Substrate.NetApi.Extensions
+{
+ public static class StringExtension
+ {
+ public static byte[] ToBytes(this string value)
+ {
+ return Encoding.UTF8.GetBytes(value);
+ }
+
+ public static bool IsHex(this string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ return false;
+
+ if (value.ToLower().StartsWith("0x"))
+ value = value.Remove(0, 2);
+
+ return value.Length % 2 == 0 && IsHexCharacters(value);
+ }
+
+ private static bool IsHexCharacters(string input)
+ {
+ foreach (char c in input)
+ {
+ if (!IsHexDigit(c))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static bool IsHexDigit(char c)
+ {
+ return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
+ }
+ }
+}
diff --git a/Substrate.NetApi/Mnemonic.cs b/Substrate.NetApi/Mnemonic.cs
index 068d825..20553a7 100644
--- a/Substrate.NetApi/Mnemonic.cs
+++ b/Substrate.NetApi/Mnemonic.cs
@@ -7,6 +7,7 @@
using Substrate.NetApi.BIP39;
using Substrate.NetApi.Model.Types;
using Schnorrkel.Keys;
+using Substrate.NetApi.Extensions;
namespace Substrate.NetApi
{
@@ -304,405 +305,62 @@ private static Wordlist GetWordlist(BIP39Wordlist language)
throw new Exception($"Unknown {language} in BIP39 implementation!");
}
}
- }
- //namespace System.Security.Cryptography
- //{
- // ///
- // /// This is a copy of Rfc28989DeiveBytes from corefx, since .NET Standard 2.0 leaves out the version
- // /// where you can specify the HMAC to use I had to copy this version in and use it instead.
- // ///
- // public class Rfc2898DeriveBytesExtended : DeriveBytes
- // {
- // private const int MinimumSaltSize = 8;
-
- // private readonly byte[] _password;
- // private byte[] _salt;
- // private uint _iterations;
- // private HMAC _hmac;
- // private int _blockSize;
-
- // private byte[] _buffer;
- // private uint _block;
- // private int _startIndex;
- // private int _endIndex;
-
- // public HashAlgorithmName HashAlgorithm { get; }
-
- // public Rfc2898DeriveBytesExtended(byte[] password, byte[] salt, int iterations)
- // : this(password, salt, iterations, HashAlgorithmName.SHA1)
- // {
- // }
-
- // public Rfc2898DeriveBytesExtended(byte[] password, byte[] salt, int iterations, HashAlgorithmName hashAlgorithm)
- // {
- // if (salt == null)
- // throw new ArgumentNullException(nameof(salt));
- // if (salt.Length < MinimumSaltSize)
- // throw new ArgumentException("Salt is not at least eight bytes.", nameof(salt));
- // if (iterations <= 0)
- // throw new ArgumentOutOfRangeException(nameof(iterations), "Positive number required.");
- // if (password == null)
- // throw new NullReferenceException(); // This "should" be ArgumentNullException but for compat, we throw NullReferenceException.
-
- // _salt = salt.CloneByteArray();
- // _iterations = (uint)iterations;
- // _password = password.CloneByteArray();
- // HashAlgorithm = hashAlgorithm;
- // _hmac = OpenHmac();
- // // _blockSize is in bytes, HashSize is in bits.
- // _blockSize = _hmac.HashSize >> 3;
-
- // Initialize();
- // }
-
- // public Rfc2898DeriveBytesExtended(string password, byte[] salt)
- // : this(password, salt, 1000)
- // {
- // }
-
- // public Rfc2898DeriveBytesExtended(string password, byte[] salt, int iterations)
- // : this(password, salt, iterations, HashAlgorithmName.SHA1)
- // {
- // }
-
- // public Rfc2898DeriveBytesExtended(string password, byte[] salt, int iterations, HashAlgorithmName hashAlgorithm)
- // : this(Encoding.UTF8.GetBytes(password), salt, iterations, hashAlgorithm)
- // {
- // }
-
- // public Rfc2898DeriveBytesExtended(string password, int saltSize)
- // : this(password, saltSize, 1000)
- // {
- // }
-
- // public Rfc2898DeriveBytesExtended(string password, int saltSize, int iterations)
- // : this(password, saltSize, iterations, HashAlgorithmName.SHA1)
- // {
- // }
-
- // public Rfc2898DeriveBytesExtended(string password, int saltSize, int iterations, HashAlgorithmName hashAlgorithm)
- // {
- // if (saltSize < 0)
- // throw new ArgumentOutOfRangeException(nameof(saltSize), "Non-negative number required.");
- // if (saltSize < MinimumSaltSize)
- // throw new ArgumentException("Salt is not at least eight bytes.", nameof(saltSize));
- // if (iterations <= 0)
- // throw new ArgumentOutOfRangeException(nameof(iterations), "Positive number required.");
-
- // _salt = Helpers.GenerateRandom(saltSize);
- // _iterations = (uint)iterations;
- // _password = Encoding.UTF8.GetBytes(password);
- // HashAlgorithm = hashAlgorithm;
- // _hmac = OpenHmac();
- // // _blockSize is in bytes, HashSize is in bits.
- // _blockSize = _hmac.HashSize >> 3;
-
- // Initialize();
- // }
-
- // public int IterationCount
- // {
- // get
- // {
- // return (int)_iterations;
- // }
-
- // set
- // {
- // if (value <= 0)
- // throw new ArgumentOutOfRangeException(nameof(value), "Positive number required.");
- // _iterations = (uint)value;
- // Initialize();
- // }
- // }
-
- // public byte[] Salt
- // {
- // get
- // {
- // return _salt.CloneByteArray();
- // }
-
- // set
- // {
- // if (value == null)
- // throw new ArgumentNullException(nameof(value));
- // if (value.Length < MinimumSaltSize)
- // throw new ArgumentException("Salt is not at least eight bytes.");
- // _salt = value.CloneByteArray();
- // Initialize();
- // }
- // }
-
- // protected override void Dispose(bool disposing)
- // {
- // if (disposing)
- // {
- // if (_hmac != null)
- // {
- // _hmac.Dispose();
- // _hmac = null;
- // }
-
- // if (_buffer != null)
- // Array.Clear(_buffer, 0, _buffer.Length);
- // if (_password != null)
- // Array.Clear(_password, 0, _password.Length);
- // if (_salt != null)
- // Array.Clear(_salt, 0, _salt.Length);
- // }
- // base.Dispose(disposing);
- // }
-
- // public override byte[] GetBytes(int cb)
- // {
- // Debug.Assert(_blockSize > 0);
-
- // if (cb <= 0)
- // throw new ArgumentOutOfRangeException(nameof(cb), "Positive number required.");
- // byte[] password = new byte[cb];
-
- // int offset = 0;
- // int size = _endIndex - _startIndex;
- // if (size > 0)
- // {
- // if (cb >= size)
- // {
- // Buffer.BlockCopy(_buffer, _startIndex, password, 0, size);
- // _startIndex = _endIndex = 0;
- // offset += size;
- // }
- // else
- // {
- // Buffer.BlockCopy(_buffer, _startIndex, password, 0, cb);
- // _startIndex += cb;
- // return password;
- // }
- // }
-
- // Debug.Assert(_startIndex == 0 && _endIndex == 0, "Invalid start or end index in the internal buffer.");
-
- // while (offset < cb)
- // {
- // byte[] T_block = Func();
- // int remainder = cb - offset;
- // if (remainder > _blockSize)
- // {
- // Buffer.BlockCopy(T_block, 0, password, offset, _blockSize);
- // offset += _blockSize;
- // }
- // else
- // {
- // Buffer.BlockCopy(T_block, 0, password, offset, remainder);
- // offset += remainder;
- // Buffer.BlockCopy(T_block, remainder, _buffer, _startIndex, _blockSize - remainder);
- // _endIndex += (_blockSize - remainder);
- // return password;
- // }
- // }
- // return password;
- // }
-
- // public byte[] CryptDeriveKey(string algname, string alghashname, int keySize, byte[] rgbIV)
- // {
- // // If this were to be implemented here, CAPI would need to be used (not CNG) because of
- // // unfortunate differences between the two. Using CNG would break compatibility. Since this
- // // assembly currently doesn't use CAPI it would require non-trivial additions.
- // // In addition, if implemented here, only Windows would be supported as it is intended as
- // // a thin wrapper over the corresponding native API.
- // // Note that this method is implemented in PasswordDeriveBytes (in the Csp assembly) using CAPI.
- // throw new PlatformNotSupportedException();
- // }
-
- // public override void Reset()
- // {
- // Initialize();
- // }
-
- // private HMAC OpenHmac()
- // {
- // Debug.Assert(_password != null);
-
- // HashAlgorithmName hashAlgorithm = HashAlgorithm;
-
- // if (string.IsNullOrEmpty(hashAlgorithm.Name))
- // throw new CryptographicException("The hash algorithm name cannot be null or empty.");
-
- // if (hashAlgorithm == HashAlgorithmName.SHA1)
- // return new HMACSHA1(_password);
- // if (hashAlgorithm == HashAlgorithmName.SHA256)
- // return new HMACSHA256(_password);
- // if (hashAlgorithm == HashAlgorithmName.SHA384)
- // return new HMACSHA384(_password);
- // if (hashAlgorithm == HashAlgorithmName.SHA512)
- // return new HMACSHA512(_password);
-
- // throw new CryptographicException($"{hashAlgorithm.Name} is not a known hash algorithm.");
- // }
-
- // private void Initialize()
- // {
- // if (_buffer != null)
- // Array.Clear(_buffer, 0, _buffer.Length);
- // _buffer = new byte[_blockSize];
- // _block = 1;
- // _startIndex = _endIndex = 0;
- // }
-
- // // This function is defined as follows:
- // // Func (S, i) = HMAC(S || i) ^ HMAC2(S || i) ^ ... ^ HMAC(iterations) (S || i)
- // // where i is the block number.
- // private byte[] Func()
- // {
- // byte[] temp = new byte[_salt.Length + sizeof(uint)];
- // Buffer.BlockCopy(_salt, 0, temp, 0, _salt.Length);
- // Helpers.WriteInt(_block, temp, _salt.Length);
-
- // temp = _hmac.ComputeHash(temp);
-
- // byte[] ret = temp;
- // for (int i = 2; i <= _iterations; i++)
- // {
- // temp = _hmac.ComputeHash(temp);
-
- // for (int j = 0; j < _blockSize; j++)
- // {
- // ret[j] ^= temp[j];
- // }
- // }
-
- // // increment the block count.
- // _block++;
- // return ret;
- // }
-
- // }
-
- // internal static class Helpers
- // {
- // public static byte[] CloneByteArray(this byte[] src)
- // {
- // if (src == null)
- // {
- // return null;
- // }
-
- // return (byte[])(src.Clone());
- // }
-
- // public static KeySizes[] CloneKeySizesArray(this KeySizes[] src)
- // {
- // return (KeySizes[])(src.Clone());
- // }
-
- // public static bool UsesIv(this CipherMode cipherMode)
- // {
- // return cipherMode != CipherMode.ECB;
- // }
-
- // public static byte[] GetCipherIv(this CipherMode cipherMode, byte[] iv)
- // {
- // if (cipherMode.UsesIv())
- // {
- // if (iv == null)
- // {
- // throw new CryptographicException("The cipher mode specified requires that an initialization vector (IV) be used.");
- // }
-
- // return iv;
- // }
-
- // return null;
- // }
-
- // public static bool IsLegalSize(this int size, KeySizes[] legalSizes)
- // {
- // for (int i = 0; i < legalSizes.Length; i++)
- // {
- // KeySizes currentSizes = legalSizes[i];
-
- // // If a cipher has only one valid key size, MinSize == MaxSize and SkipSize will be 0
- // if (currentSizes.SkipSize == 0)
- // {
- // if (currentSizes.MinSize == size)
- // return true;
- // }
- // else if (size >= currentSizes.MinSize && size <= currentSizes.MaxSize)
- // {
- // // If the number is in range, check to see if it's a legal increment above MinSize
- // int delta = size - currentSizes.MinSize;
-
- // // While it would be unusual to see KeySizes { 10, 20, 5 } and { 11, 14, 1 }, it could happen.
- // // So don't return false just because this one doesn't match.
- // if (delta % currentSizes.SkipSize == 0)
- // {
- // return true;
- // }
- // }
- // }
-
- // return false;
- // }
-
- // public static byte[] GenerateRandom(int count)
- // {
- // byte[] buffer = new byte[count];
- // using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
- // {
- // rng.GetBytes(buffer);
- // }
-
- // return buffer;
- // }
-
- // // encodes the integer i into a 4-byte array, in big endian.
- // public static void WriteInt(uint i, byte[] arr, int offset)
- // {
- // unchecked
- // {
- // Debug.Assert(arr != null);
- // Debug.Assert(arr.Length >= offset + sizeof(uint));
-
- // arr[offset] = (byte)(i >> 24);
- // arr[offset + 1] = (byte)(i >> 16);
- // arr[offset + 2] = (byte)(i >> 8);
- // arr[offset + 3] = (byte)i;
- // }
- // }
-
- // public static byte[] FixupKeyParity(this byte[] key)
- // {
- // byte[] oddParityKey = new byte[key.Length];
- // for (int index = 0; index < key.Length; index++)
- // {
- // // Get the bits we are interested in
- // oddParityKey[index] = (byte)(key[index] & 0xfe);
-
- // // Get the parity of the sum of the previous bits
- // byte tmp1 = (byte)((oddParityKey[index] & 0xF) ^ (oddParityKey[index] >> 4));
- // byte tmp2 = (byte)((tmp1 & 0x3) ^ (tmp1 >> 2));
- // byte sumBitsMod2 = (byte)((tmp2 & 0x1) ^ (tmp2 >> 1));
-
- // // We need to set the last bit in oddParityKey[index] to the negation
- // // of the last bit in sumBitsMod2
- // if (sumBitsMod2 == 0)
- // oddParityKey[index] |= 1;
- // }
-
- // return oddParityKey;
- // }
-
- // internal static void ConvertIntToByteArray(uint value, byte[] dest)
- // {
- // Debug.Assert(dest != null);
- // Debug.Assert(dest.Length == 4);
- // dest[0] = (byte)((value & 0xFF000000) >> 24);
- // dest[1] = (byte)((value & 0xFF0000) >> 16);
- // dest[2] = (byte)((value & 0xFF00) >> 8);
- // dest[3] = (byte)(value & 0xFF);
- // }
- // }
-
- //}
+ public enum MnemonicSize
+ {
+ Words12,
+ Words15,
+ Words18,
+ Words21,
+ Words24
+ }
+
+ ///
+ /// Generate new mnemonic based on nb words
+ ///
+ ///
+ ///
+ ///
+ public static string[] GenerateMnemonic(MnemonicSize mnemonicSize, BIP39Wordlist bIP39Wordlist = BIP39Wordlist.English)
+ {
+ switch (mnemonicSize)
+ {
+ case MnemonicSize.Words12:
+ return MnemonicFromEntropy(new byte[16].Populate(), bIP39Wordlist);
+ case MnemonicSize.Words15:
+ return MnemonicFromEntropy(new byte[20].Populate(), bIP39Wordlist);
+ case MnemonicSize.Words18:
+ return MnemonicFromEntropy(new byte[24].Populate(), bIP39Wordlist);
+ case MnemonicSize.Words21:
+ return MnemonicFromEntropy(new byte[28].Populate(), bIP39Wordlist);
+ case MnemonicSize.Words24:
+ return MnemonicFromEntropy(new byte[32].Populate(), bIP39Wordlist);
+ }
+
+ throw new InvalidOperationException("Invalid mnemonic size");
+ }
+
+ //public static byte[] MnemonicToMiniSecret(string mnemonic, string password, BIP39Wordlist bIP39Wordlist = BIP39Wordlist.English)
+ //{
+ // if (!ValidateMnemonic(mnemonic, bIP39Wordlist))
+ // {
+ // throw new InvalidOperationException("Invalid bip39 mnemonic specified");
+ // }
+
+ // return GetSecretKeyFromMnemonic(mnemonic, password, bIP39Wordlist);
+ //}
+
+ public static bool ValidateMnemonic(string mnemonic, BIP39Wordlist bIP39Wordlist = BIP39Wordlist.English)
+ {
+ try
+ {
+ _ = MnemonicToEntropy(mnemonic, bIP39Wordlist);
+ return true;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+ }
}
\ No newline at end of file