From c4af1966ca5a6ae565927fda28e77739964e80ae Mon Sep 17 00:00:00 2001 From: murlux Date: Sun, 16 May 2021 18:32:06 +0100 Subject: [PATCH] Finished bootstrapping wallet and key generation with both sollet and solana-keygen compatibility. --- README.md | 4 - Solnet.sln | 42 +--- src/Solnet.Accounts/Account.cs | 73 ------ src/Solnet.Accounts/Solnet.Accounts.csproj | 17 -- .../SolanaKeygenKeyGeneration.cs | 39 ++++ src/Solnet.Examples/SolletKeyGeneration.cs | 57 +++++ src/Solnet.Examples/Solnet.Examples.csproj | 9 + src/Solnet.Examples/SolnetRpcTester.cs | 2 +- .../Crypto/DecryptionException.cs | 20 -- .../Crypto/IRandomBytesGenerator.cs | 22 -- src/Solnet.KeyStore/Crypto/KeyStoreCrypto.cs | 7 - .../Crypto/RandomBytesGenerator.cs | 45 ---- src/Solnet.KeyStore/IKeyStore.cs | 38 ++++ src/Solnet.KeyStore/IKeyStoreService.cs | 52 ----- src/Solnet.KeyStore/InvalidKdfException.cs | 20 -- src/Solnet.KeyStore/KdfType.cs | 13 -- src/Solnet.KeyStore/KeyStore.cs | 80 +++++++ src/Solnet.KeyStore/KeyStoreKdfChecker.cs | 33 --- src/Solnet.KeyStore/KeyStorePbkdf2Service.cs | 7 - src/Solnet.KeyStore/KeyStoreScryptService.cs | 9 - src/Solnet.KeyStore/KeyStoreService.cs | 7 - src/Solnet.KeyStore/KeyStoreServiceBase.cs | 104 --------- src/Solnet.KeyStore/KeyStoreType.cs | 13 ++ src/Solnet.KeyStore/Model/CipherParams.cs | 32 --- src/Solnet.KeyStore/Model/CryptoInfo.cs | 77 ------- src/Solnet.KeyStore/Model/KdfParams.cs | 22 -- src/Solnet.KeyStore/Model/KeyStore.cs | 35 --- src/Solnet.KeyStore/SolanaKeyStore.cs | 60 +++++ src/Solnet.KeyStore/Solnet.KeyStore.csproj | 8 +- src/Solnet.Models/Account.cs | 35 --- src/Solnet.Models/Solnet.Models.csproj | 4 + src/Solnet.Rpc/README.md | 0 src/Solnet.Signer/Solnet.Signer.csproj | 7 - src/Solnet.Util/BigEndianBuffer.cs | 41 ++++ src/Solnet.Util/Extensions.cs | 58 +++++ src/Solnet.Util/README.md | 0 .../Solnet.Util.csproj} | 5 +- src/Solnet.Wallet/Account.cs | 52 +++++ src/Solnet.Wallet/Ed25519Bip32.cs | 134 +++++++++++ src/Solnet.Wallet/Key/KeyPair.cs | 20 ++ src/Solnet.Wallet/SecureRandom.cs | 35 --- src/Solnet.Wallet/SeedMode.cs | 24 ++ src/Solnet.Wallet/Solnet.Wallet.csproj | 11 +- src/Solnet.Wallet/Wallet.cs | 215 ++++++++++++++++++ src/Solnet.Web3/Solnet.Web3.csproj | 7 - 45 files changed, 876 insertions(+), 719 deletions(-) delete mode 100644 src/Solnet.Accounts/Account.cs delete mode 100644 src/Solnet.Accounts/Solnet.Accounts.csproj create mode 100644 src/Solnet.Examples/SolanaKeygenKeyGeneration.cs create mode 100644 src/Solnet.Examples/SolletKeyGeneration.cs delete mode 100644 src/Solnet.KeyStore/Crypto/DecryptionException.cs delete mode 100644 src/Solnet.KeyStore/Crypto/IRandomBytesGenerator.cs delete mode 100644 src/Solnet.KeyStore/Crypto/KeyStoreCrypto.cs delete mode 100644 src/Solnet.KeyStore/Crypto/RandomBytesGenerator.cs create mode 100644 src/Solnet.KeyStore/IKeyStore.cs delete mode 100644 src/Solnet.KeyStore/IKeyStoreService.cs delete mode 100644 src/Solnet.KeyStore/InvalidKdfException.cs delete mode 100644 src/Solnet.KeyStore/KdfType.cs create mode 100644 src/Solnet.KeyStore/KeyStore.cs delete mode 100644 src/Solnet.KeyStore/KeyStoreKdfChecker.cs delete mode 100644 src/Solnet.KeyStore/KeyStorePbkdf2Service.cs delete mode 100644 src/Solnet.KeyStore/KeyStoreScryptService.cs delete mode 100644 src/Solnet.KeyStore/KeyStoreService.cs delete mode 100644 src/Solnet.KeyStore/KeyStoreServiceBase.cs create mode 100644 src/Solnet.KeyStore/KeyStoreType.cs delete mode 100644 src/Solnet.KeyStore/Model/CipherParams.cs delete mode 100644 src/Solnet.KeyStore/Model/CryptoInfo.cs delete mode 100644 src/Solnet.KeyStore/Model/KdfParams.cs delete mode 100644 src/Solnet.KeyStore/Model/KeyStore.cs create mode 100644 src/Solnet.KeyStore/SolanaKeyStore.cs delete mode 100644 src/Solnet.Models/Account.cs create mode 100644 src/Solnet.Rpc/README.md delete mode 100644 src/Solnet.Signer/Solnet.Signer.csproj create mode 100644 src/Solnet.Util/BigEndianBuffer.cs create mode 100644 src/Solnet.Util/Extensions.cs create mode 100644 src/Solnet.Util/README.md rename src/{Solnet.Hex/Solnet.Hex.csproj => Solnet.Util/Solnet.Util.csproj} (55%) create mode 100644 src/Solnet.Wallet/Account.cs create mode 100644 src/Solnet.Wallet/Ed25519Bip32.cs create mode 100644 src/Solnet.Wallet/Key/KeyPair.cs delete mode 100644 src/Solnet.Wallet/SecureRandom.cs create mode 100644 src/Solnet.Wallet/SeedMode.cs delete mode 100644 src/Solnet.Web3/Solnet.Web3.csproj diff --git a/README.md b/README.md index 38b4f710..7872bfa3 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@ ![solnet](docs/assets/solnet-horizontal.png) -# Solnet - -Technical support, chat and collaboration at the [Solnet Discord](). - # What is Solnet? Solnet is Solana's .NET integration library. diff --git a/Solnet.sln b/Solnet.sln index a4a0e97b..5c4fc17d 100644 --- a/Solnet.sln +++ b/Solnet.sln @@ -1,59 +1,33 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Web3", "src\Solnet.Web3\Solnet.Web3.csproj", "{9185CC62-0674-48B2-9FFC-09D4887E3BAA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Web3.Accounts", "src\Solnet.Web3.Accounts\Solnet.Web3.Accounts.csproj", "{750E6A53-38C2-4A54-A37F-0F76CC9616BB}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Rpc", "src\Solnet.Rpc\Solnet.Rpc.csproj", "{FE8D270F-30AE-4818-AC7B-030591820419}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Accounts", "src\Solnet.Accounts\Solnet.Accounts.csproj", "{B1FB0091-A25A-4B6C-AAC6-68BB0409D2EE}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Models", "src\Solnet.Models\Solnet.Models.csproj", "{8B503BC5-F1C8-4F87-8C53-AD4D6CEFFCF4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Hex", "src\Solnet.Hex\Solnet.Hex.csproj", "{E1E63AAD-23DC-494B-9C8F-7912EB9784EA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Signer", "src\Solnet.Signer\Solnet.Signer.csproj", "{48E0662C-A237-469E-B60A-FD472D92E012}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.KeyStore", "src\Solnet.KeyStore\Solnet.KeyStore.csproj", "{BA5BFA52-8010-4438-BC87-153A34464CAA}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Wallet", "src\Solnet.Wallet\Solnet.Wallet.csproj", "{DA22B717-4FD7-4D08-A546-DFF3B21B7BAE}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Examples", "src\Solnet.Examples\Solnet.Examples.csproj", "{1198ED67-B1F1-4DE9-BDE8-AE6C96DA33A6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Util", "src\Solnet.Util\Solnet.Util.csproj", "{5B8896C9-4B06-41C9-B0F9-05B9F6190CA5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.KeyGen", "src\Solnet.KeyGen\Solnet.KeyGen.csproj", "{3C534072-1F5F-4625-9831-ACBBA28E5C47}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {9185CC62-0674-48B2-9FFC-09D4887E3BAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9185CC62-0674-48B2-9FFC-09D4887E3BAA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9185CC62-0674-48B2-9FFC-09D4887E3BAA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9185CC62-0674-48B2-9FFC-09D4887E3BAA}.Release|Any CPU.Build.0 = Release|Any CPU - {750E6A53-38C2-4A54-A37F-0F76CC9616BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {750E6A53-38C2-4A54-A37F-0F76CC9616BB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {750E6A53-38C2-4A54-A37F-0F76CC9616BB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {750E6A53-38C2-4A54-A37F-0F76CC9616BB}.Release|Any CPU.Build.0 = Release|Any CPU {FE8D270F-30AE-4818-AC7B-030591820419}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FE8D270F-30AE-4818-AC7B-030591820419}.Debug|Any CPU.Build.0 = Debug|Any CPU {FE8D270F-30AE-4818-AC7B-030591820419}.Release|Any CPU.ActiveCfg = Release|Any CPU {FE8D270F-30AE-4818-AC7B-030591820419}.Release|Any CPU.Build.0 = Release|Any CPU - {B1FB0091-A25A-4B6C-AAC6-68BB0409D2EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B1FB0091-A25A-4B6C-AAC6-68BB0409D2EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B1FB0091-A25A-4B6C-AAC6-68BB0409D2EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B1FB0091-A25A-4B6C-AAC6-68BB0409D2EE}.Release|Any CPU.Build.0 = Release|Any CPU {8B503BC5-F1C8-4F87-8C53-AD4D6CEFFCF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8B503BC5-F1C8-4F87-8C53-AD4D6CEFFCF4}.Debug|Any CPU.Build.0 = Debug|Any CPU {8B503BC5-F1C8-4F87-8C53-AD4D6CEFFCF4}.Release|Any CPU.ActiveCfg = Release|Any CPU {8B503BC5-F1C8-4F87-8C53-AD4D6CEFFCF4}.Release|Any CPU.Build.0 = Release|Any CPU - {E1E63AAD-23DC-494B-9C8F-7912EB9784EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E1E63AAD-23DC-494B-9C8F-7912EB9784EA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E1E63AAD-23DC-494B-9C8F-7912EB9784EA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E1E63AAD-23DC-494B-9C8F-7912EB9784EA}.Release|Any CPU.Build.0 = Release|Any CPU - {48E0662C-A237-469E-B60A-FD472D92E012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {48E0662C-A237-469E-B60A-FD472D92E012}.Debug|Any CPU.Build.0 = Debug|Any CPU - {48E0662C-A237-469E-B60A-FD472D92E012}.Release|Any CPU.ActiveCfg = Release|Any CPU - {48E0662C-A237-469E-B60A-FD472D92E012}.Release|Any CPU.Build.0 = Release|Any CPU {BA5BFA52-8010-4438-BC87-153A34464CAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BA5BFA52-8010-4438-BC87-153A34464CAA}.Debug|Any CPU.Build.0 = Debug|Any CPU {BA5BFA52-8010-4438-BC87-153A34464CAA}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -66,5 +40,13 @@ Global {1198ED67-B1F1-4DE9-BDE8-AE6C96DA33A6}.Debug|Any CPU.Build.0 = Debug|Any CPU {1198ED67-B1F1-4DE9-BDE8-AE6C96DA33A6}.Release|Any CPU.ActiveCfg = Release|Any CPU {1198ED67-B1F1-4DE9-BDE8-AE6C96DA33A6}.Release|Any CPU.Build.0 = Release|Any CPU + {5B8896C9-4B06-41C9-B0F9-05B9F6190CA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B8896C9-4B06-41C9-B0F9-05B9F6190CA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B8896C9-4B06-41C9-B0F9-05B9F6190CA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B8896C9-4B06-41C9-B0F9-05B9F6190CA5}.Release|Any CPU.Build.0 = Release|Any CPU + {3C534072-1F5F-4625-9831-ACBBA28E5C47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C534072-1F5F-4625-9831-ACBBA28E5C47}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C534072-1F5F-4625-9831-ACBBA28E5C47}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C534072-1F5F-4625-9831-ACBBA28E5C47}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/Solnet.Accounts/Account.cs b/src/Solnet.Accounts/Account.cs deleted file mode 100644 index 6feb8d94..00000000 --- a/src/Solnet.Accounts/Account.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Numerics; -using Solnet.RPC.Accounts; - -namespace Solnet.Accounts -{ - /// - /// - /// - public class Account : IAccount - { - public BigInteger? ChainId { get; } - - public static Account LoadFromKeyStore(string json, string password, BigInteger? chainId = null) - { - var keyStoreService = new KeyStoreService(); - var key = keyStoreService.DecryptKeyStoreFromJson(password, json); - return new Account(key, chainId); - } - - public string PrivateKey { get; private set; } - public string PublicKey { get; private set; } - - - public Account(SolECKey key, BigInteger? chainId = null) - { - ChainId = chainId; - Initialise(key); - } - - public Account(string privateKey, BigInteger? chainId = null) - { - ChainId = chainId; - Initialise(new SolECKey(privateKey)); - } - - public Account(byte[] privateKey, BigInteger? chainId = null) - { - ChainId = chainId; - Initialise(new SolECKey(privateKey, true)); - } - - public Account(SolECKey key, Chain chain) : this(key, (int) chain) - { - } - - public Account(string privateKey, Chain chain) : this(privateKey, (int) chain) - { - } - - public Account(byte[] privateKey, Chain chain) : this(privateKey, (int) chain) - { - } - - private void Initialise(SolECKey key) - { - PrivateKey = key.GetPrivateKey(); - Address = key.GetPublicAddress(); - PublicKey = key.GetPubKey().ToHex(); - InitialiseDefaultTransactionManager(); - } - - protected virtual void InitialiseDefaultTransactionManager() - { - TransactionManager = new AccountSignerTransactionManager(null, this, ChainId); - } - - public string Address { get; protected set; } - public ITransactionManager TransactionManager { get; protected set; } - - public INonceService NonceService { get; set; } - } -} \ No newline at end of file diff --git a/src/Solnet.Accounts/Solnet.Accounts.csproj b/src/Solnet.Accounts/Solnet.Accounts.csproj deleted file mode 100644 index d6089ef5..00000000 --- a/src/Solnet.Accounts/Solnet.Accounts.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - net5.0 - - - - - - - - - - - - - diff --git a/src/Solnet.Examples/SolanaKeygenKeyGeneration.cs b/src/Solnet.Examples/SolanaKeygenKeyGeneration.cs new file mode 100644 index 00000000..0abfd1bb --- /dev/null +++ b/src/Solnet.Examples/SolanaKeygenKeyGeneration.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using NBitcoin; +using Solnet.Wallet; + +namespace Solnet.Examples +{ + public class SolanaKeygenWallet + { + static void Example() + { + const string expectedSolKeygenPublicKey = "AZzmpdbZWARkPzL8GKRHjjwY74st4URgk9v7QBubeWba"; + const string expectedSolKeygenPrivateKey = "2RitwnKZwoigHk9S3ftvFQhoTy5QQKAipNjZHDgCet8hyciUbJSuhMWDKRL8JKE784pK8jJPFaNerFsS6KXhY9K6"; + + // mnemonic and passphrase to derive seed + var passphrase = "thisiseightbytesithink"; + var mnemonic = new Mnemonic("route clerk disease box emerge airport loud waste attitude film army tray forward deal onion eight catalog surface unit card window walnut wealth medal", Wordlist.English); + + var solKeygenWallet = new Wallet.Wallet(mnemonic, passphrase, SeedMode.Bip39); + + Console.WriteLine($"SOLLET publicKey>b58 {solKeygenWallet.Account.EncodedPublicKey}"); + Console.WriteLine($"SOLLET privateKey>b58 {solKeygenWallet.Account.EncodedPrivateKey}"); + + Debug.Assert(solKeygenWallet.Account.EncodedPublicKey == expectedSolKeygenPublicKey && solKeygenWallet.Account.EncodedPrivateKey == expectedSolKeygenPrivateKey); + + if (solKeygenWallet.Account.EncodedPublicKey != expectedSolKeygenPublicKey || + solKeygenWallet.Account.EncodedPrivateKey != expectedSolKeygenPrivateKey) + { + Console.WriteLine("NOT GOOD FOR THE SOL"); + } + else + { + Console.WriteLine("GOOD FOR THE SOL"); + } + + } + } +} \ No newline at end of file diff --git a/src/Solnet.Examples/SolletKeyGeneration.cs b/src/Solnet.Examples/SolletKeyGeneration.cs new file mode 100644 index 00000000..56959feb --- /dev/null +++ b/src/Solnet.Examples/SolletKeyGeneration.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using NBitcoin; + +namespace Solnet.Examples +{ + public class SolletKeyGeneration + { + static void Example() + { + var expectedSolletAddresses = new List + { + new []{"6bhhceZToGG9RsTe1nfNFXEMjavhj6CV55EsvearAt2z", "5S1UT7L6bQ8sVaPjpJyYFEEYh8HAXRXPFUEuj6kHQXs6ZE9F6a2wWrjdokAmSPP5HVP46bYxsrU8yr2FxxYmVBi6"}, + new []{"9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5", "22J7rH3DFJb1yz8JuWUWfrpsQrNsvZKov8sznfwHbPGTznSgQ8u6LQ6KixPC2mYCJDsfzME1FbdX1x89zKq4MU3K"}, + new []{"3F2RNf2f2kWYgJ2XsqcjzVeh3rsEQnwf6cawtBiJGyKV", "5954a6aMxVnPTyMNdVKrSiqoVMRvZcwU7swGp9kHsV9HP9Eu81TebS4Mbq5ZGmZwUaJkkKoCJ2eJSY9cTdWzRXeF"}, + new []{"GyWQGKYpvzFmjhnG5Pfw9jfvgDM7LB31HnTRPopCCS9", "tUV1EeY6CARAbuEfVqKS46X136PRBea8PcmYfHRWNQc6yYB14GkSBZ6PTybUt5W14A7FSJ6Mm6NN22fLhUhDUGu"}, + new []{"GjtWSPacUQFVShQKRKPc342MLCdNiusn3WTJQKxuDfXi", "iLtErFEn6w5xbsUW63QLYMTJeX8TAgFTUDTgat3gxpaiN3AJbebv6ybtmTj1t1yvkqqY2k1uwFxaKZoCQAPcDZe"}, + new []{"DjGCyxjGxpvEo921Ad4tUUWquiRG6dziJUCk8HKZoaKK", "3uvEiJiMyXqQmELLjxV8r3E7CyRFg42LUAxzz6q7fPhzTCxCzPkaMCQ9ARpWYDNiDXhue2Uma1C7KR9AkiiWUS8y"}, + new []{"HU6aKFapq4RssJqV96rfE7vv1pepz5A5miPAMxGFso4X", "4xFZDEhhw3oVewE3UCvzLmhRWjjcqvVMxuYiETWiyaV2wJwEJ4ceDDE359NMirh43VYisViHAwsXjZ3F9fk6dAxB"}, + new []{"HunD57AAvhBiX2SxmEDMbrgQ9pcqrtRyWKy7dWPEWYkJ", "2Z5CFuVDPQXxrB3iw5g6SAnKqApE1djAqtTZDA83rLZ1NDi6z13rwDX17qdyUDCxK9nDwKAHdVuy3h6jeXspcYxA"}, + new []{"9KmfMX4Ne5ocb8C7PwjmJTWTpQTQcPhkeD2zY35mawhq", "c1BzdtL4RByNQnzcaUq3WuNLuyY4tQogGT7JWwy4YGBE8FGSgWUH8eNJFyJgXNYtwTKq4emhC4V132QX9REwujm"}, + new []{"7MrtfwpJBw2hn4eopB2CVEKR1kePJV5kKmKX3wUAFsJ9", "4skUmBVmaLoriN9Ge8xcF4xQFJmF554rnRRa2u1yDbre2zj2wUpgCXUaPETLSAWNudCkNAkWM5oJFJRaeZY1g9JR"} + }; + + // mnemonic and passphrase to derive seed + var mnemonic = new Mnemonic("route clerk disease box emerge airport loud waste attitude film army tray forward deal onion eight catalog surface unit card window walnut wealth medal", Wordlist.English); + + // The passphrase isn't used to harden the mnemonic in this case. + var solletWallet = new Wallet.Wallet(mnemonic); + var flag = true; + + // Mimic sollet key generation + for (int i = 0; i < 10; i++) + { + var account = solletWallet.GetAccount(i); + + Console.WriteLine($"SOLLET publicKey>b58 {account.EncodedPublicKey}"); + Console.WriteLine($"SOLLET privateKey>b58 {account.EncodedPrivateKey}"); + + Debug.Assert(account.EncodedPublicKey == expectedSolletAddresses.get_Item(i)[0] && account.EncodedPrivateKey == expectedSolletAddresses.get_Item(i)[1]); + + if (account.EncodedPublicKey != expectedSolletAddresses.get_Item(i)[0] || + account.EncodedPrivateKey != expectedSolletAddresses.get_Item(i)[1]) + flag = false; + } + if (flag) + { + Console.WriteLine("GOOD FOR THE SOLLET"); + } + else + { + Console.WriteLine("NOT GOOD FOR THE SOLLET"); + } + } + } +} \ No newline at end of file diff --git a/src/Solnet.Examples/Solnet.Examples.csproj b/src/Solnet.Examples/Solnet.Examples.csproj index 838c7da9..aa2841b2 100644 --- a/src/Solnet.Examples/Solnet.Examples.csproj +++ b/src/Solnet.Examples/Solnet.Examples.csproj @@ -1,12 +1,21 @@ + Exe net5.0 Exe + + + + + + + + diff --git a/src/Solnet.Examples/SolnetRpcTester.cs b/src/Solnet.Examples/SolnetRpcTester.cs index 02a0ee6b..58f9d491 100644 --- a/src/Solnet.Examples/SolnetRpcTester.cs +++ b/src/Solnet.Examples/SolnetRpcTester.cs @@ -10,7 +10,7 @@ namespace Solnet.Examples class SolnetRpcTester { - static void Main(string[] args) + static void Example(string[] args) { SolanaJsonRpcClient c = new SolanaJsonRpcClient(); diff --git a/src/Solnet.KeyStore/Crypto/DecryptionException.cs b/src/Solnet.KeyStore/Crypto/DecryptionException.cs deleted file mode 100644 index 30c60d56..00000000 --- a/src/Solnet.KeyStore/Crypto/DecryptionException.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Solnet.KeyStore.Crypto -{ - /// - /// Specifies an exception thrown during decryption. - /// - public class DecryptionException : Exception - { - - /// - /// Initialize the exception. - /// - /// The message of the exception. - internal DecryptionException(string msg) : base(msg) - { - } - - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/Crypto/IRandomBytesGenerator.cs b/src/Solnet.KeyStore/Crypto/IRandomBytesGenerator.cs deleted file mode 100644 index 64e1badd..00000000 --- a/src/Solnet.KeyStore/Crypto/IRandomBytesGenerator.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Solnet.KeyStore.Crypto -{ - /// - /// Specifies functionality for a random bytes generator. - /// - public interface IRandomBytesGenerator - { - - /// - /// Generate a random initialization vector with 16 bytes. - /// - /// A byte array. - byte[] GenerateRandomInitialisationVector(); - - - /// - /// Generates a random salt with 32 bytes. - /// - /// A byte array. - byte[] GenerateRandomSalt(); - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/Crypto/KeyStoreCrypto.cs b/src/Solnet.KeyStore/Crypto/KeyStoreCrypto.cs deleted file mode 100644 index 79a70c61..00000000 --- a/src/Solnet.KeyStore/Crypto/KeyStoreCrypto.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Solnet.KeyStore.Crypto -{ - public class KeyStoreCrypto - { - - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/Crypto/RandomBytesGenerator.cs b/src/Solnet.KeyStore/Crypto/RandomBytesGenerator.cs deleted file mode 100644 index 823e7215..00000000 --- a/src/Solnet.KeyStore/Crypto/RandomBytesGenerator.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Org.BouncyCastle.Security; - -namespace Solnet.KeyStore.Crypto -{ - /// - /// Implements a random bytes generator based on the - /// - public class RandomBytesGenerator : IRandomBytesGenerator - { - /// - /// The generator. - /// - private static readonly SecureRandom Random = new SecureRandom(); - - /// - /// Generate a random initialization vector with 16 bytes. - /// - /// A byte array. - public byte[] GenerateRandomInitializationVector() - { - return GenerateRandomBytes(16); - } - - /// - /// Generates a random salt with 32 bytes. - /// - /// A byte array. - public byte[] GenerateRandomSalt() - { - return GenerateRandomBytes(32); - } - - /// - /// Generate a number of bytes. - /// - /// The number of bytes - /// A byte array. - public byte[] GenerateRandomBytes(int size) - { - var bytes = new byte[size]; - Random.NextBytes(bytes); - return bytes; - } - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/IKeyStore.cs b/src/Solnet.KeyStore/IKeyStore.cs new file mode 100644 index 00000000..d39a4b8b --- /dev/null +++ b/src/Solnet.KeyStore/IKeyStore.cs @@ -0,0 +1,38 @@ +using Solnet.Wallet; + +namespace Solnet.KeyStore +{ + /// + /// Specifies functionality for a key store service. + /// + public interface IKeyStore + { + /// + /// Restore the keypair from the keystore specified by path. + /// + /// The path to the keystore. + Account RestoreKeystore(string path); + + /// + /// TODO: Implement an encrypted keystore based on Web3 Secret Storage Definition. + /// + /// The path to the keystore. + Account DecryptAndRestoreKeystore(string path); + + + /// + /// Save the keypair to the keystore specified by path. + /// + /// The path to the keystore. + /// The account to save to the keystore. + void SaveKeystore(string path, Account account); + + + /// + /// TODO: Implement an encrypted keystore based on Web3 Secret Storage Definition. + /// + /// The path to the keystore. + /// The account to save to the keystore. + void EncryptAndSaveKeystore(string path, Account account); + } +} \ No newline at end of file diff --git a/src/Solnet.KeyStore/IKeyStoreService.cs b/src/Solnet.KeyStore/IKeyStoreService.cs deleted file mode 100644 index 0a310573..00000000 --- a/src/Solnet.KeyStore/IKeyStoreService.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Solnet.KeyStore.Model; - -namespace Solnet.KeyStore -{ - public interface IKeyStoreService where T : KdfParams - { - /// - /// - /// - /// - /// - /// - byte[] DecryptKeyStore(string password, KeyStore keyStore); - - /// - /// - /// - /// - /// - KeyStore DeserializeKeyStoreFromJson(string json); - - /// - /// - /// - /// - /// - /// - /// - KeyStore EncryptAndGenerateKeyStore(string password, byte[] privateKey, string address); - - /// - /// - /// - /// - /// - /// - /// - string EncryptAndGenerateKeyStoreAsJson(string password, byte[] privateKey, string addresss); - - /// - /// - /// - /// - string GetCipherType(); - - /// - /// - /// - /// - string GetKdfType(); - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/InvalidKdfException.cs b/src/Solnet.KeyStore/InvalidKdfException.cs deleted file mode 100644 index 82f364c8..00000000 --- a/src/Solnet.KeyStore/InvalidKdfException.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Solnet.KeyStore -{ - /// - /// Represents an exception which is used when the key derivation function is invalid. - /// - public class InvalidKdfException : Exception - { - - /// - /// Initializes the exception for the passed kdf. - /// - /// - public InvalidKdfException(string kdf) : base($"Invalid kdf: {kdf}") - { - } - - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/KdfType.cs b/src/Solnet.KeyStore/KdfType.cs deleted file mode 100644 index b72c5727..00000000 --- a/src/Solnet.KeyStore/KdfType.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Solnet.KeyStore -{ - /// - /// Specifies the different key derivation functions. - /// - public enum KdfType - { - /// - /// Represents the password-based key derivation function 2. - /// - Pbkdf2 - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/KeyStore.cs b/src/Solnet.KeyStore/KeyStore.cs new file mode 100644 index 00000000..8f5a6406 --- /dev/null +++ b/src/Solnet.KeyStore/KeyStore.cs @@ -0,0 +1,80 @@ +using Solnet.Wallet; +using Solnet.Wallet.Key; + +namespace Solnet.KeyStore +{ + /// + /// Implements the keystore abstraction for different types. + /// + public class KeyStore : IKeyStore + { + /// + /// The keystore type. + /// + public KeyStoreType KeyStoreType { get; set; } + + /// + /// The keystore object. + /// + private IKeyStore _keyStore { get; init; } + + /// + /// Initialize the keystore of the passed type. + /// + /// The keystore type. + public KeyStore(KeyStoreType keyStoreType) + { + KeyStoreType = keyStoreType; + + switch (keyStoreType) + { + case KeyStoreType.SolanaKeygen: + _keyStore = new SolanaKeyStore(); + break; + default: + _keyStore = new SolanaKeyStore(); + break; + } + } + + /// + /// Restore keypair from the keystore. + /// + /// The path of the keystore. + /// The keypair + public Account RestoreKeystore(string path) + { + return _keyStore.RestoreKeystore(path); + } + + /// + /// Decrypt and restore keypair from the keystore. + /// + /// The path of the keystore. + /// The keypair + public Account DecryptAndRestoreKeystore(string path) + { + return _keyStore.DecryptAndRestoreKeystore(path); + } + + /// + /// Save keypair to the keystore. + /// + /// The path of the keystore. + /// The keypair to save. + public void SaveKeystore(string path, Account account) + { + _keyStore.SaveKeystore(path, account); + } + + /// + /// Encrypt and save keypair to the keystore. + /// + /// The path of the keystore. + /// The keypair to save. + public void EncryptAndSaveKeystore(string path, Account account) + { + _keyStore.EncryptAndSaveKeystore(path, account); + } + } +} \ No newline at end of file diff --git a/src/Solnet.KeyStore/KeyStoreKdfChecker.cs b/src/Solnet.KeyStore/KeyStoreKdfChecker.cs deleted file mode 100644 index dee37567..00000000 --- a/src/Solnet.KeyStore/KeyStoreKdfChecker.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using Newtonsoft.Json.Linq; - -namespace Solnet.KeyStore -{ - /// - /// Implements functionality to check the keystore's key derivation function. - /// - public class KeyStoreKdfChecker - { - - public KdfType GetKeyStoreKdfType(string json) - { - try - { - var keyStoreDocument = JObject.Parse(json); - var kdf = keyStoreDocument.GetValue("crypto", StringComparison.OrdinalIgnoreCase)["kdf"] - .Value(); - - if (kdf == KeyStorePbkdf2Service.KdfType) - return KdfType.pbkdf2; - - if (kdf == KeyStoreScryptService.KdfType) - return KdfType.scrypt; - } - catch (Exception ex) - { - throw new Exception("Invalid KeyStore json", ex); - } - } - - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/KeyStorePbkdf2Service.cs b/src/Solnet.KeyStore/KeyStorePbkdf2Service.cs deleted file mode 100644 index 68d0da0e..00000000 --- a/src/Solnet.KeyStore/KeyStorePbkdf2Service.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Solnet.KeyStore -{ - public class KeyStorePbkdf2Service - { - - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/KeyStoreScryptService.cs b/src/Solnet.KeyStore/KeyStoreScryptService.cs deleted file mode 100644 index 5f4fa4f4..00000000 --- a/src/Solnet.KeyStore/KeyStoreScryptService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Solnet.KeyStore.Model; - -namespace Solnet.KeyStore -{ - public class KeyStoreScryptService : IKeyStoreService where T : KdfParams - { - - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/KeyStoreService.cs b/src/Solnet.KeyStore/KeyStoreService.cs deleted file mode 100644 index 3287e730..00000000 --- a/src/Solnet.KeyStore/KeyStoreService.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Solnet.KeyStore -{ - public class KeyStoreService - { - - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/KeyStoreServiceBase.cs b/src/Solnet.KeyStore/KeyStoreServiceBase.cs deleted file mode 100644 index 74489ca7..00000000 --- a/src/Solnet.KeyStore/KeyStoreServiceBase.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using Solnet.KeyStore.Crypto; -using Solnet.KeyStore.Model; - -namespace Solnet.KeyStore -{ - /// - /// Implements an abstract base class for the key store service. - /// - /// The KdfParams. - public abstract class KeyStoreServiceBase : IKeyStoreService where T: KdfParams - { - - public const int CurrentVersion = 3; - protected readonly KeyStoreCrypto KeyStoreCrypto; - protected readonly IRandomBytesGenerator RandomBytesGenerator; - - protected KeyStoreServiceBase() : this(new RandomBytesGenerator(), new KeyStoreCrypto()) - { - } - - protected KeyStoreServiceBase(IRandomBytesGenerator randomBytesGenerator, KeyStoreCrypto keyStoreCrypto) - { - RandomBytesGenerator = randomBytesGenerator; - KeyStoreCrypto = keyStoreCrypto; - } - - - protected KeyStoreServiceBase(IRandomBytesGenerator randomBytesGenerator) - { - RandomBytesGenerator = randomBytesGenerator; - KeyStoreCrypto = new KeyStoreCrypto(); - } - - /// - /// - /// - /// - /// - /// - /// - public byte[] DecryptKeyStore(string password, KeyStore keyStore) - { - throw new System.NotImplementedException(); - } - - /// - /// - /// - /// - /// - /// - public KeyStore DeserializeKeyStoreFromJson(string json) - { - throw new System.NotImplementedException(); - } - - /// - /// - /// - /// - /// - /// - /// - /// - public KeyStore EncryptAndGenerateKeyStore(string password, byte[] privateKey, string address) - { - throw new System.NotImplementedException(); - } - - /// - /// - /// - /// - /// - /// - /// - /// - public string EncryptAndGenerateKeyStoreAsJson(string password, byte[] privateKey, string addresss) - { - throw new System.NotImplementedException(); - } - - /// - /// - /// - /// - /// - public string GetCipherType() - { - throw new System.NotImplementedException(); - } - - /// - /// - /// - /// - /// - public string GetKdfType() - { - throw new System.NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/KeyStoreType.cs b/src/Solnet.KeyStore/KeyStoreType.cs new file mode 100644 index 00000000..4134edef --- /dev/null +++ b/src/Solnet.KeyStore/KeyStoreType.cs @@ -0,0 +1,13 @@ +namespace Solnet.KeyStore +{ + /// + /// Specifies the types of keystores. + /// + public enum KeyStoreType + { + /// + /// Keystore compatible with the original solana-keygen made in rust. + /// + SolanaKeygen, + } +} \ No newline at end of file diff --git a/src/Solnet.KeyStore/Model/CipherParams.cs b/src/Solnet.KeyStore/Model/CipherParams.cs deleted file mode 100644 index 42c42a6e..00000000 --- a/src/Solnet.KeyStore/Model/CipherParams.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Newtonsoft.Json; - -namespace Solnet.KeyStore.Model -{ - /// - /// - /// - public class CipherParams - { - /// - /// - /// - public CipherParams() - { - } - - /// - /// - /// - /// - public CipherParams(byte[] iv) - { - Iv = iv.ToHex(); - } - - /// - /// - /// - [JsonProperty("iv")] - public string Iv { get; set; } - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/Model/CryptoInfo.cs b/src/Solnet.KeyStore/Model/CryptoInfo.cs deleted file mode 100644 index f1e21ce5..00000000 --- a/src/Solnet.KeyStore/Model/CryptoInfo.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Newtonsoft.Json; - -namespace Solnet.KeyStore.Model -{ - /// - /// - /// - /// - public class CryptoInfo where TKdfParams : KdfParams - { - /// - /// - /// - public CryptoInfo() - { - } - - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public CryptoInfo(string cipher, byte[] cipherText, byte[] iv, byte[] mac, byte[] salt, TKdfParams kdfParams, - string kdfType) - { - Cipher = cipher; - CipherText = cipherText.ToHex(); - Mac = mac.ToHex(); - CipherParams = new CipherParams(iv); - Kdfparams = kdfParams; - Kdfparams.Salt = salt.ToHex(); - Kdf = kdfType; - } - - /// - /// - /// - [JsonProperty("cipher")] - public string Cipher { get; set; } - - /// - /// - /// - [JsonProperty("ciphertext")] - public string CipherText { get; set; } - - /// - /// - /// - [JsonProperty("cipherparams")] - public CipherParams CipherParams { get; set; } - - /// - /// - /// - [JsonProperty("kdf")] - public string Kdf { get; set; } - - /// - /// - /// - [JsonProperty("mac")] - public string Mac { get; set; } - - /// - /// - /// - [JsonProperty("kdfparams")] - public TKdfParams Kdfparams { get; set; } - - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/Model/KdfParams.cs b/src/Solnet.KeyStore/Model/KdfParams.cs deleted file mode 100644 index 71ab431f..00000000 --- a/src/Solnet.KeyStore/Model/KdfParams.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Newtonsoft.Json; - -namespace Solnet.KeyStore.Model -{ - /// - /// - /// - public class KdfParams - { - /// - /// - /// - [JsonProperty("dklen")] - public int Dklen { get; set; } - - /// - /// - /// - [JsonProperty("salt")] - public string Salt { get; set; } - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/Model/KeyStore.cs b/src/Solnet.KeyStore/Model/KeyStore.cs deleted file mode 100644 index ddf64f69..00000000 --- a/src/Solnet.KeyStore/Model/KeyStore.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Newtonsoft.Json; - -namespace Solnet.KeyStore.Model -{ - /// - /// - /// - /// - public class KeyStore where TKdfParams : KdfParams - { - /// - /// - /// - [JsonProperty("crypto")] - public CryptoInfo Crypto { get; set; } - - /// - /// - /// - [JsonProperty("id")] - public string Id { get; set; } - - /// - /// - /// - [JsonProperty("address")] - public string Address { get; set; } - - /// - /// - /// - [JsonProperty("version")] - public int Version { get; set; } - } -} \ No newline at end of file diff --git a/src/Solnet.KeyStore/SolanaKeyStore.cs b/src/Solnet.KeyStore/SolanaKeyStore.cs new file mode 100644 index 00000000..41300bc9 --- /dev/null +++ b/src/Solnet.KeyStore/SolanaKeyStore.cs @@ -0,0 +1,60 @@ +using System.IO; +using System.Text; +using Chaos.NaCl; +using Solnet.Util; +using Solnet.Wallet.Account; +using Solnet.Wallet.Key; + +namespace Solnet.KeyStore +{ + /// + /// Implements a keystore compatible with the solana-keygen made in rust. + /// + public class SolanaKeyStore : IKeyStore + { + /// + /// Restores a keypair from a keystore compatible with the solana-keygen made in rust. + /// + /// The path to the keystore + public Account RestoreKeystore(string path) + { + var inputBytes = File.ReadAllText(path).FromStringByteArray(); + + var acc = new Account(inputBytes, Ed25519.PublicKeyFromSeed(inputBytes[..32])); + + return acc; + } + + /// + /// NOT IMPLEMENTED. + /// + /// + /// + /// NOT IMPLEMENTED. + public Account DecryptAndRestoreKeystore(string path) + { + throw new System.NotImplementedException(); + } + + /// + /// Saves a keypair to a keystore compatible with the solana-keygen made in rust. + /// + /// The path to the keystore + /// The account to save to the keystore. + public void SaveKeystore(string path, Account account) + { + File.WriteAllBytes(path, Encoding.ASCII.GetBytes(account.PrivateKey.ToStringByteArray())); + } + + /// + /// NOT IMPLEMENTED. + /// + /// + /// The account to save to the keystore. + /// NOT IMPLEMENTED. + public void EncryptAndSaveKeystore(string path, Account account) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Solnet.KeyStore/Solnet.KeyStore.csproj b/src/Solnet.KeyStore/Solnet.KeyStore.csproj index 7e61d3a5..0f50f2ec 100644 --- a/src/Solnet.KeyStore/Solnet.KeyStore.csproj +++ b/src/Solnet.KeyStore/Solnet.KeyStore.csproj @@ -5,8 +5,14 @@ - + + + + + + + diff --git a/src/Solnet.Models/Account.cs b/src/Solnet.Models/Account.cs deleted file mode 100644 index 2a60667a..00000000 --- a/src/Solnet.Models/Account.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Numerics; - -namespace Solnet.Models -{ - /// - /// Specifies an within the Solana ecossystem. - /// - /// This account can be characterized by a pair of public and private keys. - /// - /// - public class Account - { - /// - /// - /// - public BigInteger Nonce { get; set; } - - /// - /// A scalar value equal to the amount of SOL owned by the . - /// - public BigInteger Balance { get; set; } - - /// - /// A byte-array that represents the 's address' public key. - /// - public byte[] PublicKey { get; set; } - - - /// - /// A byte-array that represents the 's address' private key. - /// - public byte[] PrivateKey { get; set; } - } -} \ No newline at end of file diff --git a/src/Solnet.Models/Solnet.Models.csproj b/src/Solnet.Models/Solnet.Models.csproj index cbfa5815..7a2d0e1d 100644 --- a/src/Solnet.Models/Solnet.Models.csproj +++ b/src/Solnet.Models/Solnet.Models.csproj @@ -4,4 +4,8 @@ net5.0 + + + + diff --git a/src/Solnet.Rpc/README.md b/src/Solnet.Rpc/README.md new file mode 100644 index 00000000..e69de29b diff --git a/src/Solnet.Signer/Solnet.Signer.csproj b/src/Solnet.Signer/Solnet.Signer.csproj deleted file mode 100644 index cbfa5815..00000000 --- a/src/Solnet.Signer/Solnet.Signer.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - net5.0 - - - diff --git a/src/Solnet.Util/BigEndianBuffer.cs b/src/Solnet.Util/BigEndianBuffer.cs new file mode 100644 index 00000000..41c1edc4 --- /dev/null +++ b/src/Solnet.Util/BigEndianBuffer.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; + +namespace Solnet.Util +{ + public class BigEndianBuffer + { + readonly List _bytes = new List(); + + public void WriteUInt(uint i) + { + _bytes.Add((byte)((i >> 0x18) & 0xff)); + _bytes.Add((byte)((i >> 0x10) & 0xff)); + _bytes.Add((byte)((i >> 8) & 0xff)); + _bytes.Add((byte)(i & 0xff)); + } + + public void Write(byte b) + { + _bytes.Add(b); + } + + public void Write(byte[] bytes) + { + Write(bytes, 0, bytes.Length); + } + + private void Write(byte[] bytes, int offset, int count) + { + var newBytes = new byte[count]; + Array.Copy(bytes, offset, newBytes, 0, count); + + _bytes.AddRange(newBytes); + } + + public byte[] ToArray() + { + return _bytes.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Solnet.Util/Extensions.cs b/src/Solnet.Util/Extensions.cs new file mode 100644 index 00000000..282c82cf --- /dev/null +++ b/src/Solnet.Util/Extensions.cs @@ -0,0 +1,58 @@ +using System; +using System.Linq; + +namespace Solnet.Util +{ + /// + /// Implements extensions to encode and decode addresses and keys. + /// + public static class Extensions + { + public static T[] Slice(this T[] source, int start, int end) + { + if (end < 0) + end = source.Length; + + var len = end - start; + + // Return new array. + var res = new T[len]; + for (var i = 0; i < len; i++) res[i] = source[i + start]; + return res; + } + + public static T[] Slice(this T[] source, int start) + { + return Slice(source, start, -1); + } + + /// + /// Formats a byte array into a string in order to be compatible with the original solana-keygen made in rust. + /// + /// The byte array to be formatted. + /// A formatted string. + public static string ToStringByteArray(this byte[] bytes) => "[" + string.Join(",", bytes) + "]"; + + /// + /// Formats a string into a byte array in order to be compatible with the original solana-keygen made in rust. + /// + /// The string to be formatted. + /// A formatted byte array. + public static byte[] FromStringByteArray(this string data) + { + var bytes = new byte[64]; + var index = 0; + var i = 0; + var newS = data.AsSpan(1, data.Length-1); + + while ((i = newS.IndexOf(',')) != -1) + { + bytes[index++] = byte.Parse(newS[..i]); + newS = newS[(i + 1)..]; + } + + bytes[index] = byte.Parse(newS[..^1]); + return bytes; + } + } +} \ No newline at end of file diff --git a/src/Solnet.Util/README.md b/src/Solnet.Util/README.md new file mode 100644 index 00000000..e69de29b diff --git a/src/Solnet.Hex/Solnet.Hex.csproj b/src/Solnet.Util/Solnet.Util.csproj similarity index 55% rename from src/Solnet.Hex/Solnet.Hex.csproj rename to src/Solnet.Util/Solnet.Util.csproj index 3eacfa19..0eee5f2d 100644 --- a/src/Solnet.Hex/Solnet.Hex.csproj +++ b/src/Solnet.Util/Solnet.Util.csproj @@ -5,8 +5,7 @@ - - + + - diff --git a/src/Solnet.Wallet/Account.cs b/src/Solnet.Wallet/Account.cs new file mode 100644 index 00000000..dfe1ec34 --- /dev/null +++ b/src/Solnet.Wallet/Account.cs @@ -0,0 +1,52 @@ +using NBitcoin.DataEncoders; +using Solnet.Wallet.Key; + +namespace Solnet.Wallet +{ + public class Account + { + /// + /// The base58 encoder instance. + /// + private static readonly Base58Encoder Encoder = new Base58Encoder(); + + /// + /// The key pair associated with this account. + /// + private KeyPair _keyPair; + + /// + /// Initialize an account with the passed private and public keys. + /// + /// The private key. + /// The public key. + public Account(byte[] privateKey, byte[] publicKey) + { + _keyPair = new KeyPair() + { + PrivateKey = privateKey, + PublicKey = publicKey + }; + } + + /// + /// Get the private key encoded as base58. + /// + public string EncodedPrivateKey => Encoder.EncodeData(_keyPair.PrivateKey); + + /// + /// Get the public key encoded as base58. + /// + public string EncodedPublicKey => Encoder.EncodeData(_keyPair.PublicKey); + + /// + /// Get the public key as a byte array. + /// + public byte[] PublicKey => _keyPair.PublicKey; + + /// + /// Get the private key as a byte array. + /// + public byte[] PrivateKey => _keyPair.PrivateKey; + } +} \ No newline at end of file diff --git a/src/Solnet.Wallet/Ed25519Bip32.cs b/src/Solnet.Wallet/Ed25519Bip32.cs new file mode 100644 index 00000000..d826afb8 --- /dev/null +++ b/src/Solnet.Wallet/Ed25519Bip32.cs @@ -0,0 +1,134 @@ +using System; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using Solnet.Util; + +namespace Solnet.Wallet +{ + /// + /// An implementation of Ed25519 based BIP32 key generation. + /// + public class Ed25519Bip32 + { + private readonly string curve = "ed25519 seed"; + private readonly uint hardenedOffset = 0x80000000; + + /// + /// The seed used for key generation. + /// + private byte[] _seed; + + /// + /// The computed master key. + /// + private byte[] _masterKey; + + /// + /// The computed chain code. + /// + private byte[] _chainCode; + + /// + /// Initialize the ed25519 based bip32 key generator with the passed seed. + /// + /// The seed. + public Ed25519Bip32(byte[] seed) + { + _seed = seed; + (_masterKey, _chainCode) = GetMasterKeyFromSeed(_seed); + } + + /// + /// Gets the master key used for key generation from the passed seed. + /// + /// + /// + public (byte[] Key, byte[] ChainCode) GetMasterKeyFromSeed(byte[] seed) + { + using (HMACSHA512 hmacSha512 = new HMACSHA512(Encoding.UTF8.GetBytes(curve))) + { + var i = hmacSha512.ComputeHash(seed); + + var il = i.Slice(0, 32); + var ir = i.Slice(32); + + return (Key: il, ChainCode: ir); + } + } + + /// + /// Computes the child key. + /// + /// + /// + /// + /// + private (byte[] Key, byte[] ChainCode) GetChildKeyDerivation(byte[] key, byte[] chainCode, uint index) + { + BigEndianBuffer buffer = new BigEndianBuffer(); + + buffer.Write(new byte[] { 0 }); + buffer.Write(key); + buffer.WriteUInt(index); + + using (HMACSHA512 hmacSha512 = new HMACSHA512(chainCode)) + { + var i = hmacSha512.ComputeHash(buffer.ToArray()); + + var il = i.Slice(0, 32); + var ir = i.Slice(32); + + return (Key: il, ChainCode: ir); + } + } + + /// + /// Checks if the derivation path is valid. + /// Returns true if the path is valid, otherwise false. + /// + /// The derivation path. + /// A boolean. + private bool IsValidPath(string path) + { + var regex = new Regex("^m(\\/[0-9]+')+$"); + + if (!regex.IsMatch(path)) + return false; + + var valid = !(path.Split('/') + .Slice(1) + .Select(a => a.Replace("'", "")) + .Any(a => !Int32.TryParse(a, out _))); + + return valid; + } + + /// + /// Derives a child key from the passed derivation path. + /// + /// The derivation path. + /// The key and chaincode. + /// Thrown when the passed derivation path is invalid. + public (byte[] Key, byte[] ChainCode) DerivePath(string path) + { + if (!IsValidPath(path)) + throw new FormatException("Invalid derivation path"); + + var segments = path + .Split('/') + .Slice(1) + .Select(a => a.Replace("'", "")) + .Select(a => Convert.ToUInt32(a, 10)); + + var results = segments + .Aggregate( + (_masterKey, _chainCode), + (masterKeyFromSeed, next) => + GetChildKeyDerivation(masterKeyFromSeed._masterKey, masterKeyFromSeed._chainCode, next + hardenedOffset)); + + return results; + } + } +} \ No newline at end of file diff --git a/src/Solnet.Wallet/Key/KeyPair.cs b/src/Solnet.Wallet/Key/KeyPair.cs new file mode 100644 index 00000000..017769d6 --- /dev/null +++ b/src/Solnet.Wallet/Key/KeyPair.cs @@ -0,0 +1,20 @@ +using NBitcoin.DataEncoders; + +namespace Solnet.Wallet.Key +{ + /// + /// An Ed25519 key pair. + /// + internal class KeyPair + { + /// + /// The private key. + /// + internal byte[] PrivateKey { get; init; } + + /// + /// The public key. + /// + internal byte[] PublicKey { get; init; } + } +} \ No newline at end of file diff --git a/src/Solnet.Wallet/SecureRandom.cs b/src/Solnet.Wallet/SecureRandom.cs deleted file mode 100644 index 6692f114..00000000 --- a/src/Solnet.Wallet/SecureRandom.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using NBitcoin; -using Org.BouncyCastle.Security; - -namespace Solnet.Wallet -{ - /// - /// - /// - public class Random : IRandom - { - /// - /// The secure random instance. - /// - public SecureRandom SecureRandom = new SecureRandom(); - - public Random(SecureRandom secureRandom = null) - { - if (secureRandom != null) - { - SecureRandom = secureRandom; - } - } - - public void GetBytes(byte[] output) - { - SecureRandom.NextBytes(output); - } - - public void GetBytes(Span output) - { - SecureRandom.NextBytes(output); - } - } -} \ No newline at end of file diff --git a/src/Solnet.Wallet/SeedMode.cs b/src/Solnet.Wallet/SeedMode.cs new file mode 100644 index 00000000..a21b4b98 --- /dev/null +++ b/src/Solnet.Wallet/SeedMode.cs @@ -0,0 +1,24 @@ +namespace Solnet.Wallet +{ + /// + /// Specifies the available seed modes for key generation. + /// + /// Available modes: + /// + /// + /// + /// + public enum SeedMode + { + /// + /// Generates Ed25519 based BIP32 keys. + /// This seed mode is compatible with the keys generated in the Sollet/SPL Token Wallet. + /// + Ed25519Bip32, + /// + /// Generates BIP39 keys. + /// This seed mode is compatible with the keys generated in the solana-keygen cli. + /// + Bip39 + } +} \ No newline at end of file diff --git a/src/Solnet.Wallet/Solnet.Wallet.csproj b/src/Solnet.Wallet/Solnet.Wallet.csproj index 4dfd2c9a..39d570af 100644 --- a/src/Solnet.Wallet/Solnet.Wallet.csproj +++ b/src/Solnet.Wallet/Solnet.Wallet.csproj @@ -1,12 +1,21 @@ + + Solnet.Wallet uses BIP32 and BIP39 to generate an HD tree of Solana compatible addresses from a randomly generated word seed. + net5.0 - + + + + + + + diff --git a/src/Solnet.Wallet/Wallet.cs b/src/Solnet.Wallet/Wallet.cs index d784086d..176e6084 100644 --- a/src/Solnet.Wallet/Wallet.cs +++ b/src/Solnet.Wallet/Wallet.cs @@ -1,7 +1,222 @@ +using System; +using Chaos.NaCl; +using NBitcoin; + namespace Solnet.Wallet { + /// + /// Represents a wallet. + /// public class Wallet { + /// + /// The derivation path. + /// + private const string DerivationPath = "m/44'/501'/x'/0'"; + + /// + /// The seed derived from the mnemonic and/or passphrase. + /// + private byte[] _seed; + /// + /// The seed mode used for key generation. + /// + private SeedMode _seedMode; + + /// + /// The method used for key generation. + /// + private Ed25519Bip32 _ed25519Bip32; + + /// + /// Initialize a wallet with the passed passphrase and seed mode. + /// See . + /// + /// The passphrase. + /// The seed mode. + public Wallet(string passphrase = "", SeedMode seedMode = SeedMode.Ed25519Bip32) + { + Mnemonic = new Mnemonic(Wordlist.English, WordCount.Twelve); + + _seedMode = seedMode; + _seed = InitializeSeed(Mnemonic, passphrase); + } + + /// + /// Initialize a wallet from passed word count and word list for the mnemonic and passphrase. + /// + /// + /// + /// The passphrase. + /// The seed generation mode. + public Wallet(WordCount wordCount, Wordlist wordlist, string passphrase = "", SeedMode seedMode = SeedMode.Ed25519Bip32) + { + Mnemonic = new Mnemonic(wordlist, wordCount); + + _seedMode = seedMode; + _seed = InitializeSeed(Mnemonic, passphrase); + } + + /// + /// Initialize a wallet from the passed mnemonic and passphrase. + /// + /// + /// The passphrase. + /// The seed generation mode. + public Wallet(Mnemonic mnemonic, string passphrase = "", SeedMode seedMode = SeedMode.Ed25519Bip32) + { + Mnemonic = mnemonic; + + _seedMode = seedMode; + _seed = InitializeSeed(mnemonic, passphrase); + } + + /// + /// Initializes a wallet from the passed account. + /// + /// The account holding the keypair. + public Wallet(Account account) + { + Account = account; + } + + /// + /// Verify the signed message. + /// + /// The signed message. + /// The signature of the message. + /// The index of the account used to verify the signed message. + /// + public bool Verify(byte[] message, byte[] signature, int accountIndex) + { + if (_seedMode != SeedMode.Ed25519Bip32) + throw new Exception("cannot verify bip39 signatures using ed25519 based bip32 keys"); + + var account = GetAccount(accountIndex); + + return Ed25519.Verify(signature, message, account.PublicKey); + } + + /// + /// Verify the signed message. + /// + /// The signed message. + /// The signature of the message. + /// + public bool Verify(byte[] message, byte[] signature) + { + if (_seedMode == SeedMode.Ed25519Bip32) + throw new Exception("cannot verify ed25519 based bip32 signatures using bip39 keys"); + + return Ed25519.Verify(signature, message, Account.PublicKey); + } + + /// + /// Sign the data. + /// + /// The data to sign. + /// The account used to sign the data. + /// The signature of the data. + public byte[] Sign(byte[] message, int accountIndex) + { + if (_seedMode != SeedMode.Ed25519Bip32) + throw new Exception("cannot compute bip39 signature using ed25519 based bip32 keys "); + + var account = GetAccount(accountIndex); + + var signature = new ArraySegment(); + Ed25519.Sign(signature, message, account.PrivateKey); + return signature.ToArray(); + } + + /// + /// Sign the data. + /// + /// The data to sign. + /// The signature of the data. + public byte[] Sign(byte[] message) + { + if (_seedMode == SeedMode.Ed25519Bip32) + throw new Exception("cannot compute ed25519 based bip32 signature using bip39 keys"); + + var signature = new ArraySegment(); + Ed25519.Sign(signature, message, Account.PrivateKey); + return signature.ToArray(); + } + + /// + /// Gets the account at the passed index using the ed25519 bip32 derivation path. + /// + /// The index of the account. + /// The account. + public Account GetAccount(int index) + { + var path = DerivationPath.Replace("x", index.ToString()); + var (account, chain) = _ed25519Bip32.DerivePath(path); + var (privateKey, publicKey) = EdKeyPairFromSeed(account); + return new(privateKey, publicKey); + } + + /// + /// Gets the corresponding ed25519 key pair from the passed seed. + /// + /// The seed + /// The key pair. + private static (byte[] privateKey, byte[] publicKey) EdKeyPairFromSeed(byte[] seed) => + new(Ed25519.ExpandedPrivateKeyFromSeed(seed), Ed25519.PublicKeyFromSeed(seed)); + + /// + /// Generate the keypair for the passed mnemonic and passphrase. + /// + /// The mnemonic + /// The passphrase. + /// The key pair. + private byte[] InitializeSeed(Mnemonic mnemonic, string passphrase = "") + { + var seed = DeriveSeed(mnemonic, passphrase); + + if (_seedMode == SeedMode.Ed25519Bip32) + { + _ed25519Bip32 = new Ed25519Bip32(seed); + Account = GetAccount(0); + } + else + { + var (privateKey, publicKey) = EdKeyPairFromSeed(seed[..32]); + Account = new Account(privateKey, publicKey); + } + + return seed; + } + + /// + /// Derive a seed from the passed mnemonic and passphrase. + /// + /// The mnemonic + /// The passphrase. + /// The seed. + private byte[] DeriveSeed(Mnemonic mnemonic, string passphrase = "") + { + switch (_seedMode) + { + case SeedMode.Ed25519Bip32: + return mnemonic.DeriveSeed(); + case SeedMode.Bip39: + return mnemonic.DeriveSeed(passphrase); + default: + return mnemonic.DeriveSeed(); + } + } + + /// + /// The key pair. + /// + public Account Account { get; private set; } + + /// + /// The mnemonic words. + /// + public Mnemonic Mnemonic { get; } } } \ No newline at end of file diff --git a/src/Solnet.Web3/Solnet.Web3.csproj b/src/Solnet.Web3/Solnet.Web3.csproj deleted file mode 100644 index cbfa5815..00000000 --- a/src/Solnet.Web3/Solnet.Web3.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - net5.0 - - -