diff --git a/src/Nethermind/Nethermind.Core.Test/Crypto/BlsSignerTests.cs b/src/Nethermind/Nethermind.Core.Test/Crypto/BlsSignerTests.cs index 8fd58d46c4c..7662ce30187 100644 --- a/src/Nethermind/Nethermind.Core.Test/Crypto/BlsSignerTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Crypto/BlsSignerTests.cs @@ -15,6 +15,7 @@ public class BlsTests { 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]; private static readonly byte[] MsgBytes = [0x3e, 0x00, 0xef, 0x2f, 0x89, 0x5f, 0x40, 0xd6, 0x7f, 0x5b, 0xb8, 0xe8, 0x1f, 0x09, 0xa5, 0xa1, 0x2c, 0x84, 0x0e, 0xc3, 0xce, 0x9a, 0x7f, 0x3b, 0x18, 0x1b, 0xe1, 0x88, 0xef, 0x71, 0x1a, 0x1e]; + private static int AggregateSignerCount = 100; [Test] public void Calculate_signature() @@ -34,6 +35,18 @@ public void Verify_signature() Assert.That(BlsSigner.Verify(publicKey.ToAffine(), s, MsgBytes)); } + [Test] + public void Verify_aggregate_signature() + { + Span skBytes = new byte[AggregateSignerCount * 32]; + Span publicKeys = new byte[AggregateSignerCount * BlsSigner.PkCompressedSz]; + + GenerateKeys(skBytes, publicKeys); + + BlsSigner.Signature s = BlsSigner.SignAggregate(skBytes, MsgBytes); + Assert.That(BlsSigner.VerifyAggregate(publicKeys, s, MsgBytes)); + } + [Test] public void Rejects_bad_signature() { @@ -49,6 +62,18 @@ public void Rejects_bad_signature() Assert.That(BlsSigner.Verify(publicKey.ToAffine(), bad, MsgBytes), Is.False); } + [Test] + public void Rejects_missing_aggregate_signature() + { + Span skBytes = new byte[AggregateSignerCount * 32]; + Span publicKeys = new byte[AggregateSignerCount * BlsSigner.PkCompressedSz]; + + GenerateKeys(skBytes, publicKeys); + + BlsSigner.Signature s = BlsSigner.SignAggregate(skBytes[32..], MsgBytes); + Assert.That(BlsSigner.VerifyAggregate(publicKeys, s, MsgBytes), Is.False); + } + [Test] public void Public_key_from_private_key() { @@ -59,4 +84,19 @@ public void Public_key_from_private_key() Assert.That(publicKey.Compress(), Is.EqualTo(expected)); } + + private void GenerateKeys(Span skBytes, Span 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)..]); + } + } } diff --git a/src/Nethermind/Nethermind.Crypto/BlsSigner.cs b/src/Nethermind/Nethermind.Crypto/BlsSigner.cs index 77e3baebc47..f0c4d672c7f 100644 --- a/src/Nethermind/Nethermind.Crypto/BlsSigner.cs +++ b/src/Nethermind/Nethermind.Crypto/BlsSigner.cs @@ -8,6 +8,9 @@ namespace Nethermind.Crypto; +// https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html + +using G1 = Bls.P1; using G1Affine = Bls.P1Affine; using G2 = Bls.P2; using G2Affine = Bls.P2Affine; @@ -15,6 +18,7 @@ namespace Nethermind.Crypto; public static class BlsSigner { + public const int PkCompressedSz = 384 / 8; private static readonly byte[] Cryptosuite = Encoding.UTF8.GetBytes("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"); private const int InputLength = 64; @@ -27,6 +31,29 @@ public static Signature Sign(Bls.SecretKey sk, ReadOnlySpan message) return new(p.Compress()); } + [SkipLocalsInit] + public static Signature SignAggregate(ReadOnlySpan skBytes, ReadOnlySpan 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(); + + for (int i = 0; i < skBytes.Length; i += 32) + { + Bls.SecretKey sk = new(skBytes.Slice(i, 32)); + p.HashTo(message, Cryptosuite); + p.SignWith(sk); + agg.Aggregate(p.ToAffine()); + } + + return new(agg.Compress()); + } + [SkipLocalsInit] public static bool Verify(G1Affine publicKey, Signature signature, ReadOnlySpan message) { @@ -53,6 +80,27 @@ public static bool Verify(G1Affine publicKey, Signature signature, ReadOnlySpan< } } + [SkipLocalsInit] + public static bool VerifyAggregate(ReadOnlySpan publicKeyBytes, Signature signature, ReadOnlySpan message) + { + if (publicKeyBytes.Length % PkCompressedSz != 0) + { + throw new Bls.BlsException(Bls.ERROR.WRONGSIZE); + } + + 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) + { + pk.Decode(publicKeyBytes.Slice(i, PkCompressedSz)); + agg.Aggregate(pk); + } + + return Verify(agg.ToAffine(), signature, message); + } + // Compressed G2 point public readonly ref struct Signature() {