diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..538773f --- /dev/null +++ b/.gitignore @@ -0,0 +1,305 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/a +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +package-lock.json +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# Visual Studio Code +.vscode + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc +*.DS_Store + +.vs/ +slnx.sqlite + +# Generated Protobuf classes +*.g.cs +*.c.cs +grpc_csharp_plugin +*Grpc.cs +**/Generated/*.cs + +launchSettings.json + +yarn.lock + +results/coverage.json +results/coverage.opencover.xml + +aelf.js + +**/contract_csharp_plugin +**/contract_csharp_plugin.exe + +**/BenchmarkDotNet.Artifacts/* + +*.zip + +**/package-lock.json + +Contracts.manifest + +# Contract patcher dlls +chain/scripts/patcher/* + +.dotnet +.store +tools/dotnet-cake +*.dll +*.dll.patched diff --git a/BIP39Wallet.sln b/BIP39Wallet.sln new file mode 100644 index 0000000..090350d --- /dev/null +++ b/BIP39Wallet.sln @@ -0,0 +1,19 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 + +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BIP39.HDWallet", "src\BIP39.HDWallet\BIP39.HDWallet.csproj", "{D8321304-102F-496B-916B-51D9A836F7A9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BIP39.HDWallet.Core", "src\BIP39.HDWallet.Core\BIP39.HDWallet.Core.csproj", "{F9BA3545-19DA-46F7-814F-74F51D41020C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BIP39Wallet", "src\BIP39Wallet\BIP39Wallet.csproj", "{017585D2-3701-4005-826C-6B29E7B11414}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BIP39Wallet.Tests", "test\BIP39Wallet.Test\BIP39Wallet.Tests.csproj", "{2437589B-6CFD-45F7-8FA9-F4BE7463DE9F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BIP39.HDWallet.Core.Tests", "test\BIP39.HDWallet.Core.Test\BIP39.HDWallet.Core.Tests.csproj", "{6D976E4A-36B9-4123-89AE-6E6B09232DC2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BIP39.HDWallet.Tests", "test\BIP39.HDWallet.Test\BIP39.HDWallet.Tests.csproj", "{C35BE143-5B8B-4453-9762-E9D8F3D46058}" +EndProject +Global + GlobalSection(ProjectConfigurationPlatforms) = postSolution + EndGlobalSection +EndGlobal diff --git a/BIP39Wallet.sln.DotSettings b/BIP39Wallet.sln.DotSettings new file mode 100644 index 0000000..40ad8db --- /dev/null +++ b/BIP39Wallet.sln.DotSettings @@ -0,0 +1,4 @@ + + False + BIP + HD \ No newline at end of file diff --git a/src/BIP39.HDWallet.Core/Account.cs b/src/BIP39.HDWallet.Core/Account.cs new file mode 100644 index 0000000..7df0906 --- /dev/null +++ b/src/BIP39.HDWallet.Core/Account.cs @@ -0,0 +1,41 @@ +using System.Diagnostics.CodeAnalysis; +using NBitcoin; + +namespace BIP39.HDWallet.Core +{ + [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] + public class Account : IAccount where TWallet : IWallet, new() + { + public uint AccountIndex { get; set; } + private ExtKey ExternalChain { get;} + private ExtKey InternalChain { get;} + + public Account(uint accountIndex, ExtKey externalChain, ExtKey internalChain) + { + ExternalChain = externalChain; + InternalChain = internalChain; + AccountIndex = accountIndex; + } + + TWallet IAccount.GetInternalWallet(uint addressIndex) + { + return GetWallet(addressIndex, true); + } + + TWallet IAccount.GetExternalWallet(uint addressIndex) + { + return GetWallet(addressIndex, false); + } + + private TWallet GetWallet(uint addressIndex, bool isInternal) + { + var extKey = isInternal ? InternalChain.Derive(addressIndex) : ExternalChain.Derive(addressIndex); + + return new TWallet + { + PrivateKey = extKey.PrivateKey.ToBytes(), + Index = addressIndex + }; + } + } +} \ No newline at end of file diff --git a/src/BIP39.HDWallet.Core/BIP39.HDWallet.Core.csproj b/src/BIP39.HDWallet.Core/BIP39.HDWallet.Core.csproj new file mode 100644 index 0000000..7f3c74b --- /dev/null +++ b/src/BIP39.HDWallet.Core/BIP39.HDWallet.Core.csproj @@ -0,0 +1,12 @@ + + + + net7.0;netstandard2.1 + + + + + + + + diff --git a/src/BIP39.HDWallet.Core/HDWallet.cs b/src/BIP39.HDWallet.Core/HDWallet.cs new file mode 100644 index 0000000..e12fc1f --- /dev/null +++ b/src/BIP39.HDWallet.Core/HDWallet.cs @@ -0,0 +1,45 @@ +using NBitcoin; + +namespace BIP39.HDWallet.Core +{ + public class HDWallet : IHDWallet where TWallet : IWallet, new() + { + public string Seed { get; set; } + + private readonly ExtKey _masterKey; + + public HDWallet(string seed, string coinPath) + { + Seed = seed; + var masterKeyPath = new KeyPath(coinPath); + _masterKey = new ExtKey(seed).Derive(masterKeyPath); + } + + public TWallet GetMasterWallet() + { + return new TWallet + { + PrivateKey = new ExtKey(Seed).PrivateKey.ToBytes() + }; + } + + public TWallet GetAccountWallet(uint accountIndex) + { + var externalKeyPath = new KeyPath($"{accountIndex}'"); + _masterKey.Derive(externalKeyPath); + + return new TWallet + { + PrivateKey = _masterKey.PrivateKey.ToBytes(), + Index = accountIndex + }; + } + + public IAccount GetAccount(uint index) + { + var externalKeyPath = new KeyPath($"{index}'/0"); + var internalKeyPath = new KeyPath($"{index}'/1"); + return new Account(index, _masterKey.Derive(externalKeyPath), _masterKey.Derive(internalKeyPath)); + } + } +} \ No newline at end of file diff --git a/src/BIP39.HDWallet.Core/Helper.cs b/src/BIP39.HDWallet.Core/Helper.cs new file mode 100644 index 0000000..efc91b6 --- /dev/null +++ b/src/BIP39.HDWallet.Core/Helper.cs @@ -0,0 +1,46 @@ +using System; +using System.Linq; +using System.Text; + +namespace BIP39.HDWallet.Core +{ + public static class Helper + { + // From NBitcoin + public static byte[] Concat(byte[] arr, params byte[][] arrs) + { + var len = arr.Length + arrs.Sum(a => a.Length); + var ret = new byte[len]; + Buffer.BlockCopy(arr, 0, ret, 0, arr.Length); + var pos = arr.Length; + foreach (var a in arrs) + { + Buffer.BlockCopy(a, 0, ret, pos, a.Length); + pos += a.Length; + } + return ret; + } + + public static string ToHexString(this byte[] bytes) + { + var hex = new StringBuilder(bytes.Length * 2); + foreach (var b in bytes) + { + hex.AppendFormat("{0:x2}", b); + } + return hex.ToString(); + } + + + public static byte[] FromHexToByteArray(this string input) + { + var numberChars = input.Length; + var bytes = new byte[numberChars / 2]; + for (var i = 0; i < numberChars; i += 2) + { + bytes[i / 2] = Convert.ToByte(input.Substring(i, 2), 16); + } + return bytes; + } + } +} \ No newline at end of file diff --git a/src/BIP39.HDWallet.Core/IAccount.cs b/src/BIP39.HDWallet.Core/IAccount.cs new file mode 100644 index 0000000..63ef525 --- /dev/null +++ b/src/BIP39.HDWallet.Core/IAccount.cs @@ -0,0 +1,8 @@ +namespace BIP39.HDWallet.Core +{ + public interface IAccount where TWallet : IWallet, new() + { + TWallet GetInternalWallet(uint addressIndex); + TWallet GetExternalWallet(uint addressIndex); + } +} \ No newline at end of file diff --git a/src/BIP39.HDWallet.Core/IHDWallet.cs b/src/BIP39.HDWallet.Core/IHDWallet.cs new file mode 100644 index 0000000..b73187f --- /dev/null +++ b/src/BIP39.HDWallet.Core/IHDWallet.cs @@ -0,0 +1,9 @@ +namespace BIP39.HDWallet.Core +{ + public interface IHDWallet where TWallet : IWallet, new() + { + TWallet GetMasterWallet(); + TWallet GetAccountWallet(uint accountIndex); + IAccount GetAccount(uint index); + } +} \ No newline at end of file diff --git a/src/BIP39.HDWallet.Core/IWallet.cs b/src/BIP39.HDWallet.Core/IWallet.cs new file mode 100644 index 0000000..3fcdd92 --- /dev/null +++ b/src/BIP39.HDWallet.Core/IWallet.cs @@ -0,0 +1,15 @@ +using AElf.Types; + +namespace BIP39.HDWallet.Core +{ + public interface IWallet + { + Address Address { get; } + + public byte[] Sign(byte[] hash); + + public byte[] PrivateKey { get; set; } + + public uint Index { get; set; } + } +} \ No newline at end of file diff --git a/src/BIP39.HDWallet.Core/Wallet.cs b/src/BIP39.HDWallet.Core/Wallet.cs new file mode 100644 index 0000000..cf53795 --- /dev/null +++ b/src/BIP39.HDWallet.Core/Wallet.cs @@ -0,0 +1,61 @@ +using System; +using AElf.Types; +using NBitcoin; +using NBitcoin.DataEncoders; + +namespace BIP39.HDWallet.Core +{ + public abstract class Wallets : IWallet + { + public Key Key { get; set; } + public Address Address => GenerateAddress(); + + //TODO: write a unit test for this method + public byte[] Sign(byte[] hash) + { + var hash32 = new uint256(hash); + var privateKey = PrivateKey; + Array.Resize(ref privateKey, 32); + var key = new Key(privateKey, -1, false); + var signature = key.SignCompact(hash32, false); + + var formattedSignature = new byte[65]; + Array.Copy(signature[1..], 0, formattedSignature, 0, 64); + + var recoverId = (byte)(signature[0] - 27); + formattedSignature[64] = recoverId; //last byte holds the recoverId + + return formattedSignature; + } + + private byte[] _privateKey; + + public byte[] PrivateKey + { + get => _privateKey; + set + { + var hexEncodeData = Encoders.Hex.EncodeData(value); + Key = PrivateKeyParse(hexEncodeData); + _privateKey = value; + } + } + + private static Key PrivateKeyParse(string privateKey) + { + var privKeyPrefix = new byte[] {128}; + var prefixedPrivKey = Helper.Concat(privKeyPrefix, Encoders.Hex.DecodeData(privateKey)); + + var privKeySuffix = new byte[] {1}; + var suffixedPrivKey = Helper.Concat(prefixedPrivKey, privKeySuffix); + + var base58Check = new Base58CheckEncoder(); + var privKeyEncoded = base58Check.EncodeData(suffixedPrivKey); + return Key.Parse(privKeyEncoded, Network.Main); + } + + public uint Index { get; set; } + + protected abstract Address GenerateAddress(); + } +} \ No newline at end of file diff --git a/src/BIP39.HDWallet/BIP39.HDWallet.csproj b/src/BIP39.HDWallet/BIP39.HDWallet.csproj new file mode 100644 index 0000000..2eb3059 --- /dev/null +++ b/src/BIP39.HDWallet/BIP39.HDWallet.csproj @@ -0,0 +1,13 @@ + + + + net7.0;netstandard2.1 + + + + + + + + + \ No newline at end of file diff --git a/src/BIP39.HDWallet/BIP39HDWallet.cs b/src/BIP39.HDWallet/BIP39HDWallet.cs new file mode 100644 index 0000000..34ac647 --- /dev/null +++ b/src/BIP39.HDWallet/BIP39HDWallet.cs @@ -0,0 +1,11 @@ +namespace BIP39.HDWallet +{ + // ReSharper disable once UnusedType.Global + public class BIP39HDWallet : Core.HDWallet + { + public BIP39HDWallet(string seed) : base(seed, BIP39HDWalletConstants.BIP39Path) + { + + } + } +} \ No newline at end of file diff --git a/src/BIP39.HDWallet/BIP39HDWalletConstants.cs b/src/BIP39.HDWallet/BIP39HDWalletConstants.cs new file mode 100644 index 0000000..7600db4 --- /dev/null +++ b/src/BIP39.HDWallet/BIP39HDWalletConstants.cs @@ -0,0 +1,14 @@ +namespace BIP39.HDWallet +{ + public class BIP39HDWalletConstants + { + public const uint HardenedOffset = 0x80000000; + /// + /// m / purpose' / coin_type' / account' / change / address_index + /// purpose: always 44 + /// coin_type: AELF is 1616 (https://github.com/satoshilabs/slips/blob/master/slip-0044.md) + /// + /// + public const string BIP39Path = "'m/44\'/1616\'/0\'/0/0'"; + } +} \ No newline at end of file diff --git a/src/BIP39.HDWallet/BIP39Wallet.cs b/src/BIP39.HDWallet/BIP39Wallet.cs new file mode 100644 index 0000000..c071c3a --- /dev/null +++ b/src/BIP39.HDWallet/BIP39Wallet.cs @@ -0,0 +1,13 @@ +using BIP39.HDWallet.Core; +using AElf.Types; + +namespace BIP39.HDWallet +{ + public class XBip39Wallet : Wallets + { + protected override Address GenerateAddress() + { + return Address.FromPublicKey(Key.PubKey.ToBytes()); + } + } +} \ No newline at end of file diff --git a/src/BIP39Wallet/BIP39Wallet.cs b/src/BIP39Wallet/BIP39Wallet.cs new file mode 100644 index 0000000..b05d6fa --- /dev/null +++ b/src/BIP39Wallet/BIP39Wallet.cs @@ -0,0 +1,216 @@ +#pragma warning disable CS0618 // Type or member is obsolete +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Security.Cryptography; +using BIP39Wallet.Types; +using BIP39Wallet.Extensions; +using System.Text.RegularExpressions; +using AElf; +using AElf.Types; +using BIP39.HDWallet; +using BIP39.HDWallet.Core; +using NBitcoin; +using Mnemonic = BIP39Wallet.Types.Mnemonic; +#pragma warning disable CS0649 +#pragma warning disable CS8618 + +namespace BIP39Wallet +{ + [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] + public class Wallet + { + private readonly IWalletWordlistProvider _wordlistProvider; + + public class BlockchainWallet + { + public string Address { get; private set; } + public string PrivateKey { get; private set; } + public string Mnemonic { get; private set; } + public string PublicKey { get; private set; } + + public BlockchainWallet(string address, string privateKey, string mnemonic, string publicKey) + { + Address = address; + PrivateKey = privateKey; + Mnemonic = mnemonic; + PublicKey = publicKey; + } + } + public Mnemonic GenerateMnemonic(int strength, Language language) + { + if (strength % 32 != 0) + { + throw new NotSupportedException(WalletConstants.InvalidEntropy); + } + + var rngCryptoServiceProvider = new RNGCryptoServiceProvider(); + var buffer = new byte[strength / 8]; + rngCryptoServiceProvider.GetBytes(buffer); + var entropy = new Entropy(BitConverter.ToString(buffer).Replace("-", ""), language); + return ConvertEntropyToMnemonic(entropy); + } + public Mnemonic ConvertEntropyToMnemonic(Entropy entropy) + { + var wordlist = EnglishWords.Words; + + var entropyBytes = Enumerable.Range(0, entropy.Hex.Length / 2) + .Select(x => Convert.ToByte(entropy.Hex.Substring(x * 2, 2), 16)) + .ToArray(); + var entropyBits = entropyBytes.ToBinary(); + var checksumBits = entropyBytes.GetChecksumBits(); + var bits = $"{entropyBits}{checksumBits}"; + var chunks = Regex.Matches(bits, "(.{1,11})") + .Select(m => m.Groups[0].Value) + .ToArray(); + + var words = chunks.Select(binary => + { + var index = Convert.ToInt32(binary, 2); + return wordlist[index]; + }); + + var joinedText = string.Join(entropy.Language == Language.Japanese ? "\u3000" : " ", words); + + return new Mnemonic(joinedText, entropy.Language); + } + public Entropy ConvertMnemonicToEntropy(Mnemonic mnemonic) + { + var wordlist = _wordlistProvider.LoadWordlist(mnemonic.Language); + var words = mnemonic.Value.Normalize(NormalizationForm.FormKD).Split(new[] {' '}, + StringSplitOptions.RemoveEmptyEntries); + + if (words.Length % 3 != 0) + { + throw new FormatException(WalletConstants.InvalidMnemonic); + } + + var bits = string.Join("", words.Select(word => + { + var index = Array.IndexOf(wordlist, word); + if (index == -1) + { + throw new FormatException(WalletConstants.InvalidMnemonic); + } + + return Convert.ToString(index, 2).LeftPad("0", 11); + })); + + var dividerIndex = (int) Math.Floor((double) bits.Length / 33) * 32; + var entropyBits = bits.Substring(0, dividerIndex); + var checksumBits = bits.Substring(dividerIndex); + + var entropyBytesMatch = Regex.Matches(entropyBits, "(.{1,8})") + .Select(m => m.Groups[0].Value) + .ToArray(); + + var entropyBytes = entropyBytesMatch + .Select(bytes => Convert.ToByte(bytes, 2)).ToArray(); + + var newChecksum = entropyBytes.GetChecksumBits(); + + if (newChecksum != checksumBits) + throw new Exception(WalletConstants.InvalidChecksum); + + return new Entropy(BitConverter + .ToString(entropyBytes) + .Replace("-", "") + .ToLower(), mnemonic.Language); + } + + public string ConvertMnemonicToSeedHex(Mnemonic mnemonic, string password) + { + var mnemonicBytes = Encoding.UTF8.GetBytes(mnemonic.Value.Normalize(NormalizationForm.FormKD)); + var saltSuffix = string.Empty; + if (!string.IsNullOrEmpty(password)) + { + saltSuffix = password; + } + var salt = $"mnemonic{saltSuffix}"; + var saltBytes = Encoding.UTF8.GetBytes(salt); + + var rfc2898DerivedBytes = new Rfc2898DeriveBytes(mnemonicBytes, saltBytes, 2048, HashAlgorithmName.SHA512); + var key = rfc2898DerivedBytes.GetBytes(64); + var hex = BitConverter + .ToString(key) + .Replace("-", "") + .ToLower(); + + return hex; + } + + public BlockchainWallet CreateWallet(int strength, Language language, string password) + { + var mnemonic = GenerateMnemonic(strength, language); + var seedHex = ConvertMnemonicToSeedHex(mnemonic, password); + var masterWallet = new HDWallet(seedHex, "m/44'/1616'"); + var account = masterWallet.GetAccount(0); + var wallet = account.GetExternalWallet(0); + var key = new Key(wallet.PrivateKey, -1, false); + var privateKey = wallet.PrivateKey.ToHex(); + var publicKey = key.PubKey; + var address = Address.FromPublicKey(publicKey.ToBytes()).ToString().Trim('\"'); + return new BlockchainWallet(address, privateKey, mnemonic.ToString(), publicKey.ToHex()); + } + + + public BlockchainWallet GetWalletByMnemonic(string mnemonic, string password = "") + { + var mnemonicValue = new Mnemonic + { + Value = mnemonic, + Language = Language.English + }; + var seedHex = ConvertMnemonicToSeedHex(mnemonicValue, password); + var masterWallet = new HDWallet(seedHex, "m/44'/1616'"); + var account = masterWallet.GetAccount(0); + var wallet = account.GetExternalWallet(0); + var key = new Key(wallet.PrivateKey, -1, false); + var privateKey = wallet.PrivateKey.ToHex(); + var publicKey = key.PubKey; + var address = Address.FromPublicKey(publicKey.ToBytes()).ToString().Trim('\"'); + + return new BlockchainWallet(address, privateKey, mnemonic, publicKey.ToHex()); + } + + // Convert hex string to byte array + static byte[] StringToByteArray(string hexString) + { + int length = hexString.Length; + byte[] byteArray = new byte[length / 2]; + + for (int i = 0; i < length; i += 2) + { + byteArray[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16); + } + + return byteArray; + } + public BlockchainWallet GetWalletByPrivateKey(string privateKey) + { + var keybyte = StringToByteArray(privateKey); + Array.Resize(ref keybyte, 32); + var key = new Key(keybyte, -1, false); + var publicKey = key.PubKey; + var address = Address.FromPublicKey(publicKey.ToBytes()).ToString().Trim('\"'); + return new BlockchainWallet(address, privateKey, null!, publicKey.ToHex()); + } + + public byte[] Sign(byte[] privateKey, byte[] hash) + { + var hash32 = new uint256(hash); + Array.Resize(ref privateKey, 32); + var key = new Key(privateKey, -1, false); + var signature = key.SignCompact(hash32, false); + + var formattedSignature = new byte[65]; + Array.Copy(signature[1..], 0, formattedSignature, 0, 64); + + var recoverId = (byte)(signature[0] - 27); + formattedSignature[64] = recoverId; //last byte holds the recoverId + + return formattedSignature; + } + +}} \ No newline at end of file diff --git a/src/BIP39Wallet/BIP39Wallet.csproj b/src/BIP39Wallet/BIP39Wallet.csproj new file mode 100644 index 0000000..8677deb --- /dev/null +++ b/src/BIP39Wallet/BIP39Wallet.csproj @@ -0,0 +1,14 @@ + + + net7.0;netstandard2.1 + Library + latest + enable + + + + + + + + \ No newline at end of file diff --git a/src/BIP39Wallet/BIP39WalletConstants.cs b/src/BIP39Wallet/BIP39WalletConstants.cs new file mode 100644 index 0000000..5647478 --- /dev/null +++ b/src/BIP39Wallet/BIP39WalletConstants.cs @@ -0,0 +1,9 @@ +namespace BIP39Wallet +{ + public class WalletConstants + { + public const string InvalidEntropy = "Invalid Entropy."; + public const string InvalidMnemonic = "Invalid Mnemonic."; + public const string InvalidChecksum = "Invalid Checksum."; + } +} \ No newline at end of file diff --git a/src/BIP39Wallet/BIP39WalletWordlistProvider.cs b/src/BIP39Wallet/BIP39WalletWordlistProvider.cs new file mode 100644 index 0000000..6e9911a --- /dev/null +++ b/src/BIP39Wallet/BIP39WalletWordlistProvider.cs @@ -0,0 +1,7 @@ +namespace BIP39Wallet +{ + public interface IWalletWordlistProvider + { + string[] LoadWordlist(Language language); + } +} \ No newline at end of file diff --git a/src/BIP39Wallet/Extensions/ByteArrayExtensions.cs b/src/BIP39Wallet/Extensions/ByteArrayExtensions.cs new file mode 100644 index 0000000..66a7cb3 --- /dev/null +++ b/src/BIP39Wallet/Extensions/ByteArrayExtensions.cs @@ -0,0 +1,19 @@ +using System; +using System.Linq; +using System.Security.Cryptography; + +namespace BIP39Wallet.Extensions +{ + public static class ByteArrayExtensions + { + internal static string ToBinary(this byte[] bytes) + { + return string.Join("", bytes.Select(h => (Convert.ToString(h, 2).LeftPad("0", 8)))); + } + + internal static string GetChecksumBits(this byte[] checksum) + { + return new SHA256CryptoServiceProvider().ComputeHash(checksum).ToBinary()[..(checksum.Length * 8 / 32)]; + } + } +} \ No newline at end of file diff --git a/src/BIP39Wallet/Extensions/StringExtensions.cs b/src/BIP39Wallet/Extensions/StringExtensions.cs new file mode 100644 index 0000000..093f851 --- /dev/null +++ b/src/BIP39Wallet/Extensions/StringExtensions.cs @@ -0,0 +1,15 @@ +namespace BIP39Wallet.Extensions +{ + public static class StringExtensions + { + public static string LeftPad(this string str, string leftPadString, int length) + { + while (str.Length < length) + { + str = $"{leftPadString}{str}"; + } + + return str; + } + } +} \ No newline at end of file diff --git a/src/BIP39Wallet/IBIP39WalletService.cs b/src/BIP39Wallet/IBIP39WalletService.cs new file mode 100644 index 0000000..5de9046 --- /dev/null +++ b/src/BIP39Wallet/IBIP39WalletService.cs @@ -0,0 +1,12 @@ +using BIP39Wallet.Types; + +namespace BIP39Wallet; + +public interface IBip39Service +{ + Wallet.BlockchainWallet CreateWallet(int strength, Language language, string password); + Mnemonic ConvertEntropyToMnemonic(Entropy entropy); + Entropy ConvertMnemonicToEntropy(Mnemonic mnemonic); + string ConvertMnemonicToSeedHex(Mnemonic mnemonic, string? password = null); + bool ValidateMnemonic(Mnemonic mnemonic); +} \ No newline at end of file diff --git a/src/BIP39Wallet/Language.cs b/src/BIP39Wallet/Language.cs new file mode 100644 index 0000000..9dadbda --- /dev/null +++ b/src/BIP39Wallet/Language.cs @@ -0,0 +1,2068 @@ +namespace BIP39Wallet +{ + public enum Language + { + English, + Japanese, + Korean, + Spanish, + ChineseSimplified, + ChineseTraditional, + French, + Italian, + Czech, + Portuguese + }; + public static class EnglishWords{ + public static string[] Words = { + "abandon", + "ability", + "able", + "about", + "above", + "absent", + "absorb", + "abstract", + "absurd", + "abuse", + "access", + "accident", + "account", + "accuse", + "achieve", + "acid", + "acoustic", + "acquire", + "across", + "act", + "action", + "actor", + "actress", + "actual", + "adapt", + "add", + "addict", + "address", + "adjust", + "admit", + "adult", + "advance", + "advice", + "aerobic", + "affair", + "afford", + "afraid", + "again", + "age", + "agent", + "agree", + "ahead", + "aim", + "air", + "airport", + "aisle", + "alarm", + "album", + "alcohol", + "alert", + "alien", + "all", + "alley", + "allow", + "almost", + "alone", + "alpha", + "already", + "also", + "alter", + "always", + "amateur", + "amazing", + "among", + "amount", + "amused", + "analyst", + "anchor", + "ancient", + "anger", + "angle", + "angry", + "animal", + "ankle", + "announce", + "annual", + "another", + "answer", + "antenna", + "antique", + "anxiety", + "any", + "apart", + "apology", + "appear", + "apple", + "approve", + "april", + "arch", + "arctic", + "area", + "arena", + "argue", + "arm", + "armed", + "armor", + "army", + "around", + "arrange", + "arrest", + "arrive", + "arrow", + "art", + "artefact", + "artist", + "artwork", + "ask", + "aspect", + "assault", + "asset", + "assist", + "assume", + "asthma", + "athlete", + "atom", + "attack", + "attend", + "attitude", + "attract", + "auction", + "audit", + "august", + "aunt", + "author", + "auto", + "autumn", + "average", + "avocado", + "avoid", + "awake", + "aware", + "away", + "awesome", + "awful", + "awkward", + "axis", + "baby", + "bachelor", + "bacon", + "badge", + "bag", + "balance", + "balcony", + "ball", + "bamboo", + "banana", + "banner", + "bar", + "barely", + "bargain", + "barrel", + "base", + "basic", + "basket", + "battle", + "beach", + "bean", + "beauty", + "because", + "become", + "beef", + "before", + "begin", + "behave", + "behind", + "believe", + "below", + "belt", + "bench", + "benefit", + "best", + "betray", + "better", + "between", + "beyond", + "bicycle", + "bid", + "bike", + "bind", + "biology", + "bird", + "birth", + "bitter", + "black", + "blade", + "blame", + "blanket", + "blast", + "bleak", + "bless", + "blind", + "blood", + "blossom", + "blouse", + "blue", + "blur", + "blush", + "board", + "boat", + "body", + "boil", + "bomb", + "bone", + "bonus", + "book", + "boost", + "border", + "boring", + "borrow", + "boss", + "bottom", + "bounce", + "box", + "boy", + "bracket", + "brain", + "brand", + "brass", + "brave", + "bread", + "breeze", + "brick", + "bridge", + "brief", + "bright", + "bring", + "brisk", + "broccoli", + "broken", + "bronze", + "broom", + "brother", + "brown", + "brush", + "bubble", + "buddy", + "budget", + "buffalo", + "build", + "bulb", + "bulk", + "bullet", + "bundle", + "bunker", + "burden", + "burger", + "burst", + "bus", + "business", + "busy", + "butter", + "buyer", + "buzz", + "cabbage", + "cabin", + "cable", + "cactus", + "cage", + "cake", + "call", + "calm", + "camera", + "camp", + "can", + "canal", + "cancel", + "candy", + "cannon", + "canoe", + "canvas", + "canyon", + "capable", + "capital", + "captain", + "car", + "carbon", + "card", + "cargo", + "carpet", + "carry", + "cart", + "case", + "cash", + "casino", + "castle", + "casual", + "cat", + "catalog", + "catch", + "category", + "cattle", + "caught", + "cause", + "caution", + "cave", + "ceiling", + "celery", + "cement", + "census", + "century", + "cereal", + "certain", + "chair", + "chalk", + "champion", + "change", + "chaos", + "chapter", + "charge", + "chase", + "chat", + "cheap", + "check", + "cheese", + "chef", + "cherry", + "chest", + "chicken", + "chief", + "child", + "chimney", + "choice", + "choose", + "chronic", + "chuckle", + "chunk", + "churn", + "cigar", + "cinnamon", + "circle", + "citizen", + "city", + "civil", + "claim", + "clap", + "clarify", + "claw", + "clay", + "clean", + "clerk", + "clever", + "click", + "client", + "cliff", + "climb", + "clinic", + "clip", + "clock", + "clog", + "close", + "cloth", + "cloud", + "clown", + "club", + "clump", + "cluster", + "clutch", + "coach", + "coast", + "coconut", + "code", + "coffee", + "coil", + "coin", + "collect", + "color", + "column", + "combine", + "come", + "comfort", + "comic", + "common", + "company", + "concert", + "conduct", + "confirm", + "congress", + "connect", + "consider", + "control", + "convince", + "cook", + "cool", + "copper", + "copy", + "coral", + "core", + "corn", + "correct", + "cost", + "cotton", + "couch", + "country", + "couple", + "course", + "cousin", + "cover", + "coyote", + "crack", + "cradle", + "craft", + "cram", + "crane", + "crash", + "crater", + "crawl", + "crazy", + "cream", + "credit", + "creek", + "crew", + "cricket", + "crime", + "crisp", + "critic", + "crop", + "cross", + "crouch", + "crowd", + "crucial", + "cruel", + "cruise", + "crumble", + "crunch", + "crush", + "cry", + "crystal", + "cube", + "culture", + "cup", + "cupboard", + "curious", + "current", + "curtain", + "curve", + "cushion", + "custom", + "cute", + "cycle", + "dad", + "damage", + "damp", + "dance", + "danger", + "daring", + "dash", + "daughter", + "dawn", + "day", + "deal", + "debate", + "debris", + "decade", + "december", + "decide", + "decline", + "decorate", + "decrease", + "deer", + "defense", + "define", + "defy", + "degree", + "delay", + "deliver", + "demand", + "demise", + "denial", + "dentist", + "deny", + "depart", + "depend", + "deposit", + "depth", + "deputy", + "derive", + "describe", + "desert", + "design", + "desk", + "despair", + "destroy", + "detail", + "detect", + "develop", + "device", + "devote", + "diagram", + "dial", + "diamond", + "diary", + "dice", + "diesel", + "diet", + "differ", + "digital", + "dignity", + "dilemma", + "dinner", + "dinosaur", + "direct", + "dirt", + "disagree", + "discover", + "disease", + "dish", + "dismiss", + "disorder", + "display", + "distance", + "divert", + "divide", + "divorce", + "dizzy", + "doctor", + "document", + "dog", + "doll", + "dolphin", + "domain", + "donate", + "donkey", + "donor", + "door", + "dose", + "double", + "dove", + "draft", + "dragon", + "drama", + "drastic", + "draw", + "dream", + "dress", + "drift", + "drill", + "drink", + "drip", + "drive", + "drop", + "drum", + "dry", + "duck", + "dumb", + "dune", + "during", + "dust", + "dutch", + "duty", + "dwarf", + "dynamic", + "eager", + "eagle", + "early", + "earn", + "earth", + "easily", + "east", + "easy", + "echo", + "ecology", + "economy", + "edge", + "edit", + "educate", + "effort", + "egg", + "eight", + "either", + "elbow", + "elder", + "electric", + "elegant", + "element", + "elephant", + "elevator", + "elite", + "else", + "embark", + "embody", + "embrace", + "emerge", + "emotion", + "employ", + "empower", + "empty", + "enable", + "enact", + "end", + "endless", + "endorse", + "enemy", + "energy", + "enforce", + "engage", + "engine", + "enhance", + "enjoy", + "enlist", + "enough", + "enrich", + "enroll", + "ensure", + "enter", + "entire", + "entry", + "envelope", + "episode", + "equal", + "equip", + "era", + "erase", + "erode", + "erosion", + "error", + "erupt", + "escape", + "essay", + "essence", + "estate", + "eternal", + "ethics", + "evidence", + "evil", + "evoke", + "evolve", + "exact", + "example", + "excess", + "exchange", + "excite", + "exclude", + "excuse", + "execute", + "exercise", + "exhaust", + "exhibit", + "exile", + "exist", + "exit", + "exotic", + "expand", + "expect", + "expire", + "explain", + "expose", + "express", + "extend", + "extra", + "eye", + "eyebrow", + "fabric", + "face", + "faculty", + "fade", + "faint", + "faith", + "fall", + "false", + "fame", + "family", + "famous", + "fan", + "fancy", + "fantasy", + "farm", + "fashion", + "fat", + "fatal", + "father", + "fatigue", + "fault", + "favorite", + "feature", + "february", + "federal", + "fee", + "feed", + "feel", + "female", + "fence", + "festival", + "fetch", + "fever", + "few", + "fiber", + "fiction", + "field", + "figure", + "file", + "film", + "filter", + "final", + "find", + "fine", + "finger", + "finish", + "fire", + "firm", + "first", + "fiscal", + "fish", + "fit", + "fitness", + "fix", + "flag", + "flame", + "flash", + "flat", + "flavor", + "flee", + "flight", + "flip", + "float", + "flock", + "floor", + "flower", + "fluid", + "flush", + "fly", + "foam", + "focus", + "fog", + "foil", + "fold", + "follow", + "food", + "foot", + "force", + "forest", + "forget", + "fork", + "fortune", + "forum", + "forward", + "fossil", + "foster", + "found", + "fox", + "fragile", + "frame", + "frequent", + "fresh", + "friend", + "fringe", + "frog", + "front", + "frost", + "frown", + "frozen", + "fruit", + "fuel", + "fun", + "funny", + "furnace", + "fury", + "future", + "gadget", + "gain", + "galaxy", + "gallery", + "game", + "gap", + "garage", + "garbage", + "garden", + "garlic", + "garment", + "gas", + "gasp", + "gate", + "gather", + "gauge", + "gaze", + "general", + "genius", + "genre", + "gentle", + "genuine", + "gesture", + "ghost", + "giant", + "gift", + "giggle", + "ginger", + "giraffe", + "girl", + "give", + "glad", + "glance", + "glare", + "glass", + "glide", + "glimpse", + "globe", + "gloom", + "glory", + "glove", + "glow", + "glue", + "goat", + "goddess", + "gold", + "good", + "goose", + "gorilla", + "gospel", + "gossip", + "govern", + "gown", + "grab", + "grace", + "grain", + "grant", + "grape", + "grass", + "gravity", + "great", + "green", + "grid", + "grief", + "grit", + "grocery", + "group", + "grow", + "grunt", + "guard", + "guess", + "guide", + "guilt", + "guitar", + "gun", + "gym", + "habit", + "hair", + "half", + "hammer", + "hamster", + "hand", + "happy", + "harbor", + "hard", + "harsh", + "harvest", + "hat", + "have", + "hawk", + "hazard", + "head", + "health", + "heart", + "heavy", + "hedgehog", + "height", + "hello", + "helmet", + "help", + "hen", + "hero", + "hidden", + "high", + "hill", + "hint", + "hip", + "hire", + "history", + "hobby", + "hockey", + "hold", + "hole", + "holiday", + "hollow", + "home", + "honey", + "hood", + "hope", + "horn", + "horror", + "horse", + "hospital", + "host", + "hotel", + "hour", + "hover", + "hub", + "huge", + "human", + "humble", + "humor", + "hundred", + "hungry", + "hunt", + "hurdle", + "hurry", + "hurt", + "husband", + "hybrid", + "ice", + "icon", + "idea", + "identify", + "idle", + "ignore", + "ill", + "illegal", + "illness", + "image", + "imitate", + "immense", + "immune", + "impact", + "impose", + "improve", + "impulse", + "inch", + "include", + "income", + "increase", + "index", + "indicate", + "indoor", + "industry", + "infant", + "inflict", + "inform", + "inhale", + "inherit", + "initial", + "inject", + "injury", + "inmate", + "inner", + "innocent", + "input", + "inquiry", + "insane", + "insect", + "inside", + "inspire", + "install", + "intact", + "interest", + "into", + "invest", + "invite", + "involve", + "iron", + "island", + "isolate", + "issue", + "item", + "ivory", + "jacket", + "jaguar", + "jar", + "jazz", + "jealous", + "jeans", + "jelly", + "jewel", + "job", + "join", + "joke", + "journey", + "joy", + "judge", + "juice", + "jump", + "jungle", + "junior", + "junk", + "just", + "kangaroo", + "keen", + "keep", + "ketchup", + "key", + "kick", + "kid", + "kidney", + "kind", + "kingdom", + "kiss", + "kit", + "kitchen", + "kite", + "kitten", + "kiwi", + "knee", + "knife", + "knock", + "know", + "lab", + "label", + "labor", + "ladder", + "lady", + "lake", + "lamp", + "language", + "laptop", + "large", + "later", + "latin", + "laugh", + "laundry", + "lava", + "law", + "lawn", + "lawsuit", + "layer", + "lazy", + "leader", + "leaf", + "learn", + "leave", + "lecture", + "left", + "leg", + "legal", + "legend", + "leisure", + "lemon", + "lend", + "length", + "lens", + "leopard", + "lesson", + "letter", + "level", + "liar", + "liberty", + "library", + "license", + "life", + "lift", + "light", + "like", + "limb", + "limit", + "link", + "lion", + "liquid", + "list", + "little", + "live", + "lizard", + "load", + "loan", + "lobster", + "local", + "lock", + "logic", + "lonely", + "long", + "loop", + "lottery", + "loud", + "lounge", + "love", + "loyal", + "lucky", + "luggage", + "lumber", + "lunar", + "lunch", + "luxury", + "lyrics", + "machine", + "mad", + "magic", + "magnet", + "maid", + "mail", + "main", + "major", + "make", + "mammal", + "man", + "manage", + "mandate", + "mango", + "mansion", + "manual", + "maple", + "marble", + "march", + "margin", + "marine", + "market", + "marriage", + "mask", + "mass", + "master", + "match", + "material", + "math", + "matrix", + "matter", + "maximum", + "maze", + "meadow", + "mean", + "measure", + "meat", + "mechanic", + "medal", + "media", + "melody", + "melt", + "member", + "memory", + "mention", + "menu", + "mercy", + "merge", + "merit", + "merry", + "mesh", + "message", + "metal", + "method", + "middle", + "midnight", + "milk", + "million", + "mimic", + "mind", + "minimum", + "minor", + "minute", + "miracle", + "mirror", + "misery", + "miss", + "mistake", + "mix", + "mixed", + "mixture", + "mobile", + "model", + "modify", + "mom", + "moment", + "monitor", + "monkey", + "monster", + "month", + "moon", + "moral", + "more", + "morning", + "mosquito", + "mother", + "motion", + "motor", + "mountain", + "mouse", + "move", + "movie", + "much", + "muffin", + "mule", + "multiply", + "muscle", + "museum", + "mushroom", + "music", + "must", + "mutual", + "myself", + "mystery", + "myth", + "naive", + "name", + "napkin", + "narrow", + "nasty", + "nation", + "nature", + "near", + "neck", + "need", + "negative", + "neglect", + "neither", + "nephew", + "nerve", + "nest", + "net", + "network", + "neutral", + "never", + "news", + "next", + "nice", + "night", + "noble", + "noise", + "nominee", + "noodle", + "normal", + "north", + "nose", + "notable", + "note", + "nothing", + "notice", + "novel", + "now", + "nuclear", + "number", + "nurse", + "nut", + "oak", + "obey", + "object", + "oblige", + "obscure", + "observe", + "obtain", + "obvious", + "occur", + "ocean", + "october", + "odor", + "off", + "offer", + "office", + "often", + "oil", + "okay", + "old", + "olive", + "olympic", + "omit", + "once", + "one", + "onion", + "online", + "only", + "open", + "opera", + "opinion", + "oppose", + "option", + "orange", + "orbit", + "orchard", + "order", + "ordinary", + "organ", + "orient", + "original", + "orphan", + "ostrich", + "other", + "outdoor", + "outer", + "output", + "outside", + "oval", + "oven", + "over", + "own", + "owner", + "oxygen", + "oyster", + "ozone", + "pact", + "paddle", + "page", + "pair", + "palace", + "palm", + "panda", + "panel", + "panic", + "panther", + "paper", + "parade", + "parent", + "park", + "parrot", + "party", + "pass", + "patch", + "path", + "patient", + "patrol", + "pattern", + "pause", + "pave", + "payment", + "peace", + "peanut", + "pear", + "peasant", + "pelican", + "pen", + "penalty", + "pencil", + "people", + "pepper", + "perfect", + "permit", + "person", + "pet", + "phone", + "photo", + "phrase", + "physical", + "piano", + "picnic", + "picture", + "piece", + "pig", + "pigeon", + "pill", + "pilot", + "pink", + "pioneer", + "pipe", + "pistol", + "pitch", + "pizza", + "place", + "planet", + "plastic", + "plate", + "play", + "please", + "pledge", + "pluck", + "plug", + "plunge", + "poem", + "poet", + "point", + "polar", + "pole", + "police", + "pond", + "pony", + "pool", + "popular", + "portion", + "position", + "possible", + "post", + "potato", + "pottery", + "poverty", + "powder", + "power", + "practice", + "praise", + "predict", + "prefer", + "prepare", + "present", + "pretty", + "prevent", + "price", + "pride", + "primary", + "print", + "priority", + "prison", + "private", + "prize", + "problem", + "process", + "produce", + "profit", + "program", + "project", + "promote", + "proof", + "property", + "prosper", + "protect", + "proud", + "provide", + "public", + "pudding", + "pull", + "pulp", + "pulse", + "pumpkin", + "punch", + "pupil", + "puppy", + "purchase", + "purity", + "purpose", + "purse", + "push", + "put", + "puzzle", + "pyramid", + "quality", + "quantum", + "quarter", + "question", + "quick", + "quit", + "quiz", + "quote", + "rabbit", + "raccoon", + "race", + "rack", + "radar", + "radio", + "rail", + "rain", + "raise", + "rally", + "ramp", + "ranch", + "random", + "range", + "rapid", + "rare", + "rate", + "rather", + "raven", + "raw", + "razor", + "ready", + "real", + "reason", + "rebel", + "rebuild", + "recall", + "receive", + "recipe", + "record", + "recycle", + "reduce", + "reflect", + "reform", + "refuse", + "region", + "regret", + "regular", + "reject", + "relax", + "release", + "relief", + "rely", + "remain", + "remember", + "remind", + "remove", + "render", + "renew", + "rent", + "reopen", + "repair", + "repeat", + "replace", + "report", + "require", + "rescue", + "resemble", + "resist", + "resource", + "response", + "result", + "retire", + "retreat", + "return", + "reunion", + "reveal", + "review", + "reward", + "rhythm", + "rib", + "ribbon", + "rice", + "rich", + "ride", + "ridge", + "rifle", + "right", + "rigid", + "ring", + "riot", + "ripple", + "risk", + "ritual", + "rival", + "river", + "road", + "roast", + "robot", + "robust", + "rocket", + "romance", + "roof", + "rookie", + "room", + "rose", + "rotate", + "rough", + "round", + "route", + "royal", + "rubber", + "rude", + "rug", + "rule", + "run", + "runway", + "rural", + "sad", + "saddle", + "sadness", + "safe", + "sail", + "salad", + "salmon", + "salon", + "salt", + "salute", + "same", + "sample", + "sand", + "satisfy", + "satoshi", + "sauce", + "sausage", + "save", + "say", + "scale", + "scan", + "scare", + "scatter", + "scene", + "scheme", + "school", + "science", + "scissors", + "scorpion", + "scout", + "scrap", + "screen", + "script", + "scrub", + "sea", + "search", + "season", + "seat", + "second", + "secret", + "section", + "security", + "seed", + "seek", + "segment", + "select", + "sell", + "seminar", + "senior", + "sense", + "sentence", + "series", + "service", + "session", + "settle", + "setup", + "seven", + "shadow", + "shaft", + "shallow", + "share", + "shed", + "shell", + "sheriff", + "shield", + "shift", + "shine", + "ship", + "shiver", + "shock", + "shoe", + "shoot", + "shop", + "short", + "shoulder", + "shove", + "shrimp", + "shrug", + "shuffle", + "shy", + "sibling", + "sick", + "side", + "siege", + "sight", + "sign", + "silent", + "silk", + "silly", + "silver", + "similar", + "simple", + "since", + "sing", + "siren", + "sister", + "situate", + "six", + "size", + "skate", + "sketch", + "ski", + "skill", + "skin", + "skirt", + "skull", + "slab", + "slam", + "sleep", + "slender", + "slice", + "slide", + "slight", + "slim", + "slogan", + "slot", + "slow", + "slush", + "small", + "smart", + "smile", + "smoke", + "smooth", + "snack", + "snake", + "snap", + "sniff", + "snow", + "soap", + "soccer", + "social", + "sock", + "soda", + "soft", + "solar", + "soldier", + "solid", + "solution", + "solve", + "someone", + "song", + "soon", + "sorry", + "sort", + "soul", + "sound", + "soup", + "source", + "south", + "space", + "spare", + "spatial", + "spawn", + "speak", + "special", + "speed", + "spell", + "spend", + "sphere", + "spice", + "spider", + "spike", + "spin", + "spirit", + "split", + "spoil", + "sponsor", + "spoon", + "sport", + "spot", + "spray", + "spread", + "spring", + "spy", + "square", + "squeeze", + "squirrel", + "stable", + "stadium", + "staff", + "stage", + "stairs", + "stamp", + "stand", + "start", + "state", + "stay", + "steak", + "steel", + "stem", + "step", + "stereo", + "stick", + "still", + "sting", + "stock", + "stomach", + "stone", + "stool", + "story", + "stove", + "strategy", + "street", + "strike", + "strong", + "struggle", + "student", + "stuff", + "stumble", + "style", + "subject", + "submit", + "subway", + "success", + "such", + "sudden", + "suffer", + "sugar", + "suggest", + "suit", + "summer", + "sun", + "sunny", + "sunset", + "super", + "supply", + "supreme", + "sure", + "surface", + "surge", + "surprise", + "surround", + "survey", + "suspect", + "sustain", + "swallow", + "swamp", + "swap", + "swarm", + "swear", + "sweet", + "swift", + "swim", + "swing", + "switch", + "sword", + "symbol", + "symptom", + "syrup", + "system", + "table", + "tackle", + "tag", + "tail", + "talent", + "talk", + "tank", + "tape", + "target", + "task", + "taste", + "tattoo", + "taxi", + "teach", + "team", + "tell", + "ten", + "tenant", + "tennis", + "tent", + "term", + "test", + "text", + "thank", + "that", + "theme", + "then", + "theory", + "there", + "they", + "thing", + "this", + "thought", + "three", + "thrive", + "throw", + "thumb", + "thunder", + "ticket", + "tide", + "tiger", + "tilt", + "timber", + "time", + "tiny", + "tip", + "tired", + "tissue", + "title", + "toast", + "tobacco", + "today", + "toddler", + "toe", + "together", + "toilet", + "token", + "tomato", + "tomorrow", + "tone", + "tongue", + "tonight", + "tool", + "tooth", + "top", + "topic", + "topple", + "torch", + "tornado", + "tortoise", + "toss", + "total", + "tourist", + "toward", + "tower", + "town", + "toy", + "track", + "trade", + "traffic", + "tragic", + "train", + "transfer", + "trap", + "trash", + "travel", + "tray", + "treat", + "tree", + "trend", + "trial", + "tribe", + "trick", + "trigger", + "trim", + "trip", + "trophy", + "trouble", + "truck", + "true", + "truly", + "trumpet", + "trust", + "truth", + "try", + "tube", + "tuition", + "tumble", + "tuna", + "tunnel", + "turkey", + "turn", + "turtle", + "twelve", + "twenty", + "twice", + "twin", + "twist", + "two", + "type", + "typical", + "ugly", + "umbrella", + "unable", + "unaware", + "uncle", + "uncover", + "under", + "undo", + "unfair", + "unfold", + "unhappy", + "uniform", + "unique", + "unit", + "universe", + "unknown", + "unlock", + "until", + "unusual", + "unveil", + "update", + "upgrade", + "uphold", + "upon", + "upper", + "upset", + "urban", + "urge", + "usage", + "use", + "used", + "useful", + "useless", + "usual", + "utility", + "vacant", + "vacuum", + "vague", + "valid", + "valley", + "valve", + "van", + "vanish", + "vapor", + "various", + "vast", + "vault", + "vehicle", + "velvet", + "vendor", + "venture", + "venue", + "verb", + "verify", + "version", + "very", + "vessel", + "veteran", + "viable", + "vibrant", + "vicious", + "victory", + "video", + "view", + "village", + "vintage", + "violin", + "virtual", + "virus", + "visa", + "visit", + "visual", + "vital", + "vivid", + "vocal", + "voice", + "void", + "volcano", + "volume", + "vote", + "voyage", + "wage", + "wagon", + "wait", + "walk", + "wall", + "walnut", + "want", + "warfare", + "warm", + "warrior", + "wash", + "wasp", + "waste", + "water", + "wave", + "way", + "wealth", + "weapon", + "wear", + "weasel", + "weather", + "web", + "wedding", + "weekend", + "weird", + "welcome", + "west", + "wet", + "whale", + "what", + "wheat", + "wheel", + "when", + "where", + "whip", + "whisper", + "wide", + "width", + "wife", + "wild", + "will", + "win", + "window", + "wine", + "wing", + "wink", + "winner", + "winter", + "wire", + "wisdom", + "wise", + "wish", + "witness", + "wolf", + "woman", + "wonder", + "wood", + "wool", + "word", + "work", + "world", + "worry", + "worth", + "wrap", + "wreck", + "wrestle", + "wrist", + "write", + "wrong", + "yard", + "year", + "yellow", + "you", + "young", + "youth", + "zebra", + "zero", + "zone", + "zoo" + };} +} + diff --git a/src/BIP39Wallet/Types/Entropy.cs b/src/BIP39Wallet/Types/Entropy.cs new file mode 100644 index 0000000..ae8f12a --- /dev/null +++ b/src/BIP39Wallet/Types/Entropy.cs @@ -0,0 +1,19 @@ +namespace BIP39Wallet.Types +{ + public class Entropy + { + public string Hex { get;} + public Language Language { get;} + + public Entropy(string hex, Language language = Language.English) + { + Hex = hex; + Language = language; + } + + public override string ToString() + { + return Hex; + } + } +} \ No newline at end of file diff --git a/src/BIP39Wallet/Types/Mnemonic.cs b/src/BIP39Wallet/Types/Mnemonic.cs new file mode 100644 index 0000000..ba0d57c --- /dev/null +++ b/src/BIP39Wallet/Types/Mnemonic.cs @@ -0,0 +1,25 @@ +#pragma warning disable CS8618 +namespace BIP39Wallet.Types +{ + public class Mnemonic + { + public string Value { get; set; } + public Language Language { get; set; } + + public Mnemonic() + { + + } + + public Mnemonic(string value, Language language = Language.English) + { + Value = value; + Language = language; + } + + public override string ToString() + { + return Value; + } + } +} \ No newline at end of file diff --git a/test/BIP39.HDWallet.Core.Test/BIP39.HDWallet.Core.Tests.cs b/test/BIP39.HDWallet.Core.Test/BIP39.HDWallet.Core.Tests.cs new file mode 100644 index 0000000..87c4b70 --- /dev/null +++ b/test/BIP39.HDWallet.Core.Test/BIP39.HDWallet.Core.Tests.cs @@ -0,0 +1,29 @@ +using AElf; +using AElf.Types; +using BIP39.HDWallet; +using BIP39.HDWallet.Core; +using NBitcoin; +using Xunit; + +namespace BIP39HDWallet.Core +{ + public class HDWalletTests + { + [Fact] + public void Generate_Wallet_by_Seed() + { + var seedHex = + "01f5bced59dec48e362f2c45b5de68b9fd6c92c6634f44d6d40aab69056506f0e35524a518034ddc1192e1dacd32c1ed3eaa3c3b131c88ed8e7e54c49a5d0998"; + var masterWallet = new HDWallet(seedHex, "m/44'/1616'"); + var account = masterWallet.GetAccount(0); + var wallet = account.GetExternalWallet(0); + var key = new Key(wallet.PrivateKey, -1, false); + var privateKey = wallet.PrivateKey.ToHex(); + var publicKey = key.PubKey.ToHex(); + var address = Address.FromPublicKey(key.PubKey.ToBytes()).ToString().Trim('\"'); + Assert.Equal("7d4a62f9d18324f4a2127ec1ead8192f3ce6a90ea6e05b3aff2ef37992115d36", privateKey); + Assert.Equal("04010fe17376a8505942a3fa64159cf380f656c78d12310b377e378ca633f6f93c26cfbaafca18b3f95ebc67a464facaa6ebafef0fad91f72843233eea81296e96", publicKey); + Assert.Equal("2fW7PaX69idNEv38B8aWQSGiZXAuVk4EBHoMW63fVyq3sMMFZi", address); + } + } +} \ No newline at end of file diff --git a/test/BIP39.HDWallet.Core.Test/BIP39.HDWallet.Core.Tests.csproj b/test/BIP39.HDWallet.Core.Test/BIP39.HDWallet.Core.Tests.csproj new file mode 100644 index 0000000..f5dac30 --- /dev/null +++ b/test/BIP39.HDWallet.Core.Test/BIP39.HDWallet.Core.Tests.csproj @@ -0,0 +1,16 @@ + + + net7.0;netstandard2.1 + http://192.168.66.111:8081/repository/nuget-group/index.json + BIP39HDWallet.Core + + + + + + + + + + + \ No newline at end of file diff --git a/test/BIP39.HDWallet.Test/BIP39.HDWallet.Tests.cs b/test/BIP39.HDWallet.Test/BIP39.HDWallet.Tests.cs new file mode 100644 index 0000000..83fce3c --- /dev/null +++ b/test/BIP39.HDWallet.Test/BIP39.HDWallet.Tests.cs @@ -0,0 +1,31 @@ +using System; +using AElf.Types; +using Xunit; + +namespace BIP39HDWallet.Tests +{ + public class HDWalletTests + { + static byte[] StringToByteArray(string hexString) + { + int length = hexString.Length; + byte[] byteArray = new byte[length / 2]; + + for (int i = 0; i < length; i += 2) + { + byteArray[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16); + } + + return byteArray; + } + + [Fact] + public void Generate_Correct_Address_by_PublicKey() + { + var publicKey = + "04c0f6abf0e3122f4a49646d67bacf85c80ad726ca781ccba572033a31162f22e55a4a106760cbf1306f26c25aea1e4bb71ee66cb3c5104245d6040cce64546cc7"; + var address = Address.FromPublicKey(StringToByteArray(publicKey)).ToString().Trim('\"'); + Assert.Equal("2ihA5K7sSsA78gekyhuh7gcnX4JkGVqJmSGnf8Kj1hZefR4sX5", address); + } + } +} \ No newline at end of file diff --git a/test/BIP39.HDWallet.Test/BIP39.HDWallet.Tests.csproj b/test/BIP39.HDWallet.Test/BIP39.HDWallet.Tests.csproj new file mode 100644 index 0000000..d4da64a --- /dev/null +++ b/test/BIP39.HDWallet.Test/BIP39.HDWallet.Tests.csproj @@ -0,0 +1,15 @@ + + + net7.0;netstandard2.1 + http://192.168.66.111:8081/repository/nuget-group/index.json + + + + + + + + + + + \ No newline at end of file diff --git a/test/BIP39Wallet.Test/BIP39Wallet.Tests.cs b/test/BIP39Wallet.Test/BIP39Wallet.Tests.cs new file mode 100644 index 0000000..0853121 --- /dev/null +++ b/test/BIP39Wallet.Test/BIP39Wallet.Tests.cs @@ -0,0 +1,86 @@ +using System; +using Xunit; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using BIP39.HDWallet.Core; + +namespace BIP39Wallet.Tests +{ + public class MyData + { + public List> English { get; set; } + } + + [SuppressMessage("ReSharper", "InconsistentNaming")] + public class WalletTests + { + [Fact] + public void Sign_ReturnsValidSignature() + { + const string PRIVATE_KEY = "03bd0cea9730bcfc8045248fd7f4841ea19315995c44801a3dfede0ca872f808"; + const string HASH = "68656c6c6f20776f726c643939482801"; + const string SIGNED = "59EF1D3B2B853FCA1E33D07765DEBAAF38A81442CFE90822D4334E8FCE9889D80C99A0BE1858C1F26B4D99987EFF6003F33B7C3F32BBDB9CEEC68A1E8A4DB4B000"; + + + // Arrange + var wallet = new Wallet(); + var result = wallet.Sign(StringToByteArray(PRIVATE_KEY), Encoding.UTF8.GetBytes(HASH)); + + // Assert + Assert.Equal(SIGNED.ToLower(), result.ToHexString()); + } + + static byte[] StringToByteArray(string hexString) + { + int length = hexString.Length; + byte[] byteArray = new byte[length / 2]; + + for (int i = 0; i < length; i += 2) + { + byteArray[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16); + } + + return byteArray; + } + + [Fact] + public void CreateWallet_ReturnsValidAccountInfo() + { + // Arrange + var wallet = new Wallet(); + var strength = 128; // Set mnemonic strength (in bits) + var language = Language.English; // Set mnemonic language + + // Act + var accountInfo = wallet.CreateWallet(strength, language, ""); + + // Assert + Assert.NotNull(accountInfo); + } + + [Fact] + public void GetWalletByMnemonic_ReturnsValidAccountInfo() + { + // Arrange + var wallet = new Wallet(); + var mnemonic = "put draft unhappy diary arctic sponsor alien awesome adjust bubble maid brave"; + var accountInfo = wallet.GetWalletByMnemonic(mnemonic); + Assert.NotNull(accountInfo); + Assert.Equal("f0c3bf2cfc4f50405afb2f1236d653cf0581f4caedf4f1e0b49480c840659ba9", accountInfo.PrivateKey); + Assert.Equal("04c0f6abf0e3122f4a49646d67bacf85c80ad726ca781ccba572033a31162f22e55a4a106760cbf1306f26c25aea1e4bb71ee66cb3c5104245d6040cce64546cc7", accountInfo.PublicKey); + Assert.Equal("2ihA5K7sSsA78gekyhuh7gcnX4JkGVqJmSGnf8Kj1hZefR4sX5", accountInfo.Address); + } + + [Fact] + public void GetWalletByPrivateKey_ReturnsValidAccountInfo() + { + var wallet = new Wallet(); + var privateKey = "f0c3bf2cfc4f50405afb2f1236d653cf0581f4caedf4f1e0b49480c840659ba9"; + var accountInfo = wallet.GetWalletByPrivateKey(privateKey); + Assert.NotNull(accountInfo); + Assert.Equal("04c0f6abf0e3122f4a49646d67bacf85c80ad726ca781ccba572033a31162f22e55a4a106760cbf1306f26c25aea1e4bb71ee66cb3c5104245d6040cce64546cc7", accountInfo.PublicKey); + Assert.Equal("2ihA5K7sSsA78gekyhuh7gcnX4JkGVqJmSGnf8Kj1hZefR4sX5", accountInfo.Address); + } + } +} diff --git a/test/BIP39Wallet.Test/BIP39Wallet.Tests.csproj b/test/BIP39Wallet.Test/BIP39Wallet.Tests.csproj new file mode 100644 index 0000000..7184f2a --- /dev/null +++ b/test/BIP39Wallet.Test/BIP39Wallet.Tests.csproj @@ -0,0 +1,17 @@ + + + net7.0;netstandard2.1 + http://192.168.66.111:8081/repository/nuget-group/index.json + + + + + + + + + + + + + \ No newline at end of file