Skip to content

Commit c796a25

Browse files
committed
Add bn254 native contract bindings and tests
1 parent 5cc2cfd commit c796a25

File tree

4 files changed

+448
-1
lines changed

4 files changed

+448
-1
lines changed

src/Neo/Neo.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
<ItemGroup>
1010
<PackageReference Include="Akka" Version="1.5.46" />
11+
<PackageReference Include="Nethermind.MclBindings" Version="1.0.2" />
1112
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" />
1213
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.8" />
1314
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.7" />
Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
// Copyright (C) 2015-2025 The Neo Project.
2+
//
3+
// CryptoLib.BN254.cs file belongs to the neo project and is free
4+
// software distributed under the MIT software license, see the
5+
// accompanying file LICENSE in the main directory of the
6+
// repository or http://www.opensource.org/licenses/mit-license.php
7+
// for more details.
8+
//
9+
// Redistribution and use in source and binary forms with or without
10+
// modifications are permitted.
11+
12+
using Nethermind.MclBindings;
13+
using System;
14+
using System.Runtime.CompilerServices;
15+
using System.Runtime.InteropServices;
16+
17+
namespace Neo.SmartContract.Native
18+
{
19+
partial class CryptoLib
20+
{
21+
private const int Bn254FieldElementLength = 32;
22+
private const int Bn254G1EncodedLength = 64;
23+
private const int Bn254AddInputLength = 128;
24+
private const int Bn254MulInputLength = 96;
25+
private const int Bn254PairInputLength = 192;
26+
27+
[ContractMethod(Hardfork.HF_Gorgon, CpuFee = 1 << 19)]
28+
public static byte[] Bn254Add(byte[] input)
29+
{
30+
ArgumentNullException.ThrowIfNull(input);
31+
if (input.Length != Bn254AddInputLength)
32+
throw new ArgumentException("Invalid BN254 add input length", nameof(input));
33+
34+
return Bn254Runtime.Add(input);
35+
}
36+
37+
[ContractMethod(Hardfork.HF_Gorgon, CpuFee = 1 << 19)]
38+
public static byte[] Bn254Mul(byte[] input)
39+
{
40+
ArgumentNullException.ThrowIfNull(input);
41+
if (input.Length != Bn254MulInputLength)
42+
throw new ArgumentException("Invalid BN254 mul input length", nameof(input));
43+
44+
return Bn254Runtime.Mul(input);
45+
}
46+
47+
[ContractMethod(Hardfork.HF_Gorgon, CpuFee = 1 << 21)]
48+
public static byte[] Bn254Pairing(byte[] input)
49+
{
50+
ArgumentNullException.ThrowIfNull(input);
51+
if (input.Length % Bn254PairInputLength != 0)
52+
throw new ArgumentException("Invalid BN254 pairing input length", nameof(input));
53+
54+
return Bn254Runtime.Pairing(input);
55+
}
56+
57+
private static class Bn254Runtime
58+
{
59+
private static readonly object s_sync = new();
60+
private static bool s_initialized;
61+
62+
internal static unsafe byte[] Add(ReadOnlySpan<byte> input)
63+
{
64+
EnsureInitialized();
65+
66+
if (!TryDeserializeG1(input[..Bn254G1EncodedLength], out var first))
67+
return new byte[Bn254G1EncodedLength];
68+
69+
if (!TryDeserializeG1(input[Bn254G1EncodedLength..], out var second))
70+
return new byte[Bn254G1EncodedLength];
71+
72+
mclBnG1 result = default;
73+
Mcl.mclBnG1_add(ref result, first, second);
74+
Mcl.mclBnG1_normalize(ref result, result);
75+
76+
return SerializeG1(result);
77+
}
78+
79+
internal static unsafe byte[] Mul(ReadOnlySpan<byte> input)
80+
{
81+
EnsureInitialized();
82+
83+
if (!TryDeserializeG1(input[..Bn254G1EncodedLength], out var basePoint))
84+
return new byte[Bn254G1EncodedLength];
85+
86+
if (!TryDeserializeScalar(input[Bn254G1EncodedLength..], out var scalar))
87+
return new byte[Bn254G1EncodedLength];
88+
89+
mclBnG1 result = default;
90+
Mcl.mclBnG1_mul(ref result, basePoint, scalar);
91+
Mcl.mclBnG1_normalize(ref result, result);
92+
93+
return SerializeG1(result);
94+
}
95+
96+
internal static unsafe byte[] Pairing(ReadOnlySpan<byte> input)
97+
{
98+
EnsureInitialized();
99+
100+
if (input.Length == 0)
101+
return SuccessWord();
102+
103+
int pairCount = input.Length / Bn254PairInputLength;
104+
bool hasEffectivePair = false;
105+
106+
mclBnGT accumulator = default;
107+
Mcl.mclBnGT_setInt32(ref accumulator, 1);
108+
109+
for (int pairIndex = 0; pairIndex < pairCount; pairIndex++)
110+
{
111+
int offset = pairIndex * Bn254PairInputLength;
112+
var g1Slice = input.Slice(offset, Bn254G1EncodedLength);
113+
var g2Slice = input.Slice(offset + Bn254G1EncodedLength, 2 * Bn254G1EncodedLength);
114+
115+
if (!TryDeserializeG1(g1Slice, out var g1))
116+
return new byte[Bn254FieldElementLength];
117+
118+
if (!TryDeserializeG2(g2Slice, out var g2))
119+
return new byte[Bn254FieldElementLength];
120+
121+
if (Mcl.mclBnG1_isZero(g1) == 1 || Mcl.mclBnG2_isZero(g2) == 1)
122+
continue;
123+
124+
hasEffectivePair = true;
125+
126+
mclBnGT current = default;
127+
Mcl.mclBn_pairing(ref current, g1, g2);
128+
129+
if (Mcl.mclBnGT_isValid(current) == 0)
130+
return new byte[Bn254FieldElementLength];
131+
132+
mclBnGT temp = accumulator;
133+
Mcl.mclBnGT_mul(ref accumulator, temp, current);
134+
}
135+
136+
if (!hasEffectivePair)
137+
return SuccessWord();
138+
139+
return Mcl.mclBnGT_isOne(accumulator) == 1 ? SuccessWord() : new byte[Bn254FieldElementLength];
140+
}
141+
142+
private static unsafe bool TryDeserializeG1(ReadOnlySpan<byte> encoded, out mclBnG1 point)
143+
{
144+
point = default;
145+
146+
if (IsAllZero(encoded))
147+
return true;
148+
149+
ReadOnlySpan<byte> xBytes = encoded[..Bn254FieldElementLength];
150+
fixed (byte* ptr = xBytes)
151+
{
152+
if (Mcl.mclBnFp_setBigEndianMod(ref point.x, (nint)ptr, (nuint)xBytes.Length) != 0)
153+
return false;
154+
}
155+
156+
ReadOnlySpan<byte> yBytes = encoded[Bn254FieldElementLength..];
157+
fixed (byte* ptr = yBytes)
158+
{
159+
if (Mcl.mclBnFp_setBigEndianMod(ref point.y, (nint)ptr, (nuint)yBytes.Length) != 0)
160+
return false;
161+
}
162+
163+
Mcl.mclBnFp_setInt32(ref point.z, 1);
164+
165+
return Mcl.mclBnG1_isValid(point) == 1;
166+
}
167+
168+
private static unsafe bool TryDeserializeG2(ReadOnlySpan<byte> encoded, out mclBnG2 point)
169+
{
170+
point = default;
171+
172+
if (IsAllZero(encoded))
173+
return true;
174+
175+
Span<byte> scratch = stackalloc byte[Bn254FieldElementLength];
176+
177+
var realSegment = encoded.Slice(Bn254FieldElementLength, Bn254FieldElementLength);
178+
CopyReversed(realSegment, scratch);
179+
fixed (byte* ptr = scratch)
180+
{
181+
if (Mcl.mclBnFp_deserialize(ref point.x.d0, (nint)ptr, (nuint)scratch.Length) == UIntPtr.Zero)
182+
return false;
183+
}
184+
185+
var imagSegment = encoded[..Bn254FieldElementLength];
186+
CopyReversed(imagSegment, scratch);
187+
fixed (byte* ptr = scratch)
188+
{
189+
if (Mcl.mclBnFp_deserialize(ref point.x.d1, (nint)ptr, (nuint)scratch.Length) == UIntPtr.Zero)
190+
return false;
191+
}
192+
193+
var yReal = encoded.Slice(3 * Bn254FieldElementLength, Bn254FieldElementLength);
194+
CopyReversed(yReal, scratch);
195+
fixed (byte* ptr = scratch)
196+
{
197+
if (Mcl.mclBnFp_deserialize(ref point.y.d0, (nint)ptr, (nuint)scratch.Length) == UIntPtr.Zero)
198+
return false;
199+
}
200+
201+
var yImag = encoded.Slice(2 * Bn254FieldElementLength, Bn254FieldElementLength);
202+
CopyReversed(yImag, scratch);
203+
fixed (byte* ptr = scratch)
204+
{
205+
if (Mcl.mclBnFp_deserialize(ref point.y.d1, (nint)ptr, (nuint)scratch.Length) == UIntPtr.Zero)
206+
return false;
207+
}
208+
209+
Mcl.mclBnFp_setInt32(ref point.z.d0, 1);
210+
211+
return true;
212+
}
213+
214+
private static unsafe bool TryDeserializeScalar(ReadOnlySpan<byte> encoded, out mclBnFr scalar)
215+
{
216+
scalar = default;
217+
218+
if (IsAllZero(encoded))
219+
{
220+
Mcl.mclBnFr_clear(ref scalar);
221+
return true;
222+
}
223+
224+
fixed (byte* ptr = &MemoryMarshal.GetReference(encoded))
225+
{
226+
if (Mcl.mclBnFr_setBigEndianMod(ref scalar, (nint)ptr, (nuint)encoded.Length) == -1)
227+
return false;
228+
}
229+
230+
return Mcl.mclBnFr_isValid(scalar) == 1;
231+
}
232+
233+
private static unsafe byte[] SerializeG1(in mclBnG1 point)
234+
{
235+
var output = new byte[Bn254G1EncodedLength];
236+
237+
if (Mcl.mclBnG1_isZero(point) == 1)
238+
return output;
239+
240+
Span<byte> scratch = stackalloc byte[Bn254FieldElementLength];
241+
242+
fixed (byte* ptr = &MemoryMarshal.GetReference(scratch))
243+
{
244+
if (Mcl.mclBnFp_getLittleEndian((nint)ptr, (nuint)scratch.Length, point.x) == UIntPtr.Zero)
245+
throw new ArgumentException("Failed to serialize BN254 point");
246+
}
247+
248+
WriteBigEndian(scratch, output.AsSpan(0, Bn254FieldElementLength));
249+
250+
fixed (byte* ptr = &MemoryMarshal.GetReference(scratch))
251+
{
252+
if (Mcl.mclBnFp_getLittleEndian((nint)ptr, (nuint)scratch.Length, point.y) == UIntPtr.Zero)
253+
throw new ArgumentException("Failed to serialize BN254 point");
254+
}
255+
256+
WriteBigEndian(scratch, output.AsSpan(Bn254FieldElementLength, Bn254FieldElementLength));
257+
258+
return output;
259+
}
260+
261+
private static byte[] SuccessWord()
262+
{
263+
var output = new byte[Bn254FieldElementLength];
264+
output[^1] = 1;
265+
return output;
266+
}
267+
268+
private static bool IsAllZero(ReadOnlySpan<byte> data)
269+
{
270+
for (int i = 0; i < data.Length; ++i)
271+
{
272+
if (data[i] != 0)
273+
return false;
274+
}
275+
276+
return true;
277+
}
278+
279+
private static void WriteBigEndian(ReadOnlySpan<byte> littleEndian, Span<byte> destination)
280+
{
281+
for (int i = 0; i < littleEndian.Length; ++i)
282+
destination[i] = littleEndian[littleEndian.Length - 1 - i];
283+
}
284+
285+
private static void CopyReversed(ReadOnlySpan<byte> source, Span<byte> destination)
286+
{
287+
for (int i = 0; i < source.Length; ++i)
288+
destination[i] = source[source.Length - 1 - i];
289+
}
290+
291+
[MethodImpl(MethodImplOptions.NoInlining)]
292+
private static unsafe void EnsureInitialized()
293+
{
294+
if (s_initialized)
295+
return;
296+
297+
lock (s_sync)
298+
{
299+
if (s_initialized)
300+
return;
301+
302+
if (Mcl.mclBn_init(Mcl.MCL_BN_SNARK1, Mcl.MCLBN_COMPILED_TIME_VAR) != 0)
303+
throw new InvalidOperationException("BN254 initialization failed");
304+
305+
Mcl.mclBn_setETHserialization(1);
306+
307+
s_initialized = true;
308+
}
309+
}
310+
}
311+
}
312+
}

0 commit comments

Comments
 (0)