diff --git a/neo.sln b/neo.sln index 11ee10a080..54e97affc4 100644 --- a/neo.sln +++ b/neo.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.2.32516.85 MinimumVisualStudioVersion = 10.0.40219.1 @@ -80,6 +80,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RpcClient", "src\Plugins\Rp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Plugins.ApplicationLogs.Tests", "tests\Neo.Plugins.ApplicationLogs.Tests\Neo.Plugins.ApplicationLogs.Tests.csproj", "{8C866DC8-2E55-4399-9563-2F47FD4602EC}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Plugins.DBFTPlugin.Tests", "tests\Neo.Plugins.DBFTPlugin.Tests\Neo.Plugins.DBFTPlugin.Tests.csproj", "{72997EAB-9B0C-4BC8-B797-955C219C2C97}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Extensions.Tests", "tests\Neo.Extensions.Tests\Neo.Extensions.Tests.csproj", "{77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Extensions.Benchmarks", "benchmarks\Neo.Extensions.Benchmarks\Neo.Extensions.Benchmarks.csproj", "{B6CB2559-10F9-41AC-8D58-364BFEF9688B}" @@ -240,6 +242,10 @@ Global {5F984D2B-793F-4683-B53A-80050E6E0286}.Debug|Any CPU.Build.0 = Debug|Any CPU {5F984D2B-793F-4683-B53A-80050E6E0286}.Release|Any CPU.ActiveCfg = Release|Any CPU {5F984D2B-793F-4683-B53A-80050E6E0286}.Release|Any CPU.Build.0 = Release|Any CPU + {72997EAB-9B0C-4BC8-B797-955C219C2C97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72997EAB-9B0C-4BC8-B797-955C219C2C97}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72997EAB-9B0C-4BC8-B797-955C219C2C97}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72997EAB-9B0C-4BC8-B797-955C219C2C97}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -283,6 +289,7 @@ Global {77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F} = {EDE05FA8-8E73-4924-BC63-DD117127EEE1} {B6CB2559-10F9-41AC-8D58-364BFEF9688B} = {C25EB0B0-0CAC-4CC1-8F36-F9229EFB99EC} {5F984D2B-793F-4683-B53A-80050E6E0286} = {C25EB0B0-0CAC-4CC1-8F36-F9229EFB99EC} + {72997EAB-9B0C-4BC8-B797-955C219C2C97} = {7F257712-D033-47FF-B439-9D4320D06599} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BCBA19D9-F868-4C6D-8061-A2B91E06E3EC} diff --git a/src/Neo/Wallets/SignException.cs b/src/Neo/Wallets/SignException.cs new file mode 100644 index 0000000000..20ab39f1d3 --- /dev/null +++ b/src/Neo/Wallets/SignException.cs @@ -0,0 +1,27 @@ +// Copyright (C) 2015-2025 The Neo Project. +// +// SignException.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.Wallets +{ + /// + /// The exception that is thrown when `Sign` fails. + /// + public class SignException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public SignException(string message) : base(message) { } + } +} diff --git a/src/Neo/Wallets/Wallet.cs b/src/Neo/Wallets/Wallet.cs index 917d0ec6af..031cf6adf7 100644 --- a/src/Neo/Wallets/Wallet.cs +++ b/src/Neo/Wallets/Wallet.cs @@ -594,7 +594,10 @@ private Transaction MakeTransaction(DataCache snapshot, ReadOnlyMemory scr /// Signs the in the specified with the wallet. /// /// The to be used. - /// if the signature is successfully added to the context; otherwise, . + /// + /// if any signature is successfully added to the context; + /// otherwise, . + /// public bool Sign(ContractParametersContext context) { if (context.Network != ProtocolSettings.Network) return false; @@ -657,6 +660,49 @@ public bool Sign(ContractParametersContext context) return fSuccess; } + /// + /// Signs the specified data with the corresponding private key of the specified public key. + /// + /// The data to sign. + /// The public key. + /// + /// Thrown when or is . + /// + /// + /// Thrown when no account is found for the given public key or no private key is found for the given public key. + /// + /// The signature + public byte[] Sign(byte[] signData, ECPoint publicKey) + { + if (signData is null) throw new ArgumentNullException(nameof(signData)); + if (publicKey is null) throw new ArgumentNullException(nameof(publicKey)); + + var account = GetAccount(publicKey); + if (account is null) + throw new SignException("No such account found"); + + var privateKey = account.GetKey()?.PrivateKey; + if (privateKey is null) + throw new SignException("No private key found for the given public key"); + + return Crypto.Sign(signData, privateKey); + } + + /// + /// Checks if the wallet contains the specified public key and the corresponding private key. + /// If the wallet has the public key but not the private key, it will return . + /// + /// The public key. + /// + /// if the wallet contains the specified public key and the corresponding private key; + /// otherwise, . + /// + public bool ContainsKeyPair(ECPoint publicKey) + { + var account = GetAccount(publicKey); + return account != null && account.HasKey; + } + /// /// Checks that the specified password is correct for the wallet. /// diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs index d15c56a649..4cb0b1bfe5 100644 --- a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs @@ -11,11 +11,11 @@ using Neo.Extensions; using Neo.Ledger; +using Neo.Network.P2P; using Neo.Network.P2P.Payloads; using Neo.Plugins.DBFTPlugin.Messages; using Neo.Plugins.DBFTPlugin.Types; using Neo.SmartContract; -using Neo.Wallets; using System; using System.Buffers.Binary; using System.Collections.Generic; @@ -37,10 +37,15 @@ public ExtensiblePayload MakeChangeView(ChangeViewReason reason) public ExtensiblePayload MakeCommit() { - return CommitPayloads[MyIndex] ?? (CommitPayloads[MyIndex] = MakeSignedPayload(new Commit + if (CommitPayloads[MyIndex] is not null) + return CommitPayloads[MyIndex]; + + var signData = EnsureHeader().GetSignData(dbftSettings.Network); + CommitPayloads[MyIndex] = MakeSignedPayload(new Commit { - Signature = EnsureHeader().Sign(keyPair, neoSystem.Settings.Network) - })); + Signature = _wallet.Sign(signData, _myPublicKey) + }); + return CommitPayloads[MyIndex]; } private ExtensiblePayload MakeSignedPayload(ConsensusMessage message) @@ -59,7 +64,7 @@ private void SignPayload(ExtensiblePayload payload) try { sc = new ContractParametersContext(neoSystem.StoreView, payload, dbftSettings.Network); - wallet.Sign(sc); + _wallet.Sign(sc); } catch (InvalidOperationException exception) { @@ -149,11 +154,22 @@ public ExtensiblePayload MakeRecoveryMessage() } return MakeSignedPayload(new RecoveryMessage { - ChangeViewMessages = LastChangeViewPayloads.Where(p => p != null).Select(p => GetChangeViewPayloadCompact(p)).Take(M).ToDictionary(p => p.ValidatorIndex), + ChangeViewMessages = LastChangeViewPayloads.Where(p => p != null) + .Select(p => GetChangeViewPayloadCompact(p)) + .Take(M) + .ToDictionary(p => p.ValidatorIndex), PrepareRequestMessage = prepareRequestMessage, // We only need a PreparationHash set if we don't have the PrepareRequest information. - PreparationHash = TransactionHashes == null ? PreparationPayloads.Where(p => p != null).GroupBy(p => GetMessage(p).PreparationHash, (k, g) => new { Hash = k, Count = g.Count() }).OrderByDescending(p => p.Count).Select(p => p.Hash).FirstOrDefault() : null, - PreparationMessages = PreparationPayloads.Where(p => p != null).Select(p => GetPreparationPayloadCompact(p)).ToDictionary(p => p.ValidatorIndex), + PreparationHash = TransactionHashes == null + ? PreparationPayloads.Where(p => p != null) + .GroupBy(p => GetMessage(p).PreparationHash, (k, g) => new { Hash = k, Count = g.Count() }) + .OrderByDescending(p => p.Count) + .Select(p => p.Hash) + .FirstOrDefault() + : null, + PreparationMessages = PreparationPayloads.Where(p => p != null) + .Select(p => GetPreparationPayloadCompact(p)) + .ToDictionary(p => p.ValidatorIndex), CommitMessages = CommitSent ? CommitPayloads.Where(p => p != null).Select(p => GetCommitPayloadCompact(p)).ToDictionary(p => p.ValidatorIndex) : new Dictionary() diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs index 9434fd010b..aaefb33b7b 100644 --- a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs @@ -55,11 +55,11 @@ public partial class ConsensusContext : IDisposable, ISerializable public TransactionVerificationContext VerificationContext = new(); public StoreCache Snapshot { get; private set; } - private KeyPair keyPair; + private ECPoint _myPublicKey; private int _witnessSize; private readonly NeoSystem neoSystem; private readonly Settings dbftSettings; - private readonly Wallet wallet; + private readonly Wallet _wallet; private readonly IStore store; private Dictionary cachedMessages; @@ -114,7 +114,7 @@ public bool ValidatorsChanged public ConsensusContext(NeoSystem neoSystem, Settings settings, Wallet wallet) { - this.wallet = wallet; + _wallet = wallet; this.neoSystem = neoSystem; dbftSettings = settings; @@ -245,13 +245,14 @@ public void Reset(byte viewNumber) LastSeenMessage[validator] = height; } } - keyPair = null; + + _myPublicKey = null; for (int i = 0; i < Validators.Length; i++) { - WalletAccount account = wallet?.GetAccount(Validators[i]); - if (account?.HasKey != true) continue; + // ContainsKeyPair may be called multiple times + if (!_wallet.ContainsKeyPair(Validators[i])) continue; MyIndex = i; - keyPair = account.GetKey(); + _myPublicKey = Validators[MyIndex]; break; } cachedMessages = new Dictionary(); diff --git a/tests/Neo.Plugins.DBFTPlugin.Tests/Neo.Plugins.DBFTPlugin.Tests.csproj b/tests/Neo.Plugins.DBFTPlugin.Tests/Neo.Plugins.DBFTPlugin.Tests.csproj new file mode 100644 index 0000000000..8fce097ebd --- /dev/null +++ b/tests/Neo.Plugins.DBFTPlugin.Tests/Neo.Plugins.DBFTPlugin.Tests.csproj @@ -0,0 +1,19 @@ + + + + true + Exe + net9.0 + + + + + + + + + + + + + diff --git a/tests/Neo.Plugins.DBFTPlugin.Tests/UT_ConsensusContext.cs b/tests/Neo.Plugins.DBFTPlugin.Tests/UT_ConsensusContext.cs new file mode 100644 index 0000000000..6ae6e283a1 --- /dev/null +++ b/tests/Neo.Plugins.DBFTPlugin.Tests/UT_ConsensusContext.cs @@ -0,0 +1,118 @@ +// Copyright (C) 2015-2025 The Neo Project. +// +// UT_ConsensusContext.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.Extensions.Configuration; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Network.P2P; +using Neo.Plugins.DBFTPlugin.Consensus; +using Neo.Plugins.DBFTPlugin.Messages; +using Neo.SmartContract.Native; +using Neo.UnitTests; +using Neo.UnitTests.Persistence; +using System; +using System.Collections.Generic; + +namespace Neo.Plugins.DBFTPlugin.Tests +{ + [TestClass] + public class UT_ConsensusContext + { + static readonly ProtocolSettings ProtocolSettings = ProtocolSettings.Default with + { + Network = 0x334F454Eu, + StandbyCommittee = + [ + // private key: [0] => 0x01 * 32, [1] => 0x02 * 32, [2] => 0x03 * 32, [3] => 0x04 * 32 + ECPoint.Parse("026ff03b949241ce1dadd43519e6960e0a85b41a69a05c328103aa2bce1594ca16", ECCurve.Secp256r1), + ECPoint.Parse("02550f471003f3df97c3df506ac797f6721fb1a1fb7b8f6f83d224498a65c88e24", ECCurve.Secp256r1), + ECPoint.Parse("02591ab771ebbcfd6d9cb9094d106528add1a69d44c2c1f627f089ec58b9c61adf", ECCurve.Secp256r1), + ECPoint.Parse("0273103ec30b3ccf57daae08e93534aef144a35940cf6bbba12a0cf7cbd5d65a64", ECCurve.Secp256r1), + ], + ValidatorsCount = 4, + SeedList = ["seed1.neo.org:10333"], + }; + + private static IConfigurationSection MockConfig() + { + return new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { + { "PluginConfiguration:IgnoreRecoveryLogs", "true" }, + { "PluginConfiguration:Network", "0x334F454E" }, + }) + .Build() + .GetSection("PluginConfiguration"); + } + + [TestMethod] + public void TestReset() + { + var config = MockConfig(); + var wallet = TestUtils.GenerateTestWallet("123"); + var system = new NeoSystem(ProtocolSettings, new TestMemoryStoreProvider(new())); + var context = new ConsensusContext(system, new Settings(config), wallet); + context.Reset(0); + Assert.AreEqual(-1, context.MyIndex); + + var validators = NativeContract.NEO.GetNextBlockValidators(system.GetSnapshotCache(), 4); + Assert.AreEqual(4, validators.Length); + + var privateKey = new byte[32]; + Array.Fill(privateKey, (byte)1); + wallet.CreateAccount(privateKey); + + context = new ConsensusContext(system, new Settings(config), wallet); + context.Reset(0); + Assert.AreEqual(2, context.MyIndex); + } + + [TestMethod] + public void TestMakeCommit() + { + var config = MockConfig(); + var wallet = TestUtils.GenerateTestWallet("123"); + var system = new NeoSystem(ProtocolSettings, new TestMemoryStoreProvider(new())); + + var privateKey = new byte[32]; + Array.Fill(privateKey, (byte)1); + wallet.CreateAccount(privateKey); + + var context = new ConsensusContext(system, new Settings(config), wallet); + context.Reset(0); + + context.Block = new() + { + Header = new() { PrevHash = UInt256.Zero, Index = 1, NextConsensus = UInt160.Zero }, + Transactions = [] + }; + context.TransactionHashes = []; + + var payload = context.MakeCommit(); + Assert.IsNotNull(payload); + Assert.IsTrue(ReferenceEquals(payload, context.MakeCommit())); + Assert.IsNotNull(payload.Witness); + + var data = context.CommitPayloads[context.MyIndex].Data; + var commit = new Commit(); + var reader = new MemoryReader(data); + ((ISerializable)commit).Deserialize(ref reader); + Assert.AreEqual(1u, commit.BlockIndex); + Assert.AreEqual(2, commit.ValidatorIndex); + Assert.AreEqual(0, commit.ViewNumber); + Assert.AreEqual(64, commit.Signature.Length); + + var signData = context.EnsureHeader().GetSignData(ProtocolSettings.Network); + Assert.IsTrue(Crypto.VerifySignature(signData, commit.Signature.Span, context.Validators[context.MyIndex])); + } + } +} diff --git a/tests/Neo.UnitTests/TestProtocolSettings.cs b/tests/Neo.UnitTests/TestProtocolSettings.cs index 2757e6f9b5..a12c4345ea 100644 --- a/tests/Neo.UnitTests/TestProtocolSettings.cs +++ b/tests/Neo.UnitTests/TestProtocolSettings.cs @@ -15,10 +15,9 @@ namespace Neo.UnitTests { public static class TestProtocolSettings { - public static readonly ProtocolSettings Default = new() + public static readonly ProtocolSettings Default = ProtocolSettings.Default with { Network = 0x334F454Eu, - AddressVersion = ProtocolSettings.Default.AddressVersion, StandbyCommittee = [ //Validators @@ -54,18 +53,11 @@ public static class TestProtocolSettings "seed4.neo.org:10333", "seed5.neo.org:10333" ], - MillisecondsPerBlock = ProtocolSettings.Default.MillisecondsPerBlock, - MaxTransactionsPerBlock = ProtocolSettings.Default.MaxTransactionsPerBlock, - MemoryPoolMaxTransactions = ProtocolSettings.Default.MemoryPoolMaxTransactions, - MaxTraceableBlocks = ProtocolSettings.Default.MaxTraceableBlocks, - InitialGasDistribution = ProtocolSettings.Default.InitialGasDistribution, - Hardforks = ProtocolSettings.Default.Hardforks }; - public static readonly ProtocolSettings SoleNode = new() + public static readonly ProtocolSettings SoleNode = ProtocolSettings.Default with { Network = 0x334F454Eu, - AddressVersion = ProtocolSettings.Default.AddressVersion, StandbyCommittee = [ //Validators @@ -80,12 +72,6 @@ public static class TestProtocolSettings "seed4.neo.org:10333", "seed5.neo.org:10333" ], - MillisecondsPerBlock = ProtocolSettings.Default.MillisecondsPerBlock, - MaxTransactionsPerBlock = ProtocolSettings.Default.MaxTransactionsPerBlock, - MemoryPoolMaxTransactions = ProtocolSettings.Default.MemoryPoolMaxTransactions, - MaxTraceableBlocks = ProtocolSettings.Default.MaxTraceableBlocks, - InitialGasDistribution = ProtocolSettings.Default.InitialGasDistribution, - Hardforks = ProtocolSettings.Default.Hardforks }; } } diff --git a/tests/Neo.UnitTests/Wallets/UT_Wallet.cs b/tests/Neo.UnitTests/Wallets/UT_Wallet.cs index 84fbb4cf6c..59aefc8264 100644 --- a/tests/Neo.UnitTests/Wallets/UT_Wallet.cs +++ b/tests/Neo.UnitTests/Wallets/UT_Wallet.cs @@ -10,7 +10,9 @@ // modifications are permitted. using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.SmartContract.Native; @@ -82,9 +84,7 @@ public override WalletAccount CreateAccount(UInt160 scriptHash) return account; } - public override void Delete() - { - } + public override void Delete() { } public override bool DeleteAccount(UInt160 scriptHash) { @@ -107,9 +107,7 @@ public override bool VerifyPassword(string password) return true; } - public override void Save() - { - } + public override void Save() { } } [TestClass] @@ -150,7 +148,7 @@ public void TestCreateAccount1() public void TestCreateAccount2() { MyWallet wallet = new(); - Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); + Contract contract = Contract.Create([ContractParameterType.Boolean], [1]); WalletAccount account = wallet.CreateAccount(contract, UT_Crypto.GenerateCertainKey(32).PrivateKey); Assert.IsNotNull(account); @@ -163,7 +161,7 @@ public void TestCreateAccount2() public void TestCreateAccount3() { MyWallet wallet = new(); - Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); + Contract contract = Contract.Create([ContractParameterType.Boolean], [1]); Assert.IsNotNull(wallet.CreateAccount(contract, glkey)); } @@ -230,7 +228,7 @@ public void TestGetAccounts() public void TestGetAvailable() { MyWallet wallet = new(); - Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); + Contract contract = Contract.Create([ContractParameterType.Boolean], [1]); WalletAccount account = wallet.CreateAccount(contract, glkey.PrivateKey); account.Lock = false; @@ -250,7 +248,7 @@ public void TestGetAvailable() public void TestGetBalance() { MyWallet wallet = new(); - Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); + Contract contract = Contract.Create([ContractParameterType.Boolean], [1]); WalletAccount account = wallet.CreateAccount(contract, glkey.PrivateKey); account.Lock = false; @@ -260,8 +258,10 @@ public void TestGetBalance() var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - Assert.AreEqual(new BigDecimal(BigInteger.Zero, 0), wallet.GetBalance(snapshotCache, UInt160.Zero, new UInt160[] { account.ScriptHash })); - Assert.AreEqual(new BigDecimal(new BigInteger(1000000000000M), 8), wallet.GetBalance(snapshotCache, NativeContract.GAS.Hash, new UInt160[] { account.ScriptHash })); + Assert.AreEqual(new BigDecimal(BigInteger.Zero, 0), + wallet.GetBalance(snapshotCache, UInt160.Zero, [account.ScriptHash])); + Assert.AreEqual(new BigDecimal(new BigInteger(1000000000000M), 8), + wallet.GetBalance(snapshotCache, NativeContract.GAS.Hash, [account.ScriptHash])); entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 0; @@ -270,13 +270,15 @@ public void TestGetBalance() [TestMethod] public void TestGetPrivateKeyFromNEP2() { - Action action = () => Wallet.GetPrivateKeyFromNEP2("3vQB7B6MrGQZaxCuFg4oh", "TestGetPrivateKeyFromNEP2", ProtocolSettings.Default.AddressVersion, 2, 1, 1); + Action action = () => Wallet.GetPrivateKeyFromNEP2("3vQB7B6MrGQZaxCuFg4oh", "TestGetPrivateKeyFromNEP2", + ProtocolSettings.Default.AddressVersion, 2, 1, 1); Assert.ThrowsExactly(action); action = () => Wallet.GetPrivateKeyFromNEP2(nep2Key, "Test", ProtocolSettings.Default.AddressVersion, 2, 1, 1); Assert.ThrowsExactly(action); - CollectionAssert.AreEqual(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }, Wallet.GetPrivateKeyFromNEP2(nep2Key, "pwd", ProtocolSettings.Default.AddressVersion, 2, 1, 1)); + CollectionAssert.AreEqual("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f".HexToBytes(), + Wallet.GetPrivateKeyFromNEP2(nep2Key, "pwd", ProtocolSettings.Default.AddressVersion, 2, 1, 1)); } [TestMethod] @@ -288,7 +290,8 @@ public void TestGetPrivateKeyFromWIF() action = () => Wallet.GetPrivateKeyFromWIF("3vQB7B6MrGQZaxCuFg4oh"); Assert.ThrowsExactly(action); - CollectionAssert.AreEqual(new byte[] { 199, 19, 77, 111, 216, 231, 61, 129, 158, 130, 117, 92, 100, 201, 55, 136, 216, 219, 9, 97, 146, 158, 2, 90, 83, 54, 60, 76, 192, 42, 105, 98 }, Wallet.GetPrivateKeyFromWIF("L3tgppXLgdaeqSGSFw1Go3skBiy8vQAM7YMXvTHsKQtE16PBncSU")); + CollectionAssert.AreEqual("c7134d6fd8e73d819e82755c64c93788d8db0961929e025a53363c4cc02a6962".HexToBytes(), + Wallet.GetPrivateKeyFromWIF("L3tgppXLgdaeqSGSFw1Go3skBiy8vQAM7YMXvTHsKQtE16PBncSU")); } [TestMethod] @@ -310,44 +313,41 @@ public void TestMakeTransaction1() { var snapshotCache = TestBlockchain.GetTestSnapshotCache(); MyWallet wallet = new(); - Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); + Contract contract = Contract.Create([ContractParameterType.Boolean], [1]); WalletAccount account = wallet.CreateAccount(contract, glkey.PrivateKey); account.Lock = false; - Action action = () => wallet.MakeTransaction(snapshotCache, new TransferOutput[] - { - new TransferOutput() + Action action = () => wallet.MakeTransaction(snapshotCache, [ + new() { - AssetId = NativeContract.GAS.Hash, - ScriptHash = account.ScriptHash, - Value = new BigDecimal(BigInteger.One,8), - Data = "Dec 12th" + AssetId = NativeContract.GAS.Hash, + ScriptHash = account.ScriptHash, + Value = new BigDecimal(BigInteger.One, 8), + Data = "Dec 12th" } - }, UInt160.Zero); + ], UInt160.Zero); Assert.ThrowsExactly(action); - action = () => wallet.MakeTransaction(snapshotCache, new TransferOutput[] - { - new TransferOutput() + action = () => wallet.MakeTransaction(snapshotCache, [ + new() { - AssetId = NativeContract.GAS.Hash, - ScriptHash = account.ScriptHash, - Value = new BigDecimal(BigInteger.One,8), - Data = "Dec 12th" + AssetId = NativeContract.GAS.Hash, + ScriptHash = account.ScriptHash, + Value = new BigDecimal(BigInteger.One, 8), + Data = "Dec 12th" } - }, account.ScriptHash); + ], account.ScriptHash); Assert.ThrowsExactly(action); - action = () => wallet.MakeTransaction(snapshotCache, new TransferOutput[] - { - new TransferOutput() + action = () => wallet.MakeTransaction(snapshotCache, [ + new() { AssetId = UInt160.Zero, ScriptHash = account.ScriptHash, Value = new BigDecimal(BigInteger.One,8), Data = "Dec 12th" } - }, account.ScriptHash); + ], account.ScriptHash); Assert.ThrowsExactly(action); // Fake balance @@ -359,27 +359,25 @@ public void TestMakeTransaction1() var entry2 = snapshotCache.GetAndChange(key, () => new StorageItem(new NeoToken.NeoAccountState())); entry2.GetInteroperable().Balance = 10000 * NativeContract.NEO.Factor; - var tx = wallet.MakeTransaction(snapshotCache, new TransferOutput[] - { - new TransferOutput() + var tx = wallet.MakeTransaction(snapshotCache, [ + new() { AssetId = NativeContract.GAS.Hash, ScriptHash = account.ScriptHash, Value = new BigDecimal(BigInteger.One,8) } - }); + ]); Assert.IsNotNull(tx); - tx = wallet.MakeTransaction(snapshotCache, new TransferOutput[] - { - new TransferOutput() + tx = wallet.MakeTransaction(snapshotCache, [ + new() { AssetId = NativeContract.NEO.Hash, ScriptHash = account.ScriptHash, Value = new BigDecimal(BigInteger.One,8), Data = "Dec 12th" } - }); + ]); Assert.IsNotNull(tx); entry1 = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); @@ -393,10 +391,10 @@ public void TestMakeTransaction2() { var snapshotCache = TestBlockchain.GetTestSnapshotCache(); MyWallet wallet = new(); - Action action = () => wallet.MakeTransaction(snapshotCache, Array.Empty(), null, null, Array.Empty()); + Action action = () => wallet.MakeTransaction(snapshotCache, Array.Empty(), null, null, []); Assert.ThrowsExactly(action); - Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); + Contract contract = Contract.Create([ContractParameterType.Boolean], [1]); WalletAccount account = wallet.CreateAccount(contract, glkey.PrivateKey); account.Lock = false; @@ -405,15 +403,17 @@ public void TestMakeTransaction2() var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 1000000 * NativeContract.GAS.Factor; - var tx = wallet.MakeTransaction(snapshotCache, Array.Empty(), account.ScriptHash, new[]{ new Signer() - { - Account = account.ScriptHash, - Scopes = WitnessScope.CalledByEntry - }}, Array.Empty()); + var tx = wallet.MakeTransaction(snapshotCache, Array.Empty(), account.ScriptHash, [ + new() + { + Account = account.ScriptHash, + Scopes = WitnessScope.CalledByEntry + } + ], []); Assert.IsNotNull(tx); - tx = wallet.MakeTransaction(snapshotCache, Array.Empty(), null, null, Array.Empty()); + tx = wallet.MakeTransaction(snapshotCache, Array.Empty(), null, null, []); Assert.IsNotNull(tx); entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); @@ -433,5 +433,70 @@ public void TestVerifyPassword() Assert.Fail(); } } + + [TestMethod] + public void TestSign() + { + MyWallet wallet = new(); + Action action = () => wallet.Sign([0xa, 0xb, 0xc, 0xd], glkey.PublicKey); + Assert.ThrowsExactly(action); // no account + + wallet.CreateAccount(glkey.PrivateKey); + + var signature = wallet.Sign([0xa, 0xb, 0xc, 0xd], glkey.PublicKey); + Assert.IsNotNull(signature); + Assert.AreEqual(signature.Length, 64); + + var isValid = Crypto.VerifySignature([0xa, 0xb, 0xc, 0xd], signature, glkey.PublicKey); + Assert.IsTrue(isValid); + + var key = new byte[32]; + Array.Fill(key, (byte)0x02); + + var pair = new KeyPair(key); + var scriptHash = Contract.CreateSignatureRedeemScript(pair.PublicKey).ToScriptHash(); + wallet.CreateAccount(scriptHash); + Assert.IsNotNull(pair.PublicKey); + + action = () => wallet.Sign([0xa, 0xb, 0xc, 0xd], pair.PublicKey); + Assert.ThrowsExactly(action); // no private key + } + + [TestMethod] + public void TestContainsKeyPair() + { + MyWallet wallet = new(); + var contains = wallet.ContainsKeyPair(glkey.PublicKey); + Assert.IsFalse(contains); + + wallet.CreateAccount(glkey.PrivateKey); + + contains = wallet.ContainsKeyPair(glkey.PublicKey); + Assert.IsTrue(contains); + + var key = new byte[32]; + Array.Fill(key, (byte)0x01); + + var pair = new KeyPair(key); + contains = wallet.ContainsKeyPair(pair.PublicKey); + Assert.IsFalse(contains); + + wallet.CreateAccount(pair.PrivateKey); + contains = wallet.ContainsKeyPair(pair.PublicKey); + Assert.IsTrue(contains); + + contains = wallet.ContainsKeyPair(glkey.PublicKey); + Assert.IsTrue(contains); + + key = new byte[32]; + Array.Fill(key, (byte)0x02); + + pair = new KeyPair(key); + var scriptHash = Contract.CreateSignatureRedeemScript(pair.PublicKey).ToScriptHash(); + wallet.CreateAccount(scriptHash); + + contains = wallet.ContainsKeyPair(pair.PublicKey); + Assert.IsFalse(contains); // no private key + } } }