Skip to content

Commit

Permalink
Bls Signature refactoring & performance (#7678)
Browse files Browse the repository at this point in the history
  • Loading branch information
Marchhill authored Oct 29, 2024
1 parent 65e0158 commit 332ed74
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 94 deletions.
2 changes: 1 addition & 1 deletion src/Nethermind/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageVersion Include="Nethermind.Crypto.Bls" Version="1.0.3" />
<PackageVersion Include="Nethermind.Crypto.Bls" Version="1.0.4" />
<PackageVersion Include="Nethermind.Crypto.Pairings" Version="1.1.1" />
<PackageVersion Include="Nethermind.Crypto.SecP256k1" Version="1.2.2" />
<PackageVersion Include="Nethermind.DotNetty.Buffers" Version="1.0.1" />
Expand Down
71 changes: 40 additions & 31 deletions src/Nethermind/Nethermind.Core.Test/Crypto/BlsSignerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public class BlsTests
public void Calculate_signature()
{
byte[] expected = [0xa5, 0xa0, 0x0d, 0xe9, 0x9d, 0x8f, 0xee, 0x7e, 0x28, 0x81, 0x1b, 0x2c, 0x08, 0xe0, 0xa7, 0xfc, 0x00, 0xa1, 0x10, 0x0c, 0x3d, 0x0f, 0x80, 0x51, 0x9d, 0x43, 0x24, 0x67, 0x1c, 0x29, 0x36, 0xb1, 0xe5, 0xa5, 0x87, 0x7d, 0x46, 0x7a, 0x6d, 0xc6, 0xf5, 0x92, 0xb2, 0x40, 0x7b, 0xcb, 0x12, 0x61, 0x0c, 0x18, 0x8a, 0x6c, 0xdf, 0x57, 0xd1, 0x77, 0x92, 0x00, 0x0f, 0xf7, 0x56, 0xf8, 0x0e, 0xbe, 0xd8, 0x00, 0x88, 0xab, 0x22, 0x9a, 0xa7, 0xe2, 0xc3, 0x24, 0x09, 0xec, 0xfe, 0x5a, 0x8d, 0x44, 0x73, 0xe9, 0x12, 0xfa, 0x19, 0x9e, 0xee, 0xa1, 0x8f, 0x3c, 0x79, 0x8d, 0xc5, 0x28, 0x64, 0x7d];
BlsSigner.Signature s = BlsSigner.Sign(new(SkBytes, Bls.ByteOrder.LittleEndian), MsgBytes);
Bls.SecretKey sk = new(SkBytes, Bls.ByteOrder.LittleEndian);
BlsSigner.Signature s = BlsSigner.Sign(sk, MsgBytes);
s.Bytes.ToArray().Should().Equal(expected);
}

Expand All @@ -38,40 +39,63 @@ public void Verify_signature()
[Test]
public void Verify_aggregate_signature()
{
Span<byte> skBytes = new byte[AggregateSignerCount * 32];
Span<byte> publicKeys = new byte[AggregateSignerCount * BlsSigner.PkCompressedSz];
BlsSigner.Signature agg = new();
BlsSigner.Signature s = new();
BlsSigner.AggregatedPublicKey aggregatedPublicKey = new();
G1 pk = new();

Bls.SecretKey masterSk = new(SkBytes, Bls.ByteOrder.LittleEndian);

GenerateKeys(skBytes, publicKeys);
for (int i = 0; i < AggregateSignerCount; i++)
{
Bls.SecretKey sk = new(masterSk, (uint)i);
s.Sign(sk, MsgBytes);
agg.Aggregate(s);
pk.FromSk(sk);
aggregatedPublicKey.Aggregate(pk.ToAffine());
}

BlsSigner.Signature s = BlsSigner.SignAggregate(skBytes, MsgBytes);
Assert.That(BlsSigner.VerifyAggregate(publicKeys, s, MsgBytes));
Assert.That(BlsSigner.VerifyAggregate(aggregatedPublicKey, agg, MsgBytes));
}

[Test]
public void Rejects_bad_signature()
{
Bls.SecretKey sk = new(SkBytes, Bls.ByteOrder.LittleEndian);
BlsSigner.Signature s = BlsSigner.Sign(sk, MsgBytes);
Span<byte> bytes = stackalloc byte[96];
s.Bytes.CopyTo(bytes);
bytes[34] += 1;
BlsSigner.Signature bad = new(bytes);
Span<byte> badSig = stackalloc byte[96];
s.Bytes.CopyTo(badSig);
badSig[34] += 1;

G1 publicKey = new();
publicKey.FromSk(sk);
Assert.That(BlsSigner.Verify(publicKey.ToAffine(), bad, MsgBytes), Is.False);
Assert.That(BlsSigner.Verify(publicKey.ToAffine(), badSig, MsgBytes), Is.False);
}

[Test]
public void Rejects_missing_aggregate_signature()
{
Span<byte> skBytes = new byte[AggregateSignerCount * 32];
Span<byte> publicKeys = new byte[AggregateSignerCount * BlsSigner.PkCompressedSz];
BlsSigner.Signature agg = new();
BlsSigner.Signature s = new();
BlsSigner.AggregatedPublicKey aggregatedPublicKey = new();
G1 pk = new();

GenerateKeys(skBytes, publicKeys);
Bls.SecretKey masterSk = new(SkBytes, Bls.ByteOrder.LittleEndian);

BlsSigner.Signature s = BlsSigner.SignAggregate(skBytes[32..], MsgBytes);
Assert.That(BlsSigner.VerifyAggregate(publicKeys, s, MsgBytes), Is.False);
for (int i = 0; i < AggregateSignerCount; i++)
{
Bls.SecretKey sk = new(masterSk, (uint)i);
s.Sign(sk, MsgBytes);
if (i != 0)
{
// exclude one signature
agg.Aggregate(s);
}
pk.FromSk(sk);
aggregatedPublicKey.Aggregate(pk.ToAffine());
}

Assert.That(BlsSigner.VerifyAggregate(aggregatedPublicKey, agg, MsgBytes), Is.False);
}

[Test]
Expand All @@ -84,19 +108,4 @@ public void Public_key_from_private_key()

Assert.That(publicKey.Compress(), Is.EqualTo(expected));
}

private void GenerateKeys(Span<byte> skBytes, Span<byte> publicKeyBytes)
{
Bls.SecretKey masterSk = new(SkBytes, Bls.ByteOrder.LittleEndian);
for (int i = 0; i < AggregateSignerCount; i++)
{
int offset = i * 32;
Bls.SecretKey sk = new(masterSk, (uint)i);
sk.ToBendian().CopyTo(skBytes[offset..(offset + 32)]);

G1 publicKey = new();
publicKey.FromSk(sk);
publicKey.Compress().CopyTo(publicKeyBytes[(i * BlsSigner.PkCompressedSz)..]);
}
}
}
150 changes: 93 additions & 57 deletions src/Nethermind/Nethermind.Crypto/BlsSigner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,96 +23,132 @@ public static class BlsSigner
private const int InputLength = 64;

[SkipLocalsInit]
// buf must be of size G2.Sz
public static Signature Sign(Span<long> buf, Bls.SecretKey sk, ReadOnlySpan<byte> message)
{
if (buf.Length != G2.Sz)
{
throw new ArgumentException($"Signature buffer {nameof(buf)} must be of size {G2.Sz}.");
}

G2 p = new(buf);
Signature s = new(p);
s.Sign(sk, message);
return s;
}

public static Signature Sign(Bls.SecretKey sk, ReadOnlySpan<byte> message)
=> Sign(new long[G2.Sz], sk, message);

public static bool Verify(G1Affine publicKey, Signature signature, ReadOnlySpan<byte> message)
{
G2 p = new(stackalloc long[G2.Sz]);
p.HashTo(message, Cryptosuite);
p.SignWith(sk);
return new(p.Compress());
int len = 2 * GT.Sz;
using ArrayPoolList<long> buf = new(len, len);

GT p1 = new(buf.AsSpan()[..GT.Sz]);
p1.MillerLoop(signature.Point, G1Affine.Generator(stackalloc long[G1Affine.Sz]));

G2 m = new(stackalloc long[G2.Sz]);
m.HashTo(message, Cryptosuite);
GT p2 = new(buf.AsSpan()[GT.Sz..]);
p2.MillerLoop(m.ToAffine(), publicKey);

return GT.FinalVerify(p1, p2);
}

[SkipLocalsInit]
public static Signature SignAggregate(ReadOnlySpan<byte> skBytes, ReadOnlySpan<byte> message)
public static bool Verify(G1Affine publicKey, ReadOnlySpan<byte> sigBytes, ReadOnlySpan<byte> message)
{
if (skBytes.Length % 32 != 0)
{
throw new Bls.BlsException(Bls.ERROR.WRONGSIZE);
}

G2 p = new(stackalloc long[G2.Sz]);
G2 agg = new(stackalloc long[G2.Sz]);
agg.Zero();
Signature s = new(p);

for (int i = 0; i < skBytes.Length; i += 32)
if (!p.TryDecode(sigBytes, out _))
{
Bls.SecretKey sk = new(skBytes.Slice(i, 32));
p.HashTo(message, Cryptosuite);
p.SignWith(sk);
agg.Aggregate(p.ToAffine());
return false;
}

return new(agg.Compress());
return Verify(publicKey, s, message);
}

[SkipLocalsInit]
public static bool Verify(G1Affine publicKey, Signature signature, ReadOnlySpan<byte> message)
public static bool VerifyAggregate(AggregatedPublicKey aggregatedPublicKey, Signature signature, ReadOnlySpan<byte> message)
=> Verify(aggregatedPublicKey.PublicKey, signature, message);

public readonly ref struct Signature
{
int len = 2 * GT.Sz;
using ArrayPoolList<long> buf = new(len, len);
try
{
G2Affine sig = new(stackalloc long[G2Affine.Sz]);
sig.Decode(signature.Bytes);
GT p1 = new(buf.AsSpan()[..GT.Sz]);
p1.MillerLoop(sig, G1Affine.Generator(stackalloc long[G1Affine.Sz]));
public const int Sz = 96;
public readonly ReadOnlySpan<byte> Bytes { get => _point.Compress(); }
public readonly G2Affine Point { get => _point.ToAffine(); }
private readonly G2 _point;

G2 m = new(stackalloc long[G2.Sz]);
m.HashTo(message, Cryptosuite);
GT p2 = new(buf.AsSpan()[GT.Sz..]);
p2.MillerLoop(m.ToAffine(), publicKey);
public Signature()
{
_point = new();
}

return GT.FinalVerify(p1, p2);
public Signature(G2 point)
{
_point = point;
}
catch (Bls.BlsException)

public void Decode(ReadOnlySpan<byte> bytes)
=> _point.Decode(bytes);

public void Sign(Bls.SecretKey sk, ReadOnlySpan<byte> message)
{
// point not on curve
return false;
_point.HashTo(message, Cryptosuite);
_point.SignWith(sk);
}

public void Aggregate(Signature s)
=> _point.Aggregate(s.Point);
}

[SkipLocalsInit]
public static bool VerifyAggregate(ReadOnlySpan<byte> publicKeyBytes, Signature signature, ReadOnlySpan<byte> message)
public readonly ref struct AggregatedPublicKey
{
if (publicKeyBytes.Length % PkCompressedSz != 0)
public G1Affine PublicKey { get => _point.ToAffine(); }
private readonly G1 _point;

public AggregatedPublicKey()
{
throw new Bls.BlsException(Bls.ERROR.WRONGSIZE);
_point = new();
}

G1Affine pk = new(stackalloc long[G1Affine.Sz]);
G1 agg = new(stackalloc long[G1.Sz]);
agg.Zero();

for (int i = 0; i < publicKeyBytes.Length; i += PkCompressedSz)
public AggregatedPublicKey(Span<long> buf)
{
pk.Decode(publicKeyBytes.Slice(i, PkCompressedSz));
agg.Aggregate(pk);
if (buf.Length != G1.Sz)
{
throw new ArgumentException($"Public key buffer {nameof(buf)} must be of size {G1.Sz}.");
}

_point = new(buf);
}

return Verify(agg.ToAffine(), signature, message);
}
public void FromSk(Bls.SecretKey sk)
=> _point.FromSk(sk);

// Compressed G2 point
public readonly ref struct Signature()
{
public readonly ReadOnlySpan<byte> Bytes;
public bool TryDecode(ReadOnlySpan<byte> publicKeyBytes, out Bls.ERROR err)
=> _point.TryDecode(publicKeyBytes, out err);

public Signature(ReadOnlySpan<byte> s) : this()
public void Decode(ReadOnlySpan<byte> publicKeyBytes)
=> _point.Decode(publicKeyBytes);

public void Aggregate(G1Affine publicKey)
=> _point.Aggregate(publicKey);

public void Aggregate(AggregatedPublicKey aggregatedPublicKey)
=> _point.Aggregate(aggregatedPublicKey.PublicKey);

public bool TryAggregate(ReadOnlySpan<byte> publicKeyBytes, out Bls.ERROR err)
{
if (s.Length != 96)
G1Affine pk = new(stackalloc long[G1Affine.Sz]);

if (!pk.TryDecode(publicKeyBytes, out err))
{
throw new Bls.BlsException(Bls.ERROR.BADENCODING);
return false;
}
Bytes = s;

Aggregate(pk);
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Collections.Generic;
using Nethermind.Core.Extensions;
using Update = (byte[] Message, byte[] Signature);
using Nethermind.Crypto;

namespace Nethermind.Shutter.Contracts;

Expand Down Expand Up @@ -46,7 +47,7 @@ public bool IsRegistered(BlockHeader header, in Dictionary<ulong, byte[]> valida
{
Update update = GetUpdate(header, updates - i - 1);

if (update.Message.Length != 46 || update.Signature.Length != 96)
if (update.Message.Length != Message.Sz || update.Signature.Length != BlsSigner.Signature.Sz)
{
if (_logger.IsDebug) _logger.Debug("Registration message was wrong length.");
continue;
Expand Down Expand Up @@ -108,6 +109,7 @@ public bool IsRegistered(BlockHeader header, in Dictionary<ulong, byte[]> valida

private readonly ref struct Message
{
public const int Sz = 46;
public readonly byte Version;
public readonly ulong ChainId;
public readonly ReadOnlySpan<byte> ContractAddress;
Expand All @@ -117,7 +119,7 @@ private readonly ref struct Message

public Message(Span<byte> encodedMessage)
{
if (encodedMessage.Length != 46)
if (encodedMessage.Length != Sz)
{
throw new ArgumentException("Validator registry contract message was wrong length.");
}
Expand Down
4 changes: 1 addition & 3 deletions src/Nethermind/Nethermind.Shutter/ShutterCrypto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,10 @@ public static bool CheckSlotDecryptionIdentitiesSignature(
[SkipLocalsInit]
public static bool CheckValidatorRegistrySignature(ReadOnlySpan<byte> pkBytes, ReadOnlySpan<byte> sigBytes, ReadOnlySpan<byte> msgBytes)
{
BlsSigner.Signature sig = new(sigBytes);
ValueHash256 h = ValueKeccak.Compute(msgBytes);

G1Affine pk = new(stackalloc long[G1Affine.Sz]);
pk.Decode(pkBytes);
return BlsSigner.Verify(pk, sig, h.Bytes);
return BlsSigner.Verify(pk, sigBytes, h.Bytes);
}

public static EncryptedMessage Encrypt(ReadOnlySpan<byte> msg, G1 identity, G2 eonKey, ReadOnlySpan<byte> sigma)
Expand Down

0 comments on commit 332ed74

Please sign in to comment.