Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 164 additions & 51 deletions src/Nethermind/Nethermind.Evm.Precompiles/BN254.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using Nethermind.MclBindings;

namespace Nethermind.Evm.Precompiles;
Expand All @@ -21,7 +25,7 @@ static BN254()
throw new InvalidOperationException("MCL initialization failed");
}

internal static bool Add(Span<byte> input, Span<byte> output)
internal static bool Add(ReadOnlySpan<byte> input, Span<byte> output)
{
if (input.Length != 128)
return false;
Expand Down Expand Up @@ -63,8 +67,12 @@ internal static bool Mul(Span<byte> input, Span<byte> output)
return SerializeG1(x, output);
}

internal static bool CheckPairing(Span<byte> input, Span<byte> output)
internal static bool CheckPairing(ReadOnlySpan<byte> input, Span<byte> output)
{
if (output.Length < 32)
return false;

// Empty input means "true" by convention
if (input.Length == 0)
{
output[31] = 1;
Expand All @@ -74,109 +82,126 @@ internal static bool CheckPairing(Span<byte> input, Span<byte> output)
if (input.Length % PairSize != 0)
return false;

mclBnGT gt = default;
Unsafe.SkipInit(out mclBnGT previous);
var hasPrevious = false;
mclBnGT acc = default;
bool hasMl = false;

for (int i = 0, count = input.Length; i < count; i += PairSize)
for (int i = 0; i < input.Length; i += PairSize)
{
var i64 = i + 64;
int i64 = i + 64;

if (!DeserializeG1(input[i..i64], out mclBnG1 g1))
return false;

if (!DeserializeG2(input[i64..(i64 + 128)], out mclBnG2 g2))
return false;

// Skip explicit neutral pairs
if (mclBnG1_isZero(g1) == 1 || mclBnG2_isZero(g2) == 1)
continue;

mclBn_pairing(ref gt, g1, g2);

// Skip multiplication for the first pairing as there's no previous result
if (hasPrevious)
mclBnGT_mul(ref gt, gt, previous); // gt *= previous

previous = gt;
hasPrevious = true;
mclBnGT ml = default;
mclBn_millerLoop(ref ml, g1, g2); // Miller loop only

if (hasMl)
{
mclBnGT_mul(ref acc, acc, ml);
}
else
{
acc = ml;
hasMl = true;
}
}

// If gt is zero, then no pairing was computed, and it's considered valid
if (mclBnGT_isOne(gt) == 1 || mclBnGT_isZero(gt) == 1)
// No effective pairs -> valid
if (!hasMl)
{
output[31] = 1;
return true;
}

return mclBnGT_isValid(gt) == 1;
// Single final exponentiation for the product
mclBn_finalExp(ref acc, acc);

// True if the product of pairings equals 1 in GT
output[31] = (byte)(mclBnGT_isOne(acc) == 1 ? 1 : 0);
return true;
}

private static bool DeserializeG1(Span<byte> data, out mclBnG1 point)
[SkipLocalsInit]
private static bool DeserializeG1(ReadOnlySpan<byte> data, out mclBnG1 point)
{
point = default;

// Check for all-zero data
// Treat all-zero as point at infinity for your calling convention
if (data.IndexOfAnyExcept((byte)0) == -1)
Comment thread
benaadams marked this conversation as resolved.
return true;

Span<byte> x = data[0..32];
x.Reverse(); // To little-endian
// Input is big-endian; MCL call below expects little-endian byte order for Fp
Span<byte> tmp = stackalloc byte[32];

fixed (byte* ptr = &MemoryMarshal.GetReference(x))
// x
CopyReverse32(data.Slice(0, 32), tmp);
fixed (byte* px = &MemoryMarshal.GetReference(tmp))
{
if (mclBnFp_deserialize(ref point.x, (nint)ptr, 32) == nuint.Zero)
if (mclBnFp_deserialize(ref point.x, (nint)px, 32) == nuint.Zero)
return false;
}

Span<byte> y = data[32..64];
y.Reverse(); // To little-endian

fixed (byte* ptr = &MemoryMarshal.GetReference(y))
// y
CopyReverse32(data.Slice(32, 32), tmp);
fixed (byte* py = &MemoryMarshal.GetReference(tmp))
{
if (mclBnFp_deserialize(ref point.y, (nint)ptr, 32) == nuint.Zero)
if (mclBnFp_deserialize(ref point.y, (nint)py, 32) == nuint.Zero)
return false;
}

mclBnFp_setInt32(ref point.z, 1);

return mclBnG1_isValid(point) == 1;
}

private static bool DeserializeG2(Span<byte> data, out mclBnG2 point)
[SkipLocalsInit]
private static bool DeserializeG2(ReadOnlySpan<byte> data, out mclBnG2 point)
{
point = default;

// Check for all-zero data
// Treat all-zero as point at infinity
if (data.IndexOfAnyExcept((byte)0) == -1)
return true;

Span<byte> x0 = data[32..64];
Span<byte> x1 = data[0..32];
x0.Reverse(); // To little-endian
x1.Reverse(); // To little-endian
// Input layout: x_im, x_re, y_im, y_re (each 32 bytes, big-endian)
// MCL Fp2 layout: d0 = re, d1 = im
Span<byte> tmp = stackalloc byte[32];

fixed (byte* ptr0 = &MemoryMarshal.GetReference(x0))
fixed (byte* ptr1 = &MemoryMarshal.GetReference(x1))
// x.re
CopyReverse32(data.Slice(32, 32), tmp);
fixed (byte* p = &MemoryMarshal.GetReference(tmp))
{
if (mclBnFp_deserialize(ref point.x.d0, (nint)ptr0, 32) == nuint.Zero)
if (mclBnFp_deserialize(ref point.x.d0, (nint)p, 32) == nuint.Zero)
return false;
}

if (mclBnFp_deserialize(ref point.x.d1, (nint)ptr1, 32) == nuint.Zero)
// x.im
CopyReverse32(data.Slice(0, 32), tmp);
fixed (byte* p = &MemoryMarshal.GetReference(tmp))
{
if (mclBnFp_deserialize(ref point.x.d1, (nint)p, 32) == nuint.Zero)
return false;
}

Span<byte> y0 = data[96..128];
Span<byte> y1 = data[64..96];
y0.Reverse(); // To little-endian
y1.Reverse(); // To little-endian

fixed (byte* ptr0 = &MemoryMarshal.GetReference(y0))
fixed (byte* ptr1 = &MemoryMarshal.GetReference(y1))
// y.re
CopyReverse32(data.Slice(96, 32), tmp);
fixed (byte* p = &MemoryMarshal.GetReference(tmp))
{
if (mclBnFp_deserialize(ref point.y.d0, (nint)ptr0, 32) == nuint.Zero)
if (mclBnFp_deserialize(ref point.y.d0, (nint)p, 32) == nuint.Zero)
return false;
}

if (mclBnFp_deserialize(ref point.y.d1, (nint)ptr1, 32) == nuint.Zero)
// y.im
CopyReverse32(data.Slice(64, 32), tmp);
fixed (byte* p = &MemoryMarshal.GetReference(tmp))
{
if (mclBnFp_deserialize(ref point.y.d1, (nint)p, 32) == nuint.Zero)
return false;
}

Expand All @@ -203,9 +228,97 @@ private static bool SerializeG1(in mclBnG1 point, Span<byte> output)
return false;
}

x.Reverse(); // To big-endian
y.Reverse(); // To big-endian
CopyReverse32(x, x); // To big-endian
Comment thread
benaadams marked this conversation as resolved.
Outdated
CopyReverse32(y, y); // To big-endian

return true;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void CopyReverse32(ReadOnlySpan<byte> src, Span<byte> dst)
{
Debug.Assert(src.Length == 32);
Debug.Assert(dst.Length == 32);

if (Avx2.IsSupported)
{
Reverse32BytesAvx2(src, dst);
}
else
if (Sse2.IsSupported && Ssse3.IsSupported)
{
Reverse32BytesSse2(src, dst);
}
else
{
// Fallback scalar path
Reverse32BytesScalar(src, dst);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void Reverse32BytesAvx2(ReadOnlySpan<byte> src, Span<byte> dst)
{
ref byte srcRef = ref MemoryMarshal.GetReference(src);
ref byte dstRef = ref MemoryMarshal.GetReference(dst);

// Load 32 bytes as one 256-bit vector
Vector256<byte> vec = Unsafe.ReadUnaligned<Vector256<byte>>(ref srcRef);

// Build reverse mask once — [31,30,...,0]
Vector256<byte> mask = Vector256.Create(
(byte)31, (byte)30, (byte)29, (byte)28,
(byte)27, (byte)26, (byte)25, (byte)24,
(byte)23, (byte)22, (byte)21, (byte)20,
(byte)19, (byte)18, (byte)17, (byte)16,
(byte)15, (byte)14, (byte)13, (byte)12,
(byte)11, (byte)10, (byte)9, (byte)8,
(byte)7, (byte)6, (byte)5, (byte)4,
(byte)3, (byte)2, (byte)1, (byte)0);

Vector256<byte> revInLane = Avx2.Shuffle(vec, mask);
Vector256<byte> fullRev = Avx2.Permute2x128(revInLane, revInLane, 0x01);
Unsafe.WriteUnaligned(ref dstRef, fullRev);
}
Comment thread
benaadams marked this conversation as resolved.

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void Reverse32BytesSse2(ReadOnlySpan<byte> src, Span<byte> dst)
{
ref byte srcRef = ref MemoryMarshal.GetReference(src);
ref byte dstRef = ref MemoryMarshal.GetReference(dst);

// Two 16-byte halves: reverse each then swap them
Vector128<byte> lo = Unsafe.ReadUnaligned<Vector128<byte>>(ref srcRef);
Vector128<byte> hi = Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.Add(ref srcRef, 16));

Vector128<byte> revMask = Vector128.Create(
(byte)15, (byte)14, (byte)13, (byte)12,
(byte)11, (byte)10, (byte)9, (byte)8,
(byte)7, (byte)6, (byte)5, (byte)4,
(byte)3, (byte)2, (byte)1, (byte)0);

lo = Ssse3.Shuffle(lo, revMask);
hi = Ssse3.Shuffle(hi, revMask);

// Store swapped halves reversed
Unsafe.WriteUnaligned(ref dstRef, hi);
Unsafe.WriteUnaligned(ref Unsafe.Add(ref dstRef, 16), lo);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void Reverse32BytesScalar(ReadOnlySpan<byte> src, Span<byte> dst)
{
ref ulong srcRef = ref Unsafe.As<byte, ulong>(ref MemoryMarshal.GetReference(src));
ref ulong dstRef = ref Unsafe.As<byte, ulong>(ref MemoryMarshal.GetReference(dst));

ulong a = BinaryPrimitives.ReverseEndianness(Unsafe.Add(ref srcRef, 3));
ulong b = BinaryPrimitives.ReverseEndianness(Unsafe.Add(ref srcRef, 2));
ulong c = BinaryPrimitives.ReverseEndianness(Unsafe.Add(ref srcRef, 1));
ulong d = BinaryPrimitives.ReverseEndianness(Unsafe.Add(ref srcRef, 0));

Unsafe.Add(ref dstRef, 0) = a;
Unsafe.Add(ref dstRef, 1) = b;
Unsafe.Add(ref dstRef, 2) = c;
Unsafe.Add(ref dstRef, 3) = d;
}
}
28 changes: 22 additions & 6 deletions src/Nethermind/Nethermind.Evm.Precompiles/BN254AddPrecompile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Runtime.CompilerServices;
using Nethermind.Core;
using Nethermind.Core.Specs;

Expand All @@ -22,15 +23,30 @@ public class BN254AddPrecompile : IPrecompile<BN254AddPrecompile>

public long DataGasCost(ReadOnlyMemory<byte> inputData, IReleaseSpec releaseSpec) => 0L;

[SkipLocalsInit]
public (byte[], bool) Run(ReadOnlyMemory<byte> inputData, IReleaseSpec releaseSpec)
{
Metrics.Bn254AddPrecompile++;

Span<byte> input = stackalloc byte[128];
Span<byte> output = stackalloc byte[64];

inputData.Span[0..Math.Min(inputData.Length, input.Length)].CopyTo(input);

return BN254.Add(input, output) ? (output.ToArray(), true) : IPrecompile.Failure;
Span<byte> inputWritable = stackalloc byte[128];
ReadOnlySpan<byte> input = inputWritable;

ReadOnlySpan<byte> orignalInput = inputData.Span;
if (orignalInput.Length == 128)
{
input = orignalInput;
}
else if (orignalInput.Length < 128)
{
orignalInput.CopyTo(inputWritable);
inputWritable[orignalInput.Length..].Clear();
}
else
{
orignalInput[0..inputWritable.Length].CopyTo(inputWritable);
}

byte[] output = new byte[64];
return BN254.Add(input, output) ? (output, true) : IPrecompile.Failure;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ public class BN254MulPrecompile : IPrecompile<BN254MulPrecompile>
Metrics.Bn254MulPrecompile++;

Span<byte> input = stackalloc byte[96];
Span<byte> output = stackalloc byte[64];

inputData.Span[0..Math.Min(inputData.Length, input.Length)].CopyTo(input);
ReadOnlySpan<byte> inputSpan = inputData.Span;
inputSpan[0..Math.Min(inputSpan.Length, input.Length)].CopyTo(input);

return BN254.Mul(input, output) ? (output.ToArray(), true) : IPrecompile.Failure;
byte[] output = new byte[64];
return BN254.Mul(input, output) ? (output, true) : IPrecompile.Failure;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Buffers;
using Nethermind.Core;
using Nethermind.Core.Specs;

Expand Down Expand Up @@ -36,15 +35,9 @@ public long DataGasCost(ReadOnlyMemory<byte> inputData, IReleaseSpec releaseSpec
return IPrecompile.Failure;
}

var input = ArrayPool<byte>.Shared.Rent(inputData.Length);
Span<byte> output = stackalloc byte[32];
byte[] output = new byte[32];
bool result = BN254.CheckPairing(inputData.Span, output);

inputData.CopyTo(input);

var result = BN254.CheckPairing(input.AsSpan(0, inputData.Length), output);

ArrayPool<byte>.Shared.Return(input);

return result ? (output.ToArray(), true) : IPrecompile.Failure;
return result ? (output, true) : IPrecompile.Failure;
}
}