Skip to content

Commit

Permalink
Shutter: validator registry V1 (#7682)
Browse files Browse the repository at this point in the history
  • Loading branch information
Marchhill authored Nov 1, 2024
1 parent d2f4b15 commit 5af8f17
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 77 deletions.
5 changes: 5 additions & 0 deletions src/Nethermind/Nethermind.Crypto/BlsSigner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public static Signature Sign(Span<long> buf, Bls.SecretKey sk, ReadOnlySpan<byte
public static Signature Sign(Bls.SecretKey sk, ReadOnlySpan<byte> message)
=> Sign(new long[G2.Sz], sk, message);

[SkipLocalsInit]
public static bool Verify(G1Affine publicKey, Signature signature, ReadOnlySpan<byte> message)
{
int len = 2 * GT.Sz;
Expand Down Expand Up @@ -126,6 +127,9 @@ public AggregatedPublicKey(Span<long> buf)
public void FromSk(Bls.SecretKey sk)
=> _point.FromSk(sk);

public void Reset()
=> _point.Zero();

public bool TryDecode(ReadOnlySpan<byte> publicKeyBytes, out Bls.ERROR err)
=> _point.TryDecode(publicKeyBytes, out err);

Expand All @@ -138,6 +142,7 @@ public void Aggregate(G1Affine publicKey)
public void Aggregate(AggregatedPublicKey aggregatedPublicKey)
=> _point.Aggregate(aggregatedPublicKey.PublicKey);

[SkipLocalsInit]
public bool TryAggregate(ReadOnlySpan<byte> publicKeyBytes, out Bls.ERROR err)
{
G1Affine pk = new(stackalloc long[G1Affine.Sz]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,4 @@ public void Ignores_outdated_block()
api.TriggerNewHeadBlock(new(Build.A.Block.WithTimestamp(upToDateTimestamp).TestObject));
Assert.That(api.EonUpdateCalled, Is.EqualTo(1));
}

}
9 changes: 4 additions & 5 deletions src/Nethermind/Nethermind.Shutter.Test/ShutterCryptoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,10 @@ 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(
new(Convert.FromHexString(pkHex)),
Convert.FromHexString(sigHex),
Convert.FromHexString(msgHex)
));
BlsSigner.AggregatedPublicKey pk = new();
pk.Decode(Convert.FromHexString(pkHex));

Assert.That(ShutterCrypto.CheckValidatorRegistrySignatures(pk, Convert.FromHexString(sigHex), Convert.FromHexString(msgHex)));
}

[Test]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using NUnit.Framework;
using Nethermind.Core;
using System;
using Nethermind.Shutter.Contracts;
using NSubstitute;
using Nethermind.Evm.TransactionProcessing;
using Nethermind.Logging;
using System.Collections.Generic;
using Nethermind.Shutter.Config;
using Nethermind.Crypto;
using Nethermind.Core.Crypto;

using Update = (byte[] Message, byte[] Signature);
using G1 = Nethermind.Crypto.Bls.P1;

namespace Nethermind.Shutter.Test;

[TestFixture]
class ShutterValidatorRegistryTests
{
private static readonly byte[] SkBytes = [0x2c, 0xd4, 0xba, 0x40, 0x6b, 0x52, 0x24, 0x59, 0xd5, 0x7a, 0x0b, 0xed, 0x51, 0xa3, 0x97, 0x43, 0x5c, 0x0b, 0xb1, 0x1d, 0xd5, 0xf3, 0xca, 0x11, 0x52, 0xb3, 0x69, 0x4b, 0xb9, 0x1d, 0x7c, 0x22];

[Test]
public void Can_check_if_registered()
{
ValidatorRegistryContract contract = new(
Substitute.For<ITransactionProcessor>(),
ShutterTestsCommon.AbiEncoder,
Address.Zero,
LimboLogs.Instance,
ShutterTestsCommon.ChainId,
1);
ShutterValidatorsInfo validatorsInfo = new();
List<(uint, Update)> updates = [];

// populate validatorsInfo
G1 pk = new();
for (ulong i = 100; i < 110; i++)
{
Bls.SecretKey sk = GetSecretKeyForIndex((uint)i);
pk.FromSk(sk);
validatorsInfo.Add(i, pk.ToAffine().Point.ToArray());
}

// register all 10, then deregister last 5
updates.Add((0, CreateUpdate(100, 10, 0, true)));
updates.Add((1, CreateUpdate(105, 5, 1, false)));

// invalid updates should be ignored
updates.Add((2, CreateUpdate(100, 10, 0, false))); // invalid nonce
updates.Add((3, CreateUpdate(50, 50, 0, true))); // not in validatorsInfo

// bad signature
Update badUpdate = CreateUpdate(100, 10, 2, true);
badUpdate.Signature[34] += 1;
updates.Add((4, badUpdate));

Assert.Multiple(() =>
{
Assert.That(!contract.IsRegistered(updates, validatorsInfo, out HashSet<ulong> unregistered));
Assert.That(unregistered, Has.Count.EqualTo(5));
});
}

private static Update CreateUpdate(ulong startIndex, uint count, uint nonce, bool isRegistration)
{
ValidatorRegistryContract.Message msg = new()
{
Version = 1,
ChainId = ShutterTestsCommon.ChainId,
ContractAddress = Address.Zero.Bytes,
StartValidatorIndex = startIndex,
Count = count,
Nonce = nonce,
IsRegistration = isRegistration
};
byte[] msgBytes = msg.Encode();
ReadOnlySpan<byte> msgHash = ValueKeccak.Compute(msgBytes).Bytes;

BlsSigner.Signature agg = new();
BlsSigner.Signature s = new();

ulong endIndex = startIndex + count;
for (ulong i = startIndex; i < endIndex; i++)
{
Bls.SecretKey sk = GetSecretKeyForIndex((uint)i);
s.Sign(sk, msgHash);
agg.Aggregate(s);
}

return (msgBytes, agg.Bytes.ToArray());
}

private static Bls.SecretKey GetSecretKeyForIndex(uint index)
{
// n.b. doesn't have to derive from master key, just done for convenience
Bls.SecretKey masterSk = new(SkBytes, Bls.ByteOrder.LittleEndian);
return new(masterSk, index);
}
}
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Shutter/Config/IShutterConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public interface IShutterConfig : IConfig
string ShutterKeyFile { get; set; }

[ConfigItem(Description = "The Shutter validator registry message version.",
DefaultValue = "0", HiddenFromDocs = true)]
DefaultValue = "1", HiddenFromDocs = true)]
ulong ValidatorRegistryMessageVersion { get; set; }

[ConfigItem(Description = "The maximum amount of gas to use on Shutter transactions.",
Expand Down
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Shutter/Config/ShutterConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class ShutterConfig : IShutterConfig
public string? P2PProtocolVersion { get; set; } = "/shutter/0.1.0";
public string? P2PAgentVersion { get; set; } = "github.com/shutter-network/rolling-shutter/rolling-shutter";
public string ShutterKeyFile { get; set; } = "shutter.key.plain";
public ulong ValidatorRegistryMessageVersion { get; set; } = 0;
public ulong ValidatorRegistryMessageVersion { get; set; } = 1;
public ulong InstanceID { get; set; } = 0;
public int EncryptedGasLimit { get; set; } = 10000000;
public ushort MaxKeyDelay { get; set; } = 1666;
Expand Down
44 changes: 28 additions & 16 deletions src/Nethermind/Nethermind.Shutter/Config/ShutterValidatorsInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,56 @@
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<ulong> ValidatorIndices { get => _indexToPubKeyBytes!.Keys; }
public bool IsEmpty { get => _indexToPubKey is null || _indexToPubKey.Count == 0; }
public IEnumerable<ulong> ValidatorIndices { get => _indexToPubKey!.Keys; }
public class ShutterValidatorsInfoException(string message) : Exception(message);

private Dictionary<ulong, byte[]>? _indexToPubKeyBytes;
private readonly Dictionary<ulong, long[]> _indexToPubKey = [];
protected readonly Dictionary<ulong, long[]> _indexToPubKey = [];
protected ulong _minIndex = ulong.MaxValue;
protected ulong _maxIndex = ulong.MinValue;

public void Load(string fp)
{
FileStream fstream = new(fp, FileMode.Open, FileAccess.Read, FileShare.None);
_indexToPubKeyBytes = new EthereumJsonSerializer().Deserialize<Dictionary<ulong, byte[]>>(fstream);
FileStream fstream = new(fp, FileMode.Open, FileAccess.Read, FileShare.Read);
Dictionary<ulong, byte[]> indexToPubKeyBytes = new EthereumJsonSerializer().Deserialize<Dictionary<ulong, byte[]>>(fstream);
AddPublicKeys(indexToPubKeyBytes);
}

public void Validate()
public bool ContainsIndex(ulong index)
=> _indexToPubKey!.ContainsKey(index);

// non inclusive of end index
public bool MayContainIndexInRange(ulong startIndex, ulong endIndex)
=> (endIndex <= _maxIndex && endIndex > _minIndex) || (startIndex < _maxIndex && startIndex >= _minIndex);

public G1Affine GetPubKey(ulong index)
=> new(_indexToPubKey[index]);

internal void Add(ulong index, long[] pubkey)
{
_indexToPubKey.Add(index, pubkey);
_minIndex = Math.Min(_minIndex, index);
_maxIndex = Math.Max(_maxIndex, index + 1);
}

private void AddPublicKeys(Dictionary<ulong, byte[]> indexToPubKeyBytes)
{
G1Affine pk = new(stackalloc long[G1Affine.Sz]);

foreach ((ulong index, byte[] pubkey) in _indexToPubKeyBytes!)
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());
Add(index, pk.Point.ToArray());
}
}

public bool IsIndexRegistered(ulong index)
=> _indexToPubKeyBytes!.ContainsKey(index);

public G1Affine GetPubKey(ulong index)
=> new(_indexToPubKey[index]);
}
Loading

0 comments on commit 5af8f17

Please sign in to comment.