diff --git a/src/Lachain.Consensus/IPrivateConsensusKeySet.cs b/src/Lachain.Consensus/IPrivateConsensusKeySet.cs index 0e0b2df01..aa567851e 100644 --- a/src/Lachain.Consensus/IPrivateConsensusKeySet.cs +++ b/src/Lachain.Consensus/IPrivateConsensusKeySet.cs @@ -4,7 +4,6 @@ namespace Lachain.Consensus { public interface IPrivateConsensusKeySet { - Crypto.TPKE.PrivateKey TpkePrivateKey { get; } Crypto.ThresholdSignature.PrivateKeyShare ThresholdSignaturePrivateKeyShare { get; } EcdsaKeyPair EcdsaKeyPair { get; } } diff --git a/src/Lachain.Consensus/IPublicConsensusKeySet.cs b/src/Lachain.Consensus/IPublicConsensusKeySet.cs index efdebb3e0..875e3a6d6 100644 --- a/src/Lachain.Consensus/IPublicConsensusKeySet.cs +++ b/src/Lachain.Consensus/IPublicConsensusKeySet.cs @@ -9,10 +9,8 @@ public interface IPublicConsensusKeySet { int N { get; } int F { get; } - PublicKey TpkePublicKey { get; } PublicKeySet ThresholdSignaturePublicKeySet { get; } IList EcdsaPublicKeySet { get; } public int GetValidatorIndex(ECDSAPublicKey publicKey); - public PublicKey? GetTpkeVerificationKey(int playerIdx); } } \ No newline at end of file diff --git a/src/Lachain.Consensus/PrivateConsensusKeySet.cs b/src/Lachain.Consensus/PrivateConsensusKeySet.cs index 2184a3819..07b432fee 100644 --- a/src/Lachain.Consensus/PrivateConsensusKeySet.cs +++ b/src/Lachain.Consensus/PrivateConsensusKeySet.cs @@ -11,11 +11,9 @@ public PrivateConsensusKeySet( PrivateKeyShare thresholdSignaturePrivateKeyShare) { EcdsaKeyPair = ecdsaKeyPair; - TpkePrivateKey = tpkePrivateKey; ThresholdSignaturePrivateKeyShare = thresholdSignaturePrivateKeyShare; } - public PrivateKey TpkePrivateKey { get; } public PrivateKeyShare ThresholdSignaturePrivateKeyShare { get; } public EcdsaKeyPair EcdsaKeyPair { get; } } diff --git a/src/Lachain.Consensus/PublicConsensusKeySet.cs b/src/Lachain.Consensus/PublicConsensusKeySet.cs index 7d4e97651..f7d1908be 100644 --- a/src/Lachain.Consensus/PublicConsensusKeySet.cs +++ b/src/Lachain.Consensus/PublicConsensusKeySet.cs @@ -11,26 +11,20 @@ public class PublicConsensusKeySet : IPublicConsensusKeySet { public int N { get; } public int F { get; } - public PublicKey TpkePublicKey { get; } public PublicKeySet ThresholdSignaturePublicKeySet { get; } private readonly List _ecdsaPublicKeys; public IList EcdsaPublicKeySet => _ecdsaPublicKeys; - private List _tpkeVerificationKeys; public PublicConsensusKeySet(int n, int f, - PublicKey tpkePublicKey, - IEnumerable tpkeVerificationKeys, PublicKeySet thresholdSignaturePublicKeySet, IEnumerable ecdsaPublicKeys ) { N = n; F = f; - TpkePublicKey = tpkePublicKey; ThresholdSignaturePublicKeySet = thresholdSignaturePublicKeySet; _ecdsaPublicKeys = ecdsaPublicKeys.ToList(); - _tpkeVerificationKeys = tpkeVerificationKeys.ToList(); } public int GetValidatorIndex(ECDSAPublicKey publicKey) @@ -43,16 +37,5 @@ public int GetValidatorIndex(ECDSAPublicKey publicKey) .First(); } - public PublicKey? GetTpkeVerificationKey(int playerIdx) - { - try - { - return _tpkeVerificationKeys[playerIdx]; - } - catch (Exception e) - { - return null; - } - } } } \ No newline at end of file diff --git a/src/Lachain.Consensus/ThresholdKeygen/Data/ThresholdKeyring.cs b/src/Lachain.Consensus/ThresholdKeygen/Data/ThresholdKeyring.cs index e30f25131..fc92331e1 100644 --- a/src/Lachain.Consensus/ThresholdKeygen/Data/ThresholdKeyring.cs +++ b/src/Lachain.Consensus/ThresholdKeygen/Data/ThresholdKeyring.cs @@ -8,15 +8,12 @@ namespace Lachain.Consensus.ThresholdKeygen.Data { public struct ThresholdKeyring { - public Crypto.TPKE.PrivateKey TpkePrivateKey; - public Crypto.TPKE.PublicKey TpkePublicKey; - public List TpkeVerificationPublicKeys; public Crypto.ThresholdSignature.PublicKeySet ThresholdSignaturePublicKeySet; public Crypto.ThresholdSignature.PrivateKeyShare ThresholdSignaturePrivateKey; public UInt256 PublicPartHash() { - return TpkePublicKey.ToBytes().Concat(ThresholdSignaturePublicKeySet.ToBytes()).Keccak(); + return ThresholdSignaturePublicKeySet.ToBytes().Keccak(); } } } \ No newline at end of file diff --git a/src/Lachain.Consensus/ThresholdKeygen/TrustlessKeygen.cs b/src/Lachain.Consensus/ThresholdKeygen/TrustlessKeygen.cs index 4c58723fd..f28a6bc72 100644 --- a/src/Lachain.Consensus/ThresholdKeygen/TrustlessKeygen.cs +++ b/src/Lachain.Consensus/ThresholdKeygen/TrustlessKeygen.cs @@ -142,6 +142,14 @@ public bool HandleConfirm(PublicKey tpkeKey, PublicKeySet tsKeys) return _confirmations[keyringHash] == Players - Faulty; } + public bool HandleConfirm(PublicKeySet tsKeys) + { + var keyringHash = tsKeys.ToBytes().Keccak(); + _confirmations.PutIfAbsent(keyringHash, 0); + _confirmations[keyringHash] += 1; + return _confirmations[keyringHash] == Players - Faulty; + } + public bool Finished() { return _keyGenStates.Count(s => s.ValueCount() > 2 * Faulty) > Faulty; @@ -170,9 +178,6 @@ public bool Finished() return new ThresholdKeyring { - TpkePrivateKey = new PrivateKey(secretKey, _myIdx), - TpkePublicKey = new PublicKey(pubKeys[0], Faulty), - TpkeVerificationPublicKeys = new List(pubKeys.Skip(1).Select(x => new PublicKey(x, Faulty))), ThresholdSignaturePrivateKey = new PrivateKeyShare(secretKey), ThresholdSignaturePublicKeySet = new PublicKeySet(pubKeys.Skip(1).Select(x => new Crypto.ThresholdSignature.PublicKey(x)), Faulty) diff --git a/src/Lachain.Console/TrustedKeygen.cs b/src/Lachain.Console/TrustedKeygen.cs index 670386e55..7986cd52a 100644 --- a/src/Lachain.Console/TrustedKeygen.cs +++ b/src/Lachain.Console/TrustedKeygen.cs @@ -198,7 +198,6 @@ public static void CloudKeygen(int n, int f, IEnumerable ips, ushort bas $"wallet{i + 1:D2}.json", ecdsaPrivateKeys[i], serializedHubPrivateKeys[i], - tpkeKeyGen.GetPrivKey(i).ToHex(), privShares[i].ToHex(), vault.Password ); @@ -363,7 +362,6 @@ public static void LocalKeygen(int n, int f, int basePort, ushort target, ulong $"wallet{i + 1:D2}.json", ecdsaPrivateKeys[i], serializedHubPrivateKeys[i], - tpkeKeyGen.GetPrivKey(i).ToHex(), privShares[i].ToHex(), vault.Password ); @@ -393,12 +391,11 @@ public static void LocalKeygen(int n, int f, int basePort, ushort target, ulong ); } - private static void GenWallet(string path, string ecdsaKey, string hubKey, string tpkeKey, string tsKey, string password) + private static void GenWallet(string path, string ecdsaKey, string hubKey, string tsKey, string password) { - var config = new JsonWallet( + var config = new NewJsonWallet( ecdsaKey, hubKey, - new Dictionary { { 0, tpkeKey } }, new Dictionary { { 0, tsKey } } ); var json = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(config)); diff --git a/src/Lachain.Core/Blockchain/Hardfork/HardforkConfig.cs b/src/Lachain.Core/Blockchain/Hardfork/HardforkConfig.cs index 6df367843..a8fcd8fdb 100644 --- a/src/Lachain.Core/Blockchain/Hardfork/HardforkConfig.cs +++ b/src/Lachain.Core/Blockchain/Hardfork/HardforkConfig.cs @@ -18,5 +18,6 @@ public class HardforkConfig [JsonProperty("hardfork_12")] public ulong? Hardfork_12 { get; set; } [JsonProperty("hardfork_13")] public ulong? Hardfork_13 { get; set; } [JsonProperty("hardfork_14")] public ulong? Hardfork_14 { get; set; } + [JsonProperty("hardfork_15")] public ulong? Hardfork_15 { get; set; } } } \ No newline at end of file diff --git a/src/Lachain.Core/Blockchain/Hardfork/HardforkHeights.cs b/src/Lachain.Core/Blockchain/Hardfork/HardforkHeights.cs index ed408acd4..dca921ab8 100644 --- a/src/Lachain.Core/Blockchain/Hardfork/HardforkHeights.cs +++ b/src/Lachain.Core/Blockchain/Hardfork/HardforkHeights.cs @@ -19,6 +19,7 @@ public static class HardforkHeights private static ulong Hardfork_12; private static ulong Hardfork_13; private static ulong Hardfork_14; + private static ulong Hardfork_15; //we need this value as default deploy height public static ulong GetHardfork_3() @@ -96,6 +97,11 @@ public static bool IsHardfork_14Active(ulong height) { return height >= Hardfork_14; } + + public static bool IsHardfork_15Active(ulong height) + { + return height >= Hardfork_15; + } public static void SetHardforkHeights(HardforkConfig hardforkConfig) { @@ -159,6 +165,10 @@ public static void SetHardforkHeights(HardforkConfig hardforkConfig) if(hardforkConfig.Hardfork_14 is null) throw new Exception("hardfork_14 is null"); Hardfork_14 = (ulong) hardforkConfig.Hardfork_14; + + if(hardforkConfig.Hardfork_15 is null) + throw new Exception("hardfork_15 is null"); + Hardfork_15 = (ulong) hardforkConfig.Hardfork_15; } } } diff --git a/src/Lachain.Core/Blockchain/Operations/BlockManager.cs b/src/Lachain.Core/Blockchain/Operations/BlockManager.cs index dd4fc0bf9..a6ef48ea3 100644 --- a/src/Lachain.Core/Blockchain/Operations/BlockManager.cs +++ b/src/Lachain.Core/Blockchain/Operations/BlockManager.cs @@ -762,7 +762,7 @@ public bool TryBuildGenesisBlock() v.ThresholdSignaturePublicKey.HexToBytes() )).ToArray() ); - snapshot.Validators.SetConsensusState(initialConsensusState, false); + snapshot.Validators.SetConsensusState(initialConsensusState, ConsensusStateStatus.OldState); // stake delegation happens even before genesis block // stake delegation means - some other address stakes for the validators diff --git a/src/Lachain.Core/Blockchain/SystemContracts/GovernanceContract.cs b/src/Lachain.Core/Blockchain/SystemContracts/GovernanceContract.cs index 0eebf2bd3..abe77825a 100644 --- a/src/Lachain.Core/Blockchain/SystemContracts/GovernanceContract.cs +++ b/src/Lachain.Core/Blockchain/SystemContracts/GovernanceContract.cs @@ -380,6 +380,67 @@ public ExecutionStatus KeyGenConfirmWithVerification(UInt256 cycle, byte[] tpkeP return ExecutionStatus.Ok; } + [ContractMethod(GovernanceInterface.MethodKeygenConfirmWithOnlyTS)] + public ExecutionStatus KeygenConfirmWithOnlyTS(UInt256 cycle, byte[][] thresholdSignaturePublicKeys, SystemContractExecutionFrame frame) + { + Logger.LogDebug( + $"KeygenConfirmWithOnlyTS([{string.Join(", ", thresholdSignaturePublicKeys.Select(s => s.ToHex()))}])"); + if (cycle.ToBigInteger() != GetConsensusGeneration(frame)) + { + Logger.LogWarning($"Invalid cycle: {cycle}, now is {GetConsensusGeneration(frame)}"); + return ExecutionStatus.Ok; + } + + frame.ReturnValue = new byte[] { }; + frame.UseGas(GasMetering.KeygenConfirmCost); + + var senderPubKey = _context.Receipt.RecoverPublicKey( + HardforkHeights.IsHardfork_9Active(_context.Snapshot.Blocks.GetTotalBlockHeight()) + ); + var nextValidators = _nextValidators.Get() + .Batch(CryptoUtils.PublicKeyLength) + .Select(x => x.ToArray().ToPublicKey()) + .ToArray(); + if (!nextValidators.Contains(senderPubKey)) + { + Logger.LogDebug($"non validator (public key: {senderPubKey.ToHex()}) sent MethodKeygenConfirmWithOnlyTS tx"); + return ExecutionStatus.ExecutionHalted; + } + + var players = thresholdSignaturePublicKeys.Length; + var faulty = (players - 1) / 3; + + UInt256 keyringHash; + PublicKeySet tsKeys; + try + { + tsKeys = new PublicKeySet( + thresholdSignaturePublicKeys.Select(x => Lachain.Crypto.ThresholdSignature.PublicKey.FromBytes(x)), + faulty + ); + keyringHash = tsKeys.ToBytes().Keccak(); + } + catch + { + Logger.LogError("GovernanceContract is halted in KeyGenConfirm"); + return ExecutionStatus.ExecutionHalted; + } + + var gen = GetConsensusGeneration(frame); + var votes = GetConfirmations(keyringHash.ToBytes(), gen); + SetConfirmations(keyringHash.ToBytes(), gen, votes + 1); + + Logger.LogDebug($"KeygenConfirmWithOnlyTS: {votes + 1} collected for this keyset"); + if (votes + 1 != players - faulty) return ExecutionStatus.Ok; + Logger.LogDebug($"KeygenConfirmWithOnlyTS: succeeded since collected {votes + 1} votes"); + _lastSuccessfulKeygenBlock.Set(new BigInteger(frame.InvocationContext.Receipt.Block).ToUInt256().ToBytes()); + SetPlayersCount(players); + SetTSKeys(tsKeys); + + Emit(GovernanceInterface.EventKeygenConfirmWithOnlyTS, thresholdSignaturePublicKeys); + return ExecutionStatus.Ok; + } + [ContractMethod(GovernanceInterface.MethodFinishCycle)] public ExecutionStatus FinishCycle(UInt256 cycle, SystemContractExecutionFrame frame) { @@ -399,6 +460,33 @@ public ExecutionStatus FinishCycle(UInt256 cycle, SystemContractExecutionFrame f return ExecutionStatus.ExecutionHalted; } + ExecutionStatus validatorsUpdated; + if (HardforkHeights.IsHardfork_15Active(currentBlock)) + validatorsUpdated = UpdateValidatorsV2(frame); + else + validatorsUpdated = UpdateValidatorsV1(frame); + if (validatorsUpdated != ExecutionStatus.Ok) + return validatorsUpdated; + + var balanceOfExecutionResult = Hepler.CallSystemContract(frame, + ContractRegisterer.LatokenContract, ContractRegisterer.GovernanceContract, + Lrc20Interface.MethodBalanceOf, + ContractRegisterer.GovernanceContract); + + if (balanceOfExecutionResult.Status != ExecutionStatus.Ok) + { + Logger.LogError("GovernanceContract is halted in FinishCycle"); + return ExecutionStatus.ExecutionHalted; + } + + var txFeesAmount = balanceOfExecutionResult.ReturnValue!.ToUInt256().ToMoney(); + SetCollectedFees(txFeesAmount); + ClearPlayersCount(); + return ExecutionStatus.Ok; + } + + private ExecutionStatus UpdateValidatorsV1(SystemContractExecutionFrame frame) + { var players = GetPlayersCount(); var gen = GetConsensusGeneration(frame) - 1; if (players != null) @@ -412,7 +500,8 @@ public ExecutionStatus FinishCycle(UInt256 cycle, SystemContractExecutionFrame f if (votes + 1 < players - faulty) { Logger.LogError( - $"GovernanceContract is halted in FinishCycle, collected {votes} votes, need {players - faulty - 1}"); + $"GovernanceContract is halted in FinishCycle, collected {votes} votes, need {players - faulty - 1}" + ); return ExecutionStatus.ExecutionHalted; } @@ -431,8 +520,14 @@ public ExecutionStatus FinishCycle(UInt256 cycle, SystemContractExecutionFrame f Logger.LogWarning(k.ToHex()); } - _context.Snapshot.Validators.UpdateValidators(ecdsaPublicKeys, tsKeys, tpkeKey, tpkeVerificationKeys, - HardforkHeights.IsHardfork_12Active(currentBlock)); + if (HardforkHeights.IsHardfork_12Active(frame.InvocationContext.Receipt.Block)) + _context.Snapshot.Validators.UpdateValidators( + ecdsaPublicKeys, tsKeys, tpkeKey, tpkeVerificationKeys, ConsensusStateStatus.StateWithTpkeVerification + ); + else + _context.Snapshot.Validators.UpdateValidators( + ecdsaPublicKeys, tsKeys, tpkeKey, tpkeVerificationKeys, ConsensusStateStatus.OldState + ); Emit(GovernanceInterface.EventFinishCycle); Logger.LogDebug("Enough confirmations collected, validators will be changed in the next block"); @@ -441,21 +536,50 @@ public ExecutionStatus FinishCycle(UInt256 cycle, SystemContractExecutionFrame f Logger.LogDebug($" - TS public keys: {string.Join(", ", tsKeys.Keys.Select(key => key.ToHex()))}"); Logger.LogDebug($" - TPKE public key: {tpkeKey.ToHex()}"); } + return ExecutionStatus.Ok; + } - var balanceOfExecutionResult = Hepler.CallSystemContract(frame, - ContractRegisterer.LatokenContract, ContractRegisterer.GovernanceContract, - Lrc20Interface.MethodBalanceOf, - ContractRegisterer.GovernanceContract); - - if (balanceOfExecutionResult.Status != ExecutionStatus.Ok) + private ExecutionStatus UpdateValidatorsV2(SystemContractExecutionFrame frame) + { + var players = GetPlayersCount(); + var gen = GetConsensusGeneration(frame) - 1; + if (players != null) { - Logger.LogError("GovernanceContract is halted in FinishCycle"); - return ExecutionStatus.ExecutionHalted; - } + var faulty = (players - 1) / 3; + var tsKeys = GetTSKeys(); + var keyringHash = tsKeys.ToBytes().Keccak(); + var votes = GetConfirmations(keyringHash.ToBytes(), gen); + if (votes + 1 < players - faulty) + { + Logger.LogError( + $"GovernanceContract is halted in FinishCycle, collected {votes} votes, need {players - faulty - 1}" + ); + return ExecutionStatus.ExecutionHalted; + } - var txFeesAmount = balanceOfExecutionResult.ReturnValue!.ToUInt256().ToMoney(); - SetCollectedFees(txFeesAmount); - ClearPlayersCount(); + var ecdsaPublicKeys = _nextValidators.Get() + .Batch(CryptoUtils.PublicKeyLength) + .Select(x => x.ToArray().ToPublicKey()) + .ToArray(); + + foreach (var k in ecdsaPublicKeys) + { + Logger.LogWarning(k.ToHex()); + } + + foreach (var k in tsKeys.Keys) + { + Logger.LogWarning(k.ToHex()); + } + + _context.Snapshot.Validators.UpdateValidators(ecdsaPublicKeys, tsKeys); + + Emit(GovernanceInterface.EventFinishCycle); + Logger.LogDebug("Enough confirmations collected, validators will be changed in the next block"); + Logger.LogDebug( + $" - ECDSA public keys: {string.Join(", ", ecdsaPublicKeys.Select(key => key.ToHex()))}"); + Logger.LogDebug($" - TS public keys: {string.Join(", ", tsKeys.Keys.Select(key => key.ToHex()))}"); + } return ExecutionStatus.Ok; } diff --git a/src/Lachain.Core/Blockchain/SystemContracts/Interface/GovernanceInterface.cs b/src/Lachain.Core/Blockchain/SystemContracts/Interface/GovernanceInterface.cs index 2a514b7a9..395b8b852 100644 --- a/src/Lachain.Core/Blockchain/SystemContracts/Interface/GovernanceInterface.cs +++ b/src/Lachain.Core/Blockchain/SystemContracts/Interface/GovernanceInterface.cs @@ -9,6 +9,7 @@ public class GovernanceInterface : IContractInterface public const string MethodKeygenSendValue = "keygenSendValue(uint256,uint256,bytes[])"; public const string MethodKeygenConfirm = "keygenConfirm(uint256,bytes,bytes[])"; public const string MethodKeygenConfirmWithVerification = "keygenConfirmWithVerification(uint256,bytes,bytes[],bytes[])"; + public const string MethodKeygenConfirmWithOnlyTS = "keygenConfirmOnlyTS(uint256,bytes[])"; public const string MethodFinishCycle = "finishCycle(uint256)"; public const string MethodIsNextValidator = "isNextValidator(bytes)"; public const string MethodDistributeCycleRewardsAndPenalties = "distibuteCycleRewardsAndPenalties(uint256)"; @@ -18,6 +19,7 @@ public class GovernanceInterface : IContractInterface public const string EventKeygenSendValue = "KeygenSendValue(uint256,bytes[])"; public const string EventKeygenConfirm = "KeygenConfirm(bytes,bytes[])"; public const string EventKeygenConfirmWithVerificationKeys = "KeygenConfirmWithVerificationKeys(bytes,bytes[])"; + public const string EventKeygenConfirmWithOnlyTS = "keygenConfirmOnlyTS(bytes[])"; public const string EventFinishCycle = "FinishCycle()"; public const string EventDistributeCycleRewardsAndPenalties = "DistibuteCycleRewardsAndPenalties(uint256)"; @@ -30,6 +32,7 @@ public class GovernanceInterface : IContractInterface MethodFinishCycle, MethodKeygenConfirm, MethodKeygenConfirmWithVerification, + MethodKeygenConfirmWithOnlyTS, MethodIsNextValidator }; @@ -44,6 +47,7 @@ public class GovernanceInterface : IContractInterface EventKeygenSendValue, EventKeygenConfirm, EventKeygenConfirmWithVerificationKeys, + EventKeygenConfirmWithOnlyTS, EventFinishCycle, EventDistributeCycleRewardsAndPenalties }; diff --git a/src/Lachain.Core/Blockchain/Validators/ValidatorManager.cs b/src/Lachain.Core/Blockchain/Validators/ValidatorManager.cs index 557b750ff..43d17a8a9 100644 --- a/src/Lachain.Core/Blockchain/Validators/ValidatorManager.cs +++ b/src/Lachain.Core/Blockchain/Validators/ValidatorManager.cs @@ -34,8 +34,6 @@ public ValidatorManager(ISnapshotIndexRepository snapshotIndexRepository) Logger.LogTrace($"Fetched {n} validators f={f}"); return new PublicConsensusKeySet( n, f, - PublicKey.FromBytes(state.TpkePublicKey), - state.TpkeVerificationKeys.Select(x => PublicKey.FromBytes(x)), new PublicKeySet( state.Validators.Select(v => Crypto.ThresholdSignature.PublicKey.FromBytes(v.ThresholdSignaturePublicKey)), diff --git a/src/Lachain.Core/Config/ConfigManager.cs b/src/Lachain.Core/Config/ConfigManager.cs index 80260040b..61c98f4e9 100644 --- a/src/Lachain.Core/Config/ConfigManager.cs +++ b/src/Lachain.Core/Config/ConfigManager.cs @@ -13,7 +13,7 @@ namespace Lachain.Core.Config { public class ConfigManager : IConfigManager { - private const ulong _CurrentVersion = 17; + private const ulong _CurrentVersion = 18; private IDictionary _config; public string ConfigPath { get; } public RunOptions CommandLineOptions { get; } @@ -75,6 +75,8 @@ private void _UpdateConfigVersion() _UpdateConfigToV16(); if (version < 17) _UpdateConfigToV17(); + if (version < 18) + _UpdateConfigToV18(); } // version 2 of config should contain hardfork section and height for first hardfork, @@ -503,6 +505,32 @@ private void _UpdateConfigToV17() _SaveCurrentConfig(); } + // version 18 of config should contain Hardfork_15 + private void _UpdateConfigToV18() + { + var network = GetConfig("network") ?? + throw new ApplicationException("No network section in config"); + + var hardforks = GetConfig("hardfork") ?? + throw new ApplicationException("No hardfork section in config"); + hardforks.Hardfork_15 ??= network.NetworkName switch + { + "mainnet" => 6311100, + "testnet" => 6027100, + "devnet" => 1750310, + _ => 10 + }; + _config["hardfork"] = JObject.FromObject(hardforks); + + var version = GetConfig("version") ?? + throw new ApplicationException("No version section in config"); + + version.Version = 18; + _config["version"] = JObject.FromObject(version); + + _SaveCurrentConfig(); + } + private void _SaveCurrentConfig() { File.WriteAllText(ConfigPath, JsonConvert.SerializeObject(_config, Formatting.Indented)); diff --git a/src/Lachain.Core/Vault/IPrivateWallet.cs b/src/Lachain.Core/Vault/IPrivateWallet.cs index 534e3e0bc..df053549d 100644 --- a/src/Lachain.Core/Vault/IPrivateWallet.cs +++ b/src/Lachain.Core/Vault/IPrivateWallet.cs @@ -8,10 +8,6 @@ public interface IPrivateWallet EcdsaKeyPair EcdsaKeyPair { get; } byte[] HubPrivateKey { get; } - Crypto.TPKE.PrivateKey? GetTpkePrivateKeyForBlock(ulong block); - - void AddTpkePrivateKeyAfterBlock(ulong block, Crypto.TPKE.PrivateKey key); - Crypto.ThresholdSignature.PrivateKeyShare? GetThresholdSignatureKeyForBlock(ulong block); void AddThresholdSignatureKeyAfterBlock(ulong block, Crypto.ThresholdSignature.PrivateKeyShare key); diff --git a/src/Lachain.Core/Vault/JsonWallet.cs b/src/Lachain.Core/Vault/JsonWallet.cs index 9dd76e31d..c05d5cad0 100644 --- a/src/Lachain.Core/Vault/JsonWallet.cs +++ b/src/Lachain.Core/Vault/JsonWallet.cs @@ -5,9 +5,9 @@ namespace Lachain.Core.Vault { [JsonObject] - public class JsonWallet + public class OldJsonWallet { - public JsonWallet( + public OldJsonWallet( string ecdsaPrivateKey, string hubPrivateKey, Dictionary tpkePrivateKeys, @@ -29,4 +29,26 @@ Dictionary thresholdSignatureKeys [JsonProperty("hubPrivateKey")] public string? HubPrivateKey { get; set; } } + + [JsonObject] + public class NewJsonWallet + { + public NewJsonWallet( + string ecdsaPrivateKey, + string hubPrivateKey, + Dictionary thresholdSignatureKeys + ) + { + HubPrivateKey = hubPrivateKey; + ThresholdSignatureKeys = thresholdSignatureKeys; + EcdsaPrivateKey = ecdsaPrivateKey; + } + + [JsonProperty("thresholdSignatureKeys")] + public Dictionary? ThresholdSignatureKeys { get; set; } + + [JsonProperty("ecdsaPrivateKey")] public string? EcdsaPrivateKey { get; set; } + + [JsonProperty("hubPrivateKey")] public string? HubPrivateKey { get; set; } + } } \ No newline at end of file diff --git a/src/Lachain.Core/Vault/KeyGenManager.cs b/src/Lachain.Core/Vault/KeyGenManager.cs index 9fb358d3e..94c6d3514 100644 --- a/src/Lachain.Core/Vault/KeyGenManager.cs +++ b/src/Lachain.Core/Vault/KeyGenManager.cs @@ -246,9 +246,19 @@ private void BlockManagerOnSystemContractInvoked(object _, InvocationContext con if (willParticipate) { - var confirmTx = HardforkHeights.IsHardfork_12Active(context.Receipt.Block) ? - MakeConfirmWithVerificationTransaction(cycle, keys) : - MakeConfirmTransaction(cycle, keys); + TransactionReceipt confirmTx; + if (HardforkHeights.IsHardfork_15Active(context.Receipt.Block)) + { + confirmTx = MakeConfirmWithOnlyTSTransaction(cycle, keys); + } + else if (HardforkHeights.IsHardfork_12Active(context.Receipt.Block)) + { + confirmTx = MakeConfirmWithVerificationTransaction(cycle, keys); + } + else + { + confirmTx = MakeConfirmTransaction(cycle, keys); + } if (_transactionPool.Add(confirmTx) is var error && error != OperatingError.Ok) Logger.LogError($"Error creating confirm transaction ({confirmTx.Hash.ToHex()}): {error}"); else @@ -289,11 +299,10 @@ private void BlockManagerOnSystemContractInvoked(object _, InvocationContext con if (keygen.HandleConfirm(tpkePublicKey, tsKeys)) { var keys = keygen.TryGetKeys() ?? throw new Exception(); - Logger.LogTrace($"Generated keyring with public hash {keys.PublicPartHash().ToHex()}"); - Logger.LogTrace($" - TPKE public key: {keys.TpkePublicKey.ToHex()}"); - Logger.LogTrace( - $" - TPKE verification public keys: {string.Join(", ", keys.TpkeVerificationPublicKeys.Select(key => key.ToHex()))}" - ); + var faulty = keys.ThresholdSignaturePublicKeySet.Threshold; + var tpkeSharedPublicKey = new PublicKey(keys.ThresholdSignaturePublicKeySet.SharedPublicKey.RawKey, faulty); + Logger.LogTrace($"Generated keyring with public hash {KeyringHashWithTpkePublicKey(tpkeSharedPublicKey, keys).ToHex()}"); + Logger.LogTrace($" - TPKE public key: {tpkeSharedPublicKey.ToHex()}"); Logger.LogTrace( $" - TS public key: {keys.ThresholdSignaturePrivateKey.GetPublicKeyShare().ToHex()}"); Logger.LogTrace( @@ -304,7 +313,6 @@ private void BlockManagerOnSystemContractInvoked(object _, InvocationContext con _privateWallet.AddThresholdSignatureKeyAfterBlock( lastBlockInCurrentCycle, keys.ThresholdSignaturePrivateKey ); - _privateWallet.AddTpkePrivateKeyAfterBlock(lastBlockInCurrentCycle, keys.TpkePrivateKey); Logger.LogDebug("New keyring saved to wallet"); _keyGenRepository.SaveKeyGenState(Array.Empty()); } @@ -344,11 +352,62 @@ private void BlockManagerOnSystemContractInvoked(object _, InvocationContext con if (keygen.HandleConfirm(tpkePublicKey, tsKeys)) { var keys = keygen.TryGetKeys() ?? throw new Exception(); - Logger.LogTrace($"Generated keyring with public hash {keys.PublicPartHash().ToHex()}"); - Logger.LogTrace($" - TPKE public key: {keys.TpkePublicKey.ToHex()}"); + var faulty = keys.ThresholdSignaturePublicKeySet.Threshold; + var tpkeSharedPublicKey = new PublicKey(keys.ThresholdSignaturePublicKeySet.SharedPublicKey.RawKey, faulty); + var tpkeVerificationKeys = keys.ThresholdSignaturePublicKeySet.Keys.Select(key => new PublicKey(key.RawKey, faulty)).ToList(); + Logger.LogTrace($"Generated keyring with public hash {KeyringHashWithTpkePublicKey(tpkeSharedPublicKey, keys).ToHex()}"); + Logger.LogTrace($" - TPKE public key: {tpkeSharedPublicKey.ToHex()}"); + Logger.LogTrace( + $" - TPKE verification public keys: {string.Join(", ", tpkeVerificationKeys.Select(key => key.ToHex()))}" + ); Logger.LogTrace( - $" - TPKE verification public keys: {string.Join(", ", keys.TpkeVerificationPublicKeys.Select(key => key.ToHex()))}" + $" - TS public key: {keys.ThresholdSignaturePrivateKey.GetPublicKeyShare().ToHex()}"); + Logger.LogTrace( + $" - TS public key set: {string.Join(", ", keys.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToHex()))}" ); + var lastBlockInCurrentCycle = (context.Receipt.Block / StakingContract.CycleDuration + 1) * + StakingContract.CycleDuration; + _privateWallet.AddThresholdSignatureKeyAfterBlock( + lastBlockInCurrentCycle, keys.ThresholdSignaturePrivateKey + ); + Logger.LogDebug("New keyring saved to wallet"); + _keyGenRepository.SaveKeyGenState(Array.Empty()); + } + else + { + _keyGenRepository.SaveKeyGenState(keygen.ToBytes()); + } + } + else if (signature == ContractEncoder.MethodSignatureAsInt(GovernanceInterface.MethodKeygenConfirmWithOnlyTS)) + { + Logger.LogDebug($"Detected call of GovernanceContract.{GovernanceInterface.MethodKeygenConfirmWithOnlyTS}"); + var keygen = GetCurrentKeyGen(); + if (keygen is null) return; + var sender = keygen.GetSenderByPublicKey(context.Receipt.RecoverPublicKey(HardforkHeights.IsHardfork_9Active(context.Receipt.Block))); + if (sender < 0) + { + Logger.LogWarning($"Skipping call because of invalid sender: {sender}"); + return; + } + + var args = decoder.Decode(GovernanceInterface.MethodKeygenConfirmWithOnlyTS); + var cycle = args[0] as UInt256 ?? throw new Exception("Failed to get cycle"); + var tsKeys = new PublicKeySet( + (args[1] as byte[][] ?? throw new Exception()).Select(x => + Crypto.ThresholdSignature.PublicKey.FromBytes(x) + ), + keygen.Faulty + ); + if (cycle.ToBigInteger() != keygen.Cycle) + { + Logger.LogError($"Got KeygenConfirm for cycle {cycle.ToBigInteger()} while doing keygen for {keygen.Cycle}"); + return; + } + + if (keygen.HandleConfirm(tsKeys)) + { + var keys = keygen.TryGetKeys() ?? throw new Exception(); + Logger.LogTrace($"Generated keyring with public hash {keys.PublicPartHash().ToHex()}"); Logger.LogTrace( $" - TS public key: {keys.ThresholdSignaturePrivateKey.GetPublicKeyShare().ToHex()}"); Logger.LogTrace( @@ -359,7 +418,6 @@ private void BlockManagerOnSystemContractInvoked(object _, InvocationContext con _privateWallet.AddThresholdSignatureKeyAfterBlock( lastBlockInCurrentCycle, keys.ThresholdSignaturePrivateKey ); - _privateWallet.AddTpkePrivateKeyAfterBlock(lastBlockInCurrentCycle, keys.TpkePrivateKey); Logger.LogDebug("New keyring saved to wallet"); _keyGenRepository.SaveKeyGenState(Array.Empty()); } @@ -373,6 +431,12 @@ private void BlockManagerOnSystemContractInvoked(object _, InvocationContext con private TransactionReceipt MakeConfirmTransaction(UInt256 cycle, ThresholdKeyring keyring) { Logger.LogTrace("MakeConfirmTransaction"); + var faulty = keyring.ThresholdSignaturePublicKeySet.Threshold; + // these tpke keys are valid, because G1 of ts and tpke is same + var tpkePubKey = new PublicKey( + keyring.ThresholdSignaturePublicKeySet.SharedPublicKey.RawKey, + faulty + ); var tx = _transactionBuilder.InvokeTransactionWithGasPrice( _privateWallet.EcdsaKeyPair.PublicKey.GetAddress(), ContractRegisterer.GovernanceContract, @@ -380,7 +444,7 @@ private TransactionReceipt MakeConfirmTransaction(UInt256 cycle, ThresholdKeyrin GovernanceInterface.MethodKeygenConfirm, 0, cycle, - keyring.TpkePublicKey.ToBytes(), + tpkePubKey.ToBytes(), // adding fake public key keyring.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToBytes()).ToArray() ); return _transactionSigner.Sign(tx, _privateWallet.EcdsaKeyPair, HardforkHeights.IsHardfork_9Active(_blockManager.GetHeight() + 1)); @@ -389,6 +453,13 @@ private TransactionReceipt MakeConfirmTransaction(UInt256 cycle, ThresholdKeyrin private TransactionReceipt MakeConfirmWithVerificationTransaction(UInt256 cycle, ThresholdKeyring keyring) { Logger.LogTrace("MakeConfirmWithVerificationTransaction"); + var faulty = keyring.ThresholdSignaturePublicKeySet.Threshold; + // these tpke keys are valid, because G1 of ts and tpke is same + var tpkePubKey = new PublicKey( + keyring.ThresholdSignaturePublicKeySet.SharedPublicKey.RawKey, + faulty + ); + var tpkeVerificationKeys = keyring.ThresholdSignaturePublicKeySet.Keys.Select(key => new PublicKey(key.RawKey, faulty)).ToList(); var tx = _transactionBuilder.InvokeTransactionWithGasPrice( _privateWallet.EcdsaKeyPair.PublicKey.GetAddress(), ContractRegisterer.GovernanceContract, @@ -396,9 +467,24 @@ private TransactionReceipt MakeConfirmWithVerificationTransaction(UInt256 cycle, GovernanceInterface.MethodKeygenConfirmWithVerification, 0, cycle, - keyring.TpkePublicKey.ToBytes(), + tpkePubKey.ToBytes(), keyring.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToBytes()).ToArray(), - keyring.TpkeVerificationPublicKeys.Select(key => key.ToBytes()).ToArray() + tpkeVerificationKeys.Select(key => key.ToBytes()).ToArray() + ); + return _transactionSigner.Sign(tx, _privateWallet.EcdsaKeyPair, HardforkHeights.IsHardfork_9Active(_blockManager.GetHeight() + 1)); + } + + private TransactionReceipt MakeConfirmWithOnlyTSTransaction(UInt256 cycle, ThresholdKeyring keyring) + { + Logger.LogTrace("MakeConfirmWithOnlyTSTransaction"); + var tx = _transactionBuilder.InvokeTransactionWithGasPrice( + _privateWallet.EcdsaKeyPair.PublicKey.GetAddress(), + ContractRegisterer.GovernanceContract, + Money.Zero, + GovernanceInterface.MethodKeygenConfirmWithOnlyTS, + 0, + cycle, + keyring.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToBytes()).ToArray() ); return _transactionSigner.Sign(tx, _privateWallet.EcdsaKeyPair, HardforkHeights.IsHardfork_9Active(_blockManager.GetHeight() + 1)); } @@ -526,8 +612,53 @@ public bool RescanBlockChainForKeys(IPublicConsensusKeySet publicKeysToSearch) if (!keygen.HandleConfirm(tpkePublicKey, tsKeys)) continue; var keys = keygen.TryGetKeys() ?? throw new Exception(); - Logger.LogTrace($"Generated keyring with public hash {keys.PublicPartHash().ToHex()}"); - Logger.LogTrace($" - TPKE public key: {keys.TpkePublicKey.ToHex()}"); + var faulty = keys.ThresholdSignaturePublicKeySet.Threshold; + var tpkeSharedPublicKey = new PublicKey(keys.ThresholdSignaturePublicKeySet.SharedPublicKey.RawKey, faulty); + Logger.LogTrace($"Generated keyring with public hash {KeyringHashWithTpkePublicKey(tpkeSharedPublicKey, keys).ToHex()}"); + Logger.LogTrace($" - TPKE public key: {tpkeSharedPublicKey.ToHex()}"); + Logger.LogTrace( + $" - TS public key: {keys.ThresholdSignaturePrivateKey.GetPublicKeyShare().ToHex()}"); + Logger.LogTrace( + $" - TS public key set: {string.Join(", ", keys.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToHex()))}" + ); + var lastBlockInCurrentCycle = + (i / StakingContract.CycleDuration + 1) * StakingContract.CycleDuration; + _privateWallet.AddThresholdSignatureKeyAfterBlock( + lastBlockInCurrentCycle, keys.ThresholdSignaturePrivateKey + ); + Logger.LogDebug("New keyring saved to wallet"); + return true; + } + else if (signature == ContractEncoder.MethodSignatureAsInt(GovernanceInterface.MethodKeygenConfirmWithVerification)) + { + Logger.LogDebug( + $"Detected call of GovernanceContract.{GovernanceInterface.MethodKeygenConfirmWithVerification}"); + var sender = keygen.GetSenderByPublicKey(tx.RecoverPublicKey(HardforkHeights.IsHardfork_9Active(i))); + if (sender < 0) + { + Logger.LogWarning($"Skipping call because of invalid sender: {sender}"); + continue; + } + + var args = decoder.Decode(GovernanceInterface.MethodKeygenConfirmWithVerification); + var tpkePublicKey = PublicKey.FromBytes(args[1] as byte[] ?? throw new Exception()); + var tsKeys = new PublicKeySet( + (args[2] as byte[][] ?? throw new Exception()).Select(x => + Crypto.ThresholdSignature.PublicKey.FromBytes(x) + ), + keygen.Faulty + ); + + if (!keygen.HandleConfirm(tpkePublicKey, tsKeys)) continue; + var keys = keygen.TryGetKeys() ?? throw new Exception(); + var faulty = keys.ThresholdSignaturePublicKeySet.Threshold; + var tpkeSharedPublicKey = new PublicKey(keys.ThresholdSignaturePublicKeySet.SharedPublicKey.RawKey, faulty); + var tpkeVerificationKeys = keys.ThresholdSignaturePublicKeySet.Keys.Select(key => new PublicKey(key.RawKey, faulty)).ToList(); + Logger.LogTrace($"Generated keyring with public hash {KeyringHashWithTpkePublicKey(tpkeSharedPublicKey, keys).ToHex()}"); + Logger.LogTrace($" - TPKE public key: {tpkeSharedPublicKey.ToHex()}"); + Logger.LogTrace( + $" - TPKE verification public keys: {string.Join(", ", tpkeVerificationKeys.Select(key => key.ToHex()))}" + ); Logger.LogTrace( $" - TS public key: {keys.ThresholdSignaturePrivateKey.GetPublicKeyShare().ToHex()}"); Logger.LogTrace( @@ -538,7 +669,40 @@ public bool RescanBlockChainForKeys(IPublicConsensusKeySet publicKeysToSearch) _privateWallet.AddThresholdSignatureKeyAfterBlock( lastBlockInCurrentCycle, keys.ThresholdSignaturePrivateKey ); - _privateWallet.AddTpkePrivateKeyAfterBlock(lastBlockInCurrentCycle, keys.TpkePrivateKey); + Logger.LogDebug("New keyring saved to wallet"); + return true; + } + else if (signature == ContractEncoder.MethodSignatureAsInt(GovernanceInterface.MethodKeygenConfirmWithOnlyTS)) + { + Logger.LogDebug($"Detected call of GovernanceContract.{GovernanceInterface.MethodKeygenConfirmWithOnlyTS}"); + var sender = keygen.GetSenderByPublicKey(tx.RecoverPublicKey(HardforkHeights.IsHardfork_9Active(i))); + if (sender < 0) + { + Logger.LogWarning($"Skipping call because of invalid sender: {sender}"); + continue; + } + + var args = decoder.Decode(GovernanceInterface.MethodKeygenConfirmWithOnlyTS); + var tsKeys = new PublicKeySet( + (args[1] as byte[][] ?? throw new Exception()).Select(x => + Crypto.ThresholdSignature.PublicKey.FromBytes(x) + ), + keygen.Faulty + ); + + if (!keygen.HandleConfirm(tsKeys)) continue; + var keys = keygen.TryGetKeys() ?? throw new Exception(); + Logger.LogTrace($"Generated keyring with public hash {keys.PublicPartHash().ToHex()}"); + Logger.LogTrace( + $" - TS public key: {keys.ThresholdSignaturePrivateKey.GetPublicKeyShare().ToHex()}"); + Logger.LogTrace( + $" - TS public key set: {string.Join(", ", keys.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToHex()))}" + ); + var lastBlockInCurrentCycle = (i / StakingContract.CycleDuration + 1) * + StakingContract.CycleDuration; + _privateWallet.AddThresholdSignatureKeyAfterBlock( + lastBlockInCurrentCycle, keys.ThresholdSignaturePrivateKey + ); Logger.LogDebug("New keyring saved to wallet"); return true; } @@ -560,5 +724,10 @@ public bool RescanBlockChainForKeys(IPublicConsensusKeySet publicKeysToSearch) return (start + 1, end); } + + private UInt256 KeyringHashWithTpkePublicKey(PublicKey tpkeKey, ThresholdKeyring keyring) + { + return tpkeKey.ToBytes().Concat(keyring.ThresholdSignaturePublicKeySet.ToBytes()).Keccak(); + } } } \ No newline at end of file diff --git a/src/Lachain.Core/Vault/PrivateWallet.cs b/src/Lachain.Core/Vault/PrivateWallet.cs index ef3564c4e..6a8a553ba 100644 --- a/src/Lachain.Core/Vault/PrivateWallet.cs +++ b/src/Lachain.Core/Vault/PrivateWallet.cs @@ -26,7 +26,6 @@ public class PrivateWallet : IPrivateWallet private readonly ISortedDictionary _tsKeys = new TreeDictionary(); - private readonly ISortedDictionary _tpkeKeys = new TreeDictionary(); private readonly string _walletPath; private string _walletPassword; @@ -60,33 +59,6 @@ public PrivateWallet(IConfigManager configManager) if (needsSave) SaveWallet(_walletPath, _walletPassword); } - public PrivateKey? GetTpkePrivateKeyForBlock(ulong block) - { - try - { - return _tpkeKeys.Predecessor(block + 1).Value; - } - catch (NoSuchItemException) - { - return null; - } - } - - public void AddTpkePrivateKeyAfterBlock(ulong block, PrivateKey key) - { - if (_tpkeKeys.Contains(block)) - { - _tpkeKeys.Update(block, key); - Logger.LogWarning($"TpkePrivateKey for block {block} is overwritten"); - } - else - { - _tpkeKeys.Add(block, key); - } - - SaveWallet(_walletPath, _walletPassword); - } - public PrivateKeyShare? GetThresholdSignatureKeyForBlock(ulong block) { return !_tsKeys.TryPredecessor(block + 1, out var predecessor) ? null : predecessor.Value; @@ -109,17 +81,10 @@ public void AddThresholdSignatureKeyAfterBlock(ulong block, PrivateKeyShare key) private void SaveWallet(string path, string password) { - var wallet = new JsonWallet + var wallet = new NewJsonWallet ( EcdsaKeyPair.PrivateKey.ToHex(), HubPrivateKey.ToHex(), - new Dictionary( - _tpkeKeys.Select(p => - new System.Collections.Generic.KeyValuePair( - p.Key, p.Value.ToHex() - ) - ) - ), new Dictionary( _tsKeys.Select(p => new System.Collections.Generic.KeyValuePair( @@ -142,14 +107,14 @@ public static (string PrivateKey, string PublicKey) GenerateHubKey() return (keyInfo[0], keyInfo[1]); } - private bool RestoreWallet(string path, string password, out EcdsaKeyPair keyPair, out byte[] hubKey) + private bool RestoreFromOldWallet(string path, string password, out EcdsaKeyPair keyPair, out byte[] hubKey) { var encryptedContent = File.ReadAllBytes(path); var key = Encoding.UTF8.GetBytes(password).KeccakBytes(); var decryptedContent = Encoding.UTF8.GetString(Crypto.AesGcmDecrypt(key, encryptedContent)); - var wallet = JsonConvert.DeserializeObject(decryptedContent); + var wallet = JsonConvert.DeserializeObject(decryptedContent); if (wallet.EcdsaPrivateKey is null) throw new Exception("Decrypted wallet does not contain ECDSA key"); var needsSave = false; @@ -164,22 +129,73 @@ private bool RestoreWallet(string path, string password, out EcdsaKeyPair keyPai keyPair = new EcdsaKeyPair(wallet.EcdsaPrivateKey.HexToBytes().ToPrivateKey()); hubKey = wallet.HubPrivateKey.HexToBytes(); - _tpkeKeys.AddAll(wallet.TpkePrivateKeys + _tsKeys.AddAll(wallet.ThresholdSignatureKeys .Select(p => - new C5.KeyValuePair(p.Key, PrivateKey.FromBytes(p.Value.HexToBytes())))); + new C5.KeyValuePair(p.Key, + PrivateKeyShare.FromBytes(p.Value.HexToBytes())))); + return needsSave; + } + + private bool RestoreFromNewWallet(string path, string password, out EcdsaKeyPair keyPair, out byte[] hubKey) + { + var encryptedContent = File.ReadAllBytes(path); + var key = Encoding.UTF8.GetBytes(password).KeccakBytes(); + var decryptedContent = + Encoding.UTF8.GetString(Crypto.AesGcmDecrypt(key, encryptedContent)); + + var wallet = JsonConvert.DeserializeObject(decryptedContent); + if (wallet.EcdsaPrivateKey is null) + throw new Exception("Decrypted wallet does not contain ECDSA key"); + var needsSave = false; + if (wallet.HubPrivateKey is null) + { + wallet.HubPrivateKey = GenerateHubKey().PrivateKey; + needsSave = true; + } + + wallet.ThresholdSignatureKeys ??= new Dictionary(); + + keyPair = new EcdsaKeyPair(wallet.EcdsaPrivateKey.HexToBytes().ToPrivateKey()); + hubKey = wallet.HubPrivateKey.HexToBytes(); _tsKeys.AddAll(wallet.ThresholdSignatureKeys .Select(p => new C5.KeyValuePair(p.Key, PrivateKeyShare.FromBytes(p.Value.HexToBytes())))); return needsSave; } + + private bool RestoreWallet(string path, string password, out EcdsaKeyPair keyPair, out byte[] hubKey) + { + try + { + var needsSave = RestoreFromNewWallet(path, password, out var key, out var hubPrivateKey); + keyPair = key; + hubKey = hubPrivateKey; + return needsSave; + } + catch (Exception ex) + { + Logger.LogInformation($"Could not restore wallet from new configuration, trying old configuration: {ex}"); + try + { + var needsSave = RestoreFromOldWallet(path, password, out var key, out var hubPrivateKey); + keyPair = key; + hubKey = hubPrivateKey; + return needsSave; + } + catch (Exception ex1) + { + Logger.LogError($"Could not restore wallet from old or new configuration. Wallet is corrupted: {ex1}"); + throw; + } + } + } private static void GenerateNewWallet(string path, string password) { - var config = new JsonWallet( + var config = new NewJsonWallet( CryptoProvider.GetCrypto().GenerateRandomBytes(32).ToHex(false), GenerateHubKey().PrivateKey, - new Dictionary {}, new Dictionary {} ); var json = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(config)); @@ -204,7 +220,6 @@ public bool HasKeyForKeySet(PublicKeySet thresholdSignaturePublicKeySet, ulong b public void DeleteKeysAfterBlock(ulong block) { _tsKeys.RemoveRangeFrom(block + 1); - _tpkeKeys.RemoveRangeFrom(block + 1); SaveWallet(_walletPath, _walletPassword); } diff --git a/src/Lachain.Storage/State/IValidatorSnapshot.cs b/src/Lachain.Storage/State/IValidatorSnapshot.cs index 6771fc3dc..15a654ad8 100644 --- a/src/Lachain.Storage/State/IValidatorSnapshot.cs +++ b/src/Lachain.Storage/State/IValidatorSnapshot.cs @@ -10,10 +10,15 @@ public interface IValidatorSnapshot : ISnapshot { ConsensusState GetConsensusState(); - void SetConsensusState(ConsensusState consensusState, bool useNewFormat); + void SetConsensusState(ConsensusState consensusState, ConsensusStateStatus format); IEnumerable GetValidatorsPublicKeys(); - void UpdateValidators(IEnumerable ecdsaKeys, PublicKeySet tsKeys, PublicKey tpkePublicKey, IEnumerable tpkeVerificationKeys, bool useNewFormat); + void UpdateValidators( + IEnumerable ecdsaKeys, PublicKeySet tsKeys, PublicKey tpkePublicKey, IEnumerable tpkeVerificationKeys, + ConsensusStateStatus format + ); + + void UpdateValidators(IEnumerable ecdsaKeys, PublicKeySet tsKeys); } } \ No newline at end of file diff --git a/src/Lachain.Storage/State/ValidatorSnapshot.cs b/src/Lachain.Storage/State/ValidatorSnapshot.cs index 096fd2498..4b3e67bac 100644 --- a/src/Lachain.Storage/State/ValidatorSnapshot.cs +++ b/src/Lachain.Storage/State/ValidatorSnapshot.cs @@ -53,9 +53,9 @@ public ConsensusState GetConsensusState() return ConsensusState.FromBytes(raw); } - public void SetConsensusState(ConsensusState consensusState, bool useNewFormat) + public void SetConsensusState(ConsensusState consensusState, ConsensusStateStatus format) { - var raw = consensusState.ToBytes(useNewFormat); + var raw = consensusState.ToBytes(format); _state.AddOrUpdate(EntryPrefix.ConsensusState.BuildPrefix(), raw); } @@ -65,7 +65,8 @@ public IEnumerable GetValidatorsPublicKeys() } public void UpdateValidators( - IEnumerable ecdsaKeys, PublicKeySet tsKeys, PublicKey tpkePublicKey, IEnumerable tpkeVerificationKeys, bool useNewFormat + IEnumerable ecdsaKeys, PublicKeySet tsKeys, PublicKey tpkePublicKey, IEnumerable tpkeVerificationKeys, + ConsensusStateStatus format ) { var state = new ConsensusState( @@ -75,7 +76,19 @@ public void UpdateValidators( .Zip(tsKeys.Keys, (ecdsaKey, tsKey) => new ValidatorCredentials(ecdsaKey, tsKey.ToBytes())) .ToArray() ); - SetConsensusState(state, useNewFormat); + SetConsensusState(state, format); + } + + public void UpdateValidators(IEnumerable ecdsaKeys, PublicKeySet tsKeys) + { + var state = new ConsensusState( + Array.Empty(), + Array.Empty(), + ecdsaKeys + .Zip(tsKeys.Keys, (ecdsaKey, tsKey) => new ValidatorCredentials(ecdsaKey, tsKey.ToBytes())) + .ToArray() + ); + SetConsensusState(state, ConsensusStateStatus.StateWithOnlyValidatorInfo); } public void SetCurrentVersion(ulong root) { diff --git a/src/Lachain.Utility/ConsensusState.cs b/src/Lachain.Utility/ConsensusState.cs index 6764e6326..4fb582c90 100644 --- a/src/Lachain.Utility/ConsensusState.cs +++ b/src/Lachain.Utility/ConsensusState.cs @@ -8,6 +8,7 @@ namespace Lachain.Utility { public class ConsensusState { + // if number of validators is greater than 255, we need to change ConsensusState with a hardfork public ConsensusState(byte[] tpkePublicKey, byte[][] tpkeVerificationKeys, ValidatorCredentials[] validators) { TpkePublicKey = tpkePublicKey; @@ -20,35 +21,49 @@ public ConsensusState(byte[] tpkePublicKey, byte[][] tpkeVerificationKeys, Valid public byte[][] TpkeVerificationKeys { get; } public ValidatorCredentials[] Validators { get; } - public byte[] ToBytes(bool useNewFormat) + private byte[] OldStateToBytes() { - var a = new List {TpkePublicKey}; - if (useNewFormat) + var rawInfo = new List {TpkePublicKey}; + rawInfo.AddRange(Validators.Select(c => c.ToBytes())); + return RLP.EncodeList(rawInfo.Select(RLP.EncodeElement).ToArray()); + } + + private byte[] StateWithTpkeVerificationToBytes() + { + var rawInfo = new List {TpkePublicKey}; + rawInfo.Add(new byte[] {(byte) TpkeVerificationKeys.Length}); + rawInfo.AddRange(TpkeVerificationKeys); + rawInfo.AddRange(Validators.Select(c => c.ToBytes())); + return RLP.EncodeList(rawInfo.Select(RLP.EncodeElement).ToArray()); + } + + private byte[] StateWithOnlyValidatorInfoToBytes() + { + var rawInfo = new List(); + rawInfo.Add(new byte[] {(byte) Validators.Length}); + rawInfo.AddRange(Validators.Select(c => c.ToBytes())); + return RLP.EncodeList(rawInfo.Select(RLP.EncodeElement).ToArray()); + } + + public byte[] ToBytes(ConsensusStateStatus status) + { + switch (status) { - a.Add(new byte[] {(byte) TpkeVerificationKeys.Length}); - a.AddRange(TpkeVerificationKeys); + case ConsensusStateStatus.OldState: + return OldStateToBytes(); + case ConsensusStateStatus.StateWithTpkeVerification: + return StateWithTpkeVerificationToBytes(); + case ConsensusStateStatus.StateWithOnlyValidatorInfo: + return StateWithOnlyValidatorInfoToBytes(); + default: + throw new Exception($"Unhandled ConsensusStateStatus: {status}"); } - a.AddRange(Validators.Select(c => c.ToBytes())); - return RLP.EncodeList(a.Select(RLP.EncodeElement).ToArray()); } - public static ConsensusState FromBytes(ReadOnlySpan bytes) + // old state where we have tpke public key and validator credentials + public static ConsensusState LoadOldState(RLPCollection decoded) { - var decoded = (RLPCollection) RLP.Decode(bytes.ToArray()); var tpkePubKey = decoded[0].RLPData; - if (decoded[1].RLPData.Length == 1) // new data format with verification keys - { - var keysNumber = decoded[1].RLPData[0]; - var tpkeVerificationKeys = decoded.Skip(2).Take(keysNumber) - .Select(x => x.RLPData) - .ToArray(); - var credentials = decoded.Skip(2 + keysNumber) - .Select(x => x.RLPData) - .Select(x => ValidatorCredentials.FromBytes(x)) - .ToArray(); - return new ConsensusState(tpkePubKey, tpkeVerificationKeys, credentials); - } - // old data format without verification keys var old_credentials = decoded.Skip(1) .Select(x => x.RLPData) .Select(x => ValidatorCredentials.FromBytes(x)) @@ -57,5 +72,53 @@ public static ConsensusState FromBytes(ReadOnlySpan bytes) .Select(i => tpkePubKey).ToArray(); return new ConsensusState(tpkePubKey, fakeTpkeVerificationKeys, old_credentials); } + + // state where we have tpke public and verification keys and validator credentials + public static ConsensusState LoadStateWithTpkeVerification(RLPCollection decoded) + { + var tpkePubKey = decoded[0].RLPData; + var keysNumber = decoded[1].RLPData[0]; + var tpkeVerificationKeys = decoded.Skip(2).Take(keysNumber) + .Select(x => x.RLPData) + .ToArray(); + var credentials = decoded.Skip(2 + keysNumber) + .Select(x => x.RLPData) + .Select(x => ValidatorCredentials.FromBytes(x)) + .ToArray(); + return new ConsensusState(tpkePubKey, tpkeVerificationKeys, credentials); + } + + // updated state where we have only validator credentials + public static ConsensusState LoadStateWithOnlyValidatorInfo(RLPCollection decoded) + { + var keysNumber = decoded[0].RLPData[0]; + var credentials = decoded.Skip(1).Take(keysNumber) + .Select(x => x.RLPData) + .Select(x => ValidatorCredentials.FromBytes(x)) + .ToArray(); + return new ConsensusState(Array.Empty(), Array.Empty(), credentials); + } + + public static ConsensusState FromBytes(ReadOnlySpan bytes) + { + var decoded = (RLPCollection) RLP.Decode(bytes.ToArray()); + if (decoded[0].RLPData.Length == 1) + { + // updated data format with only validator info + return LoadStateWithOnlyValidatorInfo(decoded); + } + else if (decoded[1].RLPData.Length == 1) // new data format with verification keys + { + return LoadStateWithTpkeVerification(decoded); + } + else return LoadOldState(decoded); + } + } + + public enum ConsensusStateStatus : byte + { + OldState = 0, + StateWithTpkeVerification = 1, + StateWithOnlyValidatorInfo = 2, } } \ No newline at end of file diff --git a/test/Lachain.ConsensusTest/BinaryAgreementTest.cs b/test/Lachain.ConsensusTest/BinaryAgreementTest.cs index c30ee94c2..fff75307a 100644 --- a/test/Lachain.ConsensusTest/BinaryAgreementTest.cs +++ b/test/Lachain.ConsensusTest/BinaryAgreementTest.cs @@ -32,8 +32,7 @@ private void SetUpAllHonest(int n, int f) var shares = keygen.GetPrivateShares().ToArray(); var pubKeys = new PublicKeySet(shares.Select(share => share.GetPublicKeyShare()), f); - _publicKeys = new PublicConsensusKeySet(n, f, null!, - new Crypto.TPKE.PublicKey[]{}, + _publicKeys = new PublicConsensusKeySet(n, f, pubKeys, Enumerable.Empty()); for (var i = 0; i < n; ++i) { diff --git a/test/Lachain.ConsensusTest/BinaryBroadcastTest.cs b/test/Lachain.ConsensusTest/BinaryBroadcastTest.cs index 1e2cc1d2a..cd6ceb4b8 100644 --- a/test/Lachain.ConsensusTest/BinaryBroadcastTest.cs +++ b/test/Lachain.ConsensusTest/BinaryBroadcastTest.cs @@ -28,7 +28,7 @@ private void SetUp(int n, int f) _broadcasters = new IConsensusBroadcaster[n]; _resultInterceptors = new ProtocolInvoker[n]; _privateKeys = new IPrivateConsensusKeySet[n]; - _publicKeys = new PublicConsensusKeySet(n, f, null!, new Crypto.TPKE.PublicKey[]{},null!, Enumerable.Empty()); + _publicKeys = new PublicConsensusKeySet(n, f,null!, Enumerable.Empty()); for (var i = 0; i < n; ++i) { _resultInterceptors[i] = new ProtocolInvoker(); diff --git a/test/Lachain.ConsensusTest/CommonCoinTest.cs b/test/Lachain.ConsensusTest/CommonCoinTest.cs index d43ef5955..1ba7da4a9 100644 --- a/test/Lachain.ConsensusTest/CommonCoinTest.cs +++ b/test/Lachain.ConsensusTest/CommonCoinTest.cs @@ -29,7 +29,7 @@ public void SetUp(int n, int f) _broadcasters = new IConsensusBroadcaster[n]; _resultInterceptors = new ProtocolInvoker[n]; _wallets = new IPrivateConsensusKeySet[n]; - _publicKeys = new PublicConsensusKeySet(n, f, null!,new Crypto.TPKE.PublicKey[]{}, pubKeys, Enumerable.Empty()); + _publicKeys = new PublicConsensusKeySet(n, f, pubKeys, Enumerable.Empty()); for (var i = 0; i < n; ++i) { _resultInterceptors[i] = new ProtocolInvoker(); diff --git a/test/Lachain.ConsensusTest/CommonSubsetTest.cs b/test/Lachain.ConsensusTest/CommonSubsetTest.cs index a591c437b..648d0ca42 100644 --- a/test/Lachain.ConsensusTest/CommonSubsetTest.cs +++ b/test/Lachain.ConsensusTest/CommonSubsetTest.cs @@ -38,7 +38,7 @@ private void SetUpAllHonest(int n, int f) var shares = keygen.GetPrivateShares().ToArray(); var pubKeys = new PublicKeySet(shares.Select(share => share.GetPublicKeyShare()), f); _publicKeys = new PublicConsensusKeySet( - n, f, null!, new Crypto.TPKE.PublicKey[]{}, pubKeys, + n, f, pubKeys, Enumerable.Range(0, n) .Select(i => new ECDSAPublicKey {Buffer = ByteString.CopyFrom(i.ToBytes().ToArray())}) ); diff --git a/test/Lachain.ConsensusTest/ErasureCodingTest.cs b/test/Lachain.ConsensusTest/ErasureCodingTest.cs index 58dcae0c6..b7db6a827 100644 --- a/test/Lachain.ConsensusTest/ErasureCodingTest.cs +++ b/test/Lachain.ConsensusTest/ErasureCodingTest.cs @@ -17,7 +17,7 @@ public void TestErasureCoding() const int nShards = 4, nErasures = 2; var rbc = new ReliableBroadcast( new ReliableBroadcastId(0, 0), - new PublicConsensusKeySet(4, 1, null!, new Crypto.TPKE.PublicKey[]{},null!, Enumerable.Empty()), + new PublicConsensusKeySet(4, 1, null!, Enumerable.Empty()), null! ); var data = Enumerable.Range(0, 100) diff --git a/test/Lachain.ConsensusTest/HoneyBadgerTest.cs b/test/Lachain.ConsensusTest/HoneyBadgerTest.cs index 1c9f8e9bb..9d1215e10 100644 --- a/test/Lachain.ConsensusTest/HoneyBadgerTest.cs +++ b/test/Lachain.ConsensusTest/HoneyBadgerTest.cs @@ -49,7 +49,7 @@ public void SetUp(int n, int f) .ToArray(); var tpkeVerificationKeys = Enumerable.Range(0, n) .Select(i => tpkeKeygen.GetVerificationPubKey(i)).ToArray(); - _publicKeys = new PublicConsensusKeySet(n, f, tpkeKeygen.GetPubKey(), tpkeVerificationKeys, pubKeys, + _publicKeys = new PublicConsensusKeySet(n, f, pubKeys, ecdsaKeys.Select(k => k.PublicKey)); _privateKeys = new IPrivateConsensusKeySet[n]; for (var i = 0; i < n; ++i) diff --git a/test/Lachain.ConsensusTest/ReliableBroadcastTest.cs b/test/Lachain.ConsensusTest/ReliableBroadcastTest.cs index 5c0464fd2..1de5b352a 100644 --- a/test/Lachain.ConsensusTest/ReliableBroadcastTest.cs +++ b/test/Lachain.ConsensusTest/ReliableBroadcastTest.cs @@ -48,7 +48,7 @@ private void SetUp(int n, int f) _resultInterceptors = new ProtocolInvoker[n]; _wallets = new IPrivateConsensusKeySet[n]; _publicKeys = new PublicConsensusKeySet( - n, f, null!, new Crypto.TPKE.PublicKey[]{}, null!, + n, f, null!, Enumerable.Range(0, n) .Select(i => new ECDSAPublicKey {Buffer = ByteString.CopyFrom(i.ToBytes().ToArray())}) ); diff --git a/test/Lachain.ConsensusTest/TrustlessKeygenTest.cs b/test/Lachain.ConsensusTest/TrustlessKeygenTest.cs index f9963d6c3..6869b3c31 100644 --- a/test/Lachain.ConsensusTest/TrustlessKeygenTest.cs +++ b/test/Lachain.ConsensusTest/TrustlessKeygenTest.cs @@ -106,8 +106,6 @@ private static ThresholdKeyring[] SimulateKeygen(int n, int f, DeliveryServiceMo continue; } - Assert.IsTrue(curKey.Value.TpkePrivateKey.ToBytes() - .SequenceEqual(curKeys[i]!.Value.TpkePrivateKey.ToBytes())); Assert.AreEqual(curKey.Value.PublicPartHash(), curKeys[i]!.Value.PublicPartHash()); } @@ -129,7 +127,6 @@ private static ThresholdKeyring[] SimulateKeygen(int n, int f, DeliveryServiceMo for (var i = 0; i < n; ++i) { - Assert.AreEqual(keys[0].TpkePublicKey, keys[i].TpkePublicKey); Assert.AreEqual(keys[0].ThresholdSignaturePublicKeySet, keys[i].ThresholdSignaturePublicKeySet); } diff --git a/test/Lachain.CoreTest/Blockchain/SystemContracts/GovernanceContractTest.cs b/test/Lachain.CoreTest/Blockchain/SystemContracts/GovernanceContractTest.cs index 5d57fe985..1173ba3e3 100644 --- a/test/Lachain.CoreTest/Blockchain/SystemContracts/GovernanceContractTest.cs +++ b/test/Lachain.CoreTest/Blockchain/SystemContracts/GovernanceContractTest.cs @@ -20,6 +20,7 @@ using Lachain.Core.DI.SimpleInjector; using Lachain.Crypto; using Lachain.Crypto.ECDSA; +using Lachain.Crypto.TPKE; using Lachain.Proto; using Lachain.Storage.State; using Lachain.Utility.Containers; @@ -137,16 +138,18 @@ public void Test_OneNodeCycle() { ThresholdKeyring? keyring = keygen.TryGetKeys(); Assert.IsNotNull(keyring); + var faulty = keyring.Value.ThresholdSignaturePublicKeySet.Threshold; + var tpkePubKey = new PublicKey(keyring.Value.ThresholdSignaturePublicKeySet.SharedPublicKey.RawKey, faulty); var input = ContractEncoder.Encode(GovernanceInterface.MethodKeygenConfirm, cycle, - keyring!.Value.TpkePublicKey.ToBytes(), + tpkePubKey.ToBytes(), keyring!.Value.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToBytes()).ToArray()); var call = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input); Assert.IsNotNull(call); var frame = new SystemContractExecutionFrame(call!, context, input, 100_000_000); - Assert.AreEqual(ExecutionStatus.Ok, contract.KeyGenConfirm(cycle, keyring!.Value.TpkePublicKey.ToBytes(), + Assert.AreEqual(ExecutionStatus.Ok, contract.KeyGenConfirm(cycle, tpkePubKey.ToBytes(), keyring!.Value.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToBytes()).ToArray(), frame)); // set keygen state - Assert.IsTrue(keygen.HandleConfirm(keyring!.Value.TpkePublicKey, + Assert.IsTrue(keygen.HandleConfirm(tpkePubKey, keyring!.Value.ThresholdSignaturePublicKeySet)); } // check no validators in storage @@ -226,16 +229,18 @@ public void Test_InvalidValidatorKey() { ThresholdKeyring? keyring = keygen.TryGetKeys(); Assert.IsNotNull(keyring); + var faulty = keyring.Value.ThresholdSignaturePublicKeySet.Threshold; + var tpkePubKey = new PublicKey(keyring.Value.ThresholdSignaturePublicKeySet.SharedPublicKey.RawKey, faulty); var input = ContractEncoder.Encode(GovernanceInterface.MethodKeygenConfirm, cycle, - keyring!.Value.TpkePublicKey.ToBytes(), + tpkePubKey.ToBytes(), keyring!.Value.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToBytes()).ToArray()); var call = contractRegisterer.DecodeContract(context, ContractRegisterer.GovernanceContract, input); Assert.IsNotNull(call); var frame = new SystemContractExecutionFrame(call!, context, input, 100_000_000); - Assert.AreEqual(ExecutionStatus.Ok, contract.KeyGenConfirm(cycle, keyring!.Value.TpkePublicKey.ToBytes(), + Assert.AreEqual(ExecutionStatus.Ok, contract.KeyGenConfirm(cycle, tpkePubKey.ToBytes(), keyring!.Value.ThresholdSignaturePublicKeySet.Keys.Select(key => key.ToBytes()).ToArray(), frame)); // set keygen state - Assert.IsTrue(keygen.HandleConfirm(keyring!.Value.TpkePublicKey, + Assert.IsTrue(keygen.HandleConfirm(tpkePubKey, keyring!.Value.ThresholdSignaturePublicKeySet)); } diff --git a/test/Lachain.CoreTest/Resources/config.json b/test/Lachain.CoreTest/Resources/config.json index 847ae4f7e..95e7b779b 100644 --- a/test/Lachain.CoreTest/Resources/config.json +++ b/test/Lachain.CoreTest/Resources/config.json @@ -52,7 +52,13 @@ "hardfork_6": 10, "hardfork_7": 10, "hardfork_8": 10, - "hardfork_9": 1000 + "hardfork_9": 1000, + "hardfork_10": 0, + "hardfork_11": 0, + "hardfork_12": 0, + "hardfork_13": 0, + "hardfork_14": 0, + "hardfork_15": 0 }, "storage": { "provider": "RocksDB", diff --git a/test/Lachain.CoreTest/Resources/config2.json b/test/Lachain.CoreTest/Resources/config2.json index d5caec56e..e9ad640eb 100644 --- a/test/Lachain.CoreTest/Resources/config2.json +++ b/test/Lachain.CoreTest/Resources/config2.json @@ -52,7 +52,13 @@ "hardfork_6": 10, "hardfork_7": 10, "hardfork_8": 10, - "hardfork_9": 1000 + "hardfork_9": 1000, + "hardfork_10": 0, + "hardfork_11": 0, + "hardfork_12": 0, + "hardfork_13": 0, + "hardfork_14": 0, + "hardfork_15": 0 }, "storage": { "provider": "RocksDB", diff --git a/test/Lachain.CryptoTest/Resources/config.json b/test/Lachain.CryptoTest/Resources/config.json index 6ad311cf4..8308c9c1f 100644 --- a/test/Lachain.CryptoTest/Resources/config.json +++ b/test/Lachain.CryptoTest/Resources/config.json @@ -50,7 +50,13 @@ "hardfork_6": 10, "hardfork_7": 10, "hardfork_8": 10, - "hardfork_9": 1000 + "hardfork_9": 1000, + "hardfork_10": 0, + "hardfork_11": 0, + "hardfork_12": 0, + "hardfork_13": 0, + "hardfork_14": 0, + "hardfork_15": 0 }, "storage": { "provider": "RocksDB",