diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterApiSimulator.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterApiSimulator.cs index 8f8d8ceb00c..68616f48c68 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterApiSimulator.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterApiSimulator.cs @@ -38,7 +38,7 @@ public class ShutterApiSimulator( IFileSystem fileSystem, IKeyStoreConfig keyStoreConfig, IShutterConfig cfg, - Dictionary validatorsInfo, + ShutterValidatorsInfo validatorsInfo, Random rnd ) : ShutterApi(abiEncoder, blockTree, ecdsa, logFinder, receiptStorage, logManager, specProvider, timestamper, worldStateManager, fileSystem, diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterCryptoTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterCryptoTests.cs index 53b25b0fe96..4122084bf22 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterCryptoTests.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterCryptoTests.cs @@ -178,7 +178,7 @@ public void Can_decrypt_data(string cipherTextHex, string decryptionKeyHex, stri public void Can_verify_validator_registration_signature(string msgHex, string sigHex, string pkHex) { Assert.That(ShutterCrypto.CheckValidatorRegistrySignature( - Convert.FromHexString(pkHex), + new(Convert.FromHexString(pkHex)), Convert.FromHexString(sigHex), Convert.FromHexString(msgHex) )); diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterTestsCommon.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterTestsCommon.cs index 612414beca8..5f0689493b0 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterTestsCommon.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterTestsCommon.cs @@ -59,7 +59,7 @@ public static ShutterApiSimulator InitApi(Random rnd, ITimestamper? timestamper AbiEncoder, blockTree, Ecdsa, logFinder, receiptStorage, LogManager, SpecProvider, timestamper ?? Substitute.For(), worldStateManager, Substitute.For(), - Substitute.For(), Cfg, [], rnd + Substitute.For(), Cfg, new(), rnd ); } @@ -68,7 +68,7 @@ public static ShutterApiSimulator InitApi(Random rnd, MergeTestBlockchain chain, eventSimulator ?? InitEventSimulator(rnd), AbiEncoder, chain.BlockTree.AsReadOnly(), chain.EthereumEcdsa, chain.LogFinder, chain.ReceiptStorage, chain.LogManager, chain.SpecProvider, timestamper ?? chain.Timestamper, chain.WorldStateManager, - Substitute.For(), Substitute.For(), Cfg, [], rnd + Substitute.For(), Substitute.For(), Cfg, new(), rnd ); public static ShutterEventSimulator InitEventSimulator(Random rnd) diff --git a/src/Nethermind/Nethermind.Shutter/Config/ShutterValidatorsInfo.cs b/src/Nethermind/Nethermind.Shutter/Config/ShutterValidatorsInfo.cs new file mode 100644 index 00000000000..98211c92e07 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Config/ShutterValidatorsInfo.cs @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.IO; +using Nethermind.Crypto; +using Nethermind.Serialization.Json; + +using G1Affine = Nethermind.Crypto.Bls.P1Affine; + +namespace Nethermind.Shutter.Config; + +public class ShutterValidatorsInfo +{ + public bool IsEmpty { get => _indexToPubKeyBytes is null || _indexToPubKeyBytes.Count == 0; } + public IEnumerable ValidatorIndices { get => _indexToPubKeyBytes!.Keys; } + public class ShutterValidatorsInfoException(string message) : Exception(message); + + private Dictionary? _indexToPubKeyBytes; + private readonly Dictionary _indexToPubKey = []; + + public void Load(string fp) + { + FileStream fstream = new(fp, FileMode.Open, FileAccess.Read, FileShare.None); + _indexToPubKeyBytes = new EthereumJsonSerializer().Deserialize>(fstream); + } + + public void Validate() + { + G1Affine pk = new(stackalloc long[G1Affine.Sz]); + + foreach ((ulong index, byte[] pubkey) in _indexToPubKeyBytes!) + { + if (!pk.TryDecode(pubkey, out Bls.ERROR _)) + { + throw new ShutterValidatorsInfoException($"Validator info file contains invalid public key with index {index}."); + } + + _indexToPubKey.Add(index, pk.Point.ToArray()); + } + } + + public bool IsIndexRegistered(ulong index) + => _indexToPubKeyBytes!.ContainsKey(index); + + public G1Affine GetPubKey(ulong index) + => new(_indexToPubKey[index]); +} diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/IValidatorRegistryContract.cs b/src/Nethermind/Nethermind.Shutter/Contracts/IValidatorRegistryContract.cs index 01c99f531f4..9011d6f64a1 100644 --- a/src/Nethermind/Nethermind.Shutter/Contracts/IValidatorRegistryContract.cs +++ b/src/Nethermind/Nethermind.Shutter/Contracts/IValidatorRegistryContract.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Nethermind.Core; using Nethermind.Int256; +using Nethermind.Shutter.Config; using Update = (byte[] Message, byte[] Signature); namespace Nethermind.Shutter.Contracts; @@ -15,7 +16,7 @@ public interface IValidatorRegistryContract /// /// /// - bool IsRegistered(BlockHeader header, in Dictionary validatorsInfo, out HashSet unregistered); + bool IsRegistered(in BlockHeader header, in ShutterValidatorsInfo validatorsInfo, out HashSet unregistered); /// /// Returns the number of previous updates to the registry. diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/ValidatorRegistryContract.cs b/src/Nethermind/Nethermind.Shutter/Contracts/ValidatorRegistryContract.cs index fee095e8d4a..9270a90ba32 100644 --- a/src/Nethermind/Nethermind.Shutter/Contracts/ValidatorRegistryContract.cs +++ b/src/Nethermind/Nethermind.Shutter/Contracts/ValidatorRegistryContract.cs @@ -13,6 +13,7 @@ using Nethermind.Core.Extensions; using Update = (byte[] Message, byte[] Signature); using Nethermind.Crypto; +using Nethermind.Shutter.Config; namespace Nethermind.Shutter.Contracts; @@ -32,14 +33,14 @@ public class ValidatorRegistryContract( public Update GetUpdate(BlockHeader header, in UInt256 i) => (Update)Call(header, nameof(GetUpdate), Address.Zero, [i])[0]; - public bool IsRegistered(BlockHeader header, in Dictionary validatorsInfo, out HashSet unregistered) + public bool IsRegistered(in BlockHeader header, in ShutterValidatorsInfo validatorsInfo, out HashSet unregistered) { Dictionary nonces = []; unregistered = []; - foreach (KeyValuePair validatorInfo in validatorsInfo) + foreach (ulong index in validatorsInfo.ValidatorIndices) { - nonces.Add(validatorInfo.Key, null); - unregistered.Add(validatorInfo.Key); + nonces.Add(index, null); + unregistered.Add(index); } uint updates = (uint)GetNumUpdates(header); @@ -56,7 +57,7 @@ public bool IsRegistered(BlockHeader header, in Dictionary valida Message msg = new(update.Message.AsSpan()); // skip untracked validators - if (!validatorsInfo.ContainsKey(msg.ValidatorIndex)) + if (!validatorsInfo.IsIndexRegistered(msg.ValidatorIndex)) { continue; } @@ -85,7 +86,7 @@ public bool IsRegistered(BlockHeader header, in Dictionary valida continue; } - if (!ShutterCrypto.CheckValidatorRegistrySignature(validatorsInfo[msg.ValidatorIndex], update.Signature, update.Message)) + if (!ShutterCrypto.CheckValidatorRegistrySignature(validatorsInfo.GetPubKey(msg.ValidatorIndex), update.Signature, update.Message)) { if (_logger.IsDebug) _logger.Debug("Registration message has invalid signature."); continue; diff --git a/src/Nethermind/Nethermind.Shutter/ShutterApi.cs b/src/Nethermind/Nethermind.Shutter/ShutterApi.cs index 7c87a7bf7a7..72e6968a31b 100644 --- a/src/Nethermind/Nethermind.Shutter/ShutterApi.cs +++ b/src/Nethermind/Nethermind.Shutter/ShutterApi.cs @@ -63,7 +63,7 @@ public ShutterApi( IFileSystem fileSystem, IKeyStoreConfig keyStoreConfig, IShutterConfig cfg, - Dictionary validatorsInfo, + ShutterValidatorsInfo validatorsInfo, TimeSpan slotLength, IPAddress ip ) diff --git a/src/Nethermind/Nethermind.Shutter/ShutterBlockHandler.cs b/src/Nethermind/Nethermind.Shutter/ShutterBlockHandler.cs index 3e1be55af16..68f4c7d1e3c 100644 --- a/src/Nethermind/Nethermind.Shutter/ShutterBlockHandler.cs +++ b/src/Nethermind/Nethermind.Shutter/ShutterBlockHandler.cs @@ -27,7 +27,7 @@ public class ShutterBlockHandler : IShutterBlockHandler private readonly IShutterEon _eon; private readonly IReceiptFinder _receiptFinder; private readonly ShutterTxLoader _txLoader; - private readonly Dictionary _validatorsInfo; + private readonly ShutterValidatorsInfo _validatorsInfo; private readonly ILogManager _logManager; private readonly IAbiEncoder _abiEncoder; private readonly IBlockTree _blockTree; @@ -50,7 +50,7 @@ public ShutterBlockHandler( IBlockTree blockTree, IAbiEncoder abiEncoder, IReceiptFinder receiptFinder, - Dictionary validatorsInfo, + ShutterValidatorsInfo validatorsInfo, IShutterEon eon, ShutterTxLoader txLoader, ShutterTime time, @@ -190,9 +190,9 @@ private void OnNewHeadBlock(object? _, BlockEventArgs e) } - private void CheckAllValidatorsRegistered(BlockHeader parent, Dictionary validatorsInfo) + private void CheckAllValidatorsRegistered(in BlockHeader parent, in ShutterValidatorsInfo validatorsInfo) { - if (validatorsInfo.Count == 0) + if (validatorsInfo.IsEmpty) { return; } diff --git a/src/Nethermind/Nethermind.Shutter/ShutterCrypto.cs b/src/Nethermind/Nethermind.Shutter/ShutterCrypto.cs index 9afab67f631..5bacab9c531 100644 --- a/src/Nethermind/Nethermind.Shutter/ShutterCrypto.cs +++ b/src/Nethermind/Nethermind.Shutter/ShutterCrypto.cs @@ -217,13 +217,8 @@ public static bool CheckSlotDecryptionIdentitiesSignature( } [SkipLocalsInit] - public static bool CheckValidatorRegistrySignature(ReadOnlySpan pkBytes, ReadOnlySpan sigBytes, ReadOnlySpan msgBytes) - { - ValueHash256 h = ValueKeccak.Compute(msgBytes); - G1Affine pk = new(stackalloc long[G1Affine.Sz]); - pk.Decode(pkBytes); - return BlsSigner.Verify(pk, sigBytes, h.Bytes); - } + public static bool CheckValidatorRegistrySignature(G1Affine pk, ReadOnlySpan sigBytes, ReadOnlySpan msgBytes) + => BlsSigner.Verify(pk, sigBytes, ValueKeccak.Compute(msgBytes).Bytes); public static EncryptedMessage Encrypt(ReadOnlySpan msg, G1 identity, G2 eonKey, ReadOnlySpan sigma) { diff --git a/src/Nethermind/Nethermind.Shutter/ShutterPlugin.cs b/src/Nethermind/Nethermind.Shutter/ShutterPlugin.cs index bc75443016c..3f2e389463b 100644 --- a/src/Nethermind/Nethermind.Shutter/ShutterPlugin.cs +++ b/src/Nethermind/Nethermind.Shutter/ShutterPlugin.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; using System.Threading.Tasks; using Nethermind.Api; using Nethermind.Api.Extensions; @@ -12,8 +11,6 @@ using Nethermind.Shutter.Config; using Nethermind.Merge.Plugin; using Nethermind.Logging; -using System.IO; -using Nethermind.Serialization.Json; using System.Threading; using Nethermind.Config; using Multiformats.Address; @@ -86,12 +83,13 @@ public IBlockProducer InitBlockProducer(IBlockProducerFactory consensusPlugin, I throw new ShutterLoadingException("Invalid Shutter config", e); } - Dictionary validatorsInfo = []; + ShutterValidatorsInfo validatorsInfo = new(); if (_shutterConfig!.ValidatorInfoFile is not null) { try { - validatorsInfo = LoadValidatorInfo(_shutterConfig!.ValidatorInfoFile); + validatorsInfo.Load(_shutterConfig!.ValidatorInfoFile); + validatorsInfo.Validate(); } catch (Exception e) { @@ -135,10 +133,4 @@ public async ValueTask DisposeAsync() _cts.Dispose(); await (_shutterApi?.DisposeAsync() ?? default); } - - private static Dictionary LoadValidatorInfo(string fp) - { - FileStream fstream = new(fp, FileMode.Open, FileAccess.Read, FileShare.None); - return new EthereumJsonSerializer().Deserialize>(fstream); - } }