Skip to content

Commit 1f82a9b

Browse files
committed
Implement BLS12-381 multi exponentiation
1 parent dc9b374 commit 1f82a9b

File tree

4 files changed

+207
-85
lines changed

4 files changed

+207
-85
lines changed

docs/native-contracts-api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ When calling a native contract method by transaction script, there are several t
7878
| bls12381Equal | Determines whether the specified points are equal. | InteropInterface(*x*), InteropInterface(*y*) | Boolean | 1<<5 | 0 | -- | -- |
7979
| bls12381Add | Add operation of two points. | InteropInterface(*x*), InteropInterface(*y*) | InteropInterface | 1<<19 | 0 | -- | -- |
8080
| bls12381Mul | Mul operation of gt point and multiplier | InteropInterface(*x*), Byte[](*mul*), Boolean(*neg*) | InteropInterface | 1<<21 | 0 | -- | -- |
81+
| bls12381MultiExp | Multi exponentiation operation for bls12381 points. | Array(*pairs*) | InteropInterface | 1<<23 | 0 | -- | HF_Gorgon |
8182
| bls12381Pairing | Pairing operation of g1 and g2 | InteropInterface(*g1*), InteropInterface(*g2*) | InteropInterface | 1<<23 | 0 | -- | -- |
8283
| recoverSecp256K1 | Recovers the public key from a secp256k1 signature in a single byte array format. | Byte[](*messageHash*), Byte[](*signature*) | Byte[] | 1<<15 | 0 | -- | HF_Echidna |
8384
| ripemd160 | Computes the hash value for the specified byte array using the ripemd160 algorithm. | Byte[](*data*) | Byte[] | 1<<15 | 0 | -- | -- |

src/Neo/SmartContract/Native/CryptoLib.BLS12_381.cs

Lines changed: 106 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
using Neo.Cryptography.BLS12_381;
1313
using Neo.VM.Types;
1414
using System;
15+
using Array = Neo.VM.Types.Array;
16+
using VMBuffer = Neo.VM.Types.Buffer;
1517

1618
namespace Neo.SmartContract.Native
1719
{
@@ -97,14 +99,6 @@ public static InteropInterface Bls12381Add(InteropInterface x, InteropInterface
9799
};
98100
}
99101

100-
[ContractMethod(Hardfork.HF_Gorgon, CpuFee = 1 << 19, Name = "bls12_g1add")]
101-
public static InteropInterface Bls12G1Add(InteropInterface x, InteropInterface y)
102-
=> Bls12381Add(x, y);
103-
104-
[ContractMethod(Hardfork.HF_Gorgon, CpuFee = 1 << 19, Name = "bls12_g2add")]
105-
public static InteropInterface Bls12G2Add(InteropInterface x, InteropInterface y)
106-
=> Bls12381Add(x, y);
107-
108102
/// <summary>
109103
/// Mul operation of gt point and multiplier
110104
/// </summary>
@@ -127,13 +121,76 @@ public static InteropInterface Bls12381Mul(InteropInterface x, byte[] mul, bool
127121
};
128122
}
129123

130-
[ContractMethod(Hardfork.HF_Gorgon, CpuFee = 1 << 21, Name = "bls12_g1mul")]
131-
public static InteropInterface Bls12G1Mul(InteropInterface x, byte[] mul, bool neg)
132-
=> Bls12381Mul(x, mul, neg);
124+
/// <summary>
125+
/// Multi exponentiation operation for bls12381 points.
126+
/// </summary>
127+
/// <param name="pairs">Array of [point, scalar] pairs.</param>
128+
/// <returns>The accumulated point.</returns>
129+
[ContractMethod(Hardfork.HF_Gorgon, CpuFee = 1 << 23)]
130+
public static InteropInterface Bls12381MultiExp(Array pairs)
131+
{
132+
if (pairs is null || pairs.Count == 0)
133+
throw new ArgumentException("BLS12-381 multi exponent requires at least one pair");
134+
135+
bool? useG2 = null;
136+
G1Projective g1Accumulator = G1Projective.Identity;
137+
G2Projective g2Accumulator = G2Projective.Identity;
133138

134-
[ContractMethod(Hardfork.HF_Gorgon, CpuFee = 1 << 21, Name = "bls12_g2mul")]
135-
public static InteropInterface Bls12G2Mul(InteropInterface x, byte[] mul, bool neg)
136-
=> Bls12381Mul(x, mul, neg);
139+
foreach (StackItem item in pairs)
140+
{
141+
if (item is not Array pair || pair.Count != 2)
142+
throw new ArgumentException("BLS12-381 multi exponent pair must contain point and scalar");
143+
144+
if (pair[0] is not InteropInterface pointInterface)
145+
throw new ArgumentException("BLS12-381 multi exponent requires interop points");
146+
147+
var point = pointInterface.GetInterface<object>();
148+
switch (point)
149+
{
150+
case G1Affine g1Affine:
151+
EnsureGroupType(ref useG2, false);
152+
{
153+
var scalar = ParseScalar(pair[1]);
154+
if (!scalar.IsZero)
155+
g1Accumulator += new G1Projective(g1Affine) * scalar;
156+
}
157+
break;
158+
case G1Projective g1Projective:
159+
EnsureGroupType(ref useG2, false);
160+
{
161+
var scalar = ParseScalar(pair[1]);
162+
if (!scalar.IsZero)
163+
g1Accumulator += g1Projective * scalar;
164+
}
165+
break;
166+
case G2Affine g2Affine:
167+
EnsureGroupType(ref useG2, true);
168+
{
169+
var scalar = ParseScalar(pair[1]);
170+
if (!scalar.IsZero)
171+
g2Accumulator += new G2Projective(g2Affine) * scalar;
172+
}
173+
break;
174+
case G2Projective g2Projective:
175+
EnsureGroupType(ref useG2, true);
176+
{
177+
var scalar = ParseScalar(pair[1]);
178+
if (!scalar.IsZero)
179+
g2Accumulator += g2Projective * scalar;
180+
}
181+
break;
182+
default:
183+
throw new ArgumentException("BLS12-381 type mismatch");
184+
}
185+
}
186+
187+
if (useG2 is null)
188+
throw new ArgumentException("BLS12-381 multi exponent requires at least one valid pair");
189+
190+
return useG2.Value
191+
? new InteropInterface(g2Accumulator)
192+
: new InteropInterface(g1Accumulator);
193+
}
137194

138195
/// <summary>
139196
/// Pairing operation of g1 and g2
@@ -159,8 +216,40 @@ public static InteropInterface Bls12381Pairing(InteropInterface g1, InteropInter
159216
return new(Bls12.Pairing(in g1a, in g2a));
160217
}
161218

162-
[ContractMethod(Hardfork.HF_Gorgon, CpuFee = 1 << 23, Name = "bls12_pairing")]
163-
public static InteropInterface Bls12Pairing(InteropInterface g1, InteropInterface g2)
164-
=> Bls12381Pairing(g1, g2);
219+
private static void EnsureGroupType(ref bool? current, bool isG2)
220+
{
221+
if (current is null)
222+
{
223+
current = isG2;
224+
}
225+
else if (current.Value != isG2)
226+
{
227+
throw new ArgumentException("BLS12-381 multi exponent cannot mix groups");
228+
}
229+
}
230+
231+
private static Scalar ParseScalar(StackItem scalarItem)
232+
{
233+
ReadOnlySpan<byte> data = scalarItem switch
234+
{
235+
ByteString bs when bs.GetSpan().Length == Scalar.Size => bs.GetSpan(),
236+
VMBuffer buffer when buffer.Size == Scalar.Size => buffer.InnerBuffer.Span,
237+
_ => throw new ArgumentException("BLS12-381 scalar must be 32 bytes"),
238+
};
239+
240+
Span<byte> littleEndian = stackalloc byte[Scalar.Size];
241+
data.CopyTo(littleEndian);
242+
243+
try
244+
{
245+
return Scalar.FromBytes(littleEndian);
246+
}
247+
catch (FormatException)
248+
{
249+
var wide = new byte[Scalar.Size * 2];
250+
littleEndian.CopyTo(wide);
251+
return Scalar.FromBytesWide(wide);
252+
}
253+
}
165254
}
166255
}

tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

Lines changed: 99 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@
2121
using Neo.SmartContract;
2222
using Neo.SmartContract.Native;
2323
using Neo.VM;
24+
using Neo.VM.Types;
2425
using Org.BouncyCastle.Utilities.Encoders;
2526
using System;
2627
using System.Collections.Generic;
2728
using System.Linq;
29+
using System.Numerics;
2830
using System.Text;
31+
using VMArray = Neo.VM.Types.Array;
2932

3033
namespace Neo.UnitTests.SmartContract.Native
3134
{
@@ -265,30 +268,98 @@ public void TestBls12381Pairing()
265268
}
266269

267270
[TestMethod]
268-
public void TestBls12AddAliases()
271+
public void TestBls12381MultiExpG1()
269272
{
270-
var expected = InvokeBlsAddMethod("bls12381Add");
271-
foreach (var alias in new[] { "bls12_g1add", "bls12_g2add" })
273+
var g1Point = G1Affine.FromCompressed(g1);
274+
var pair1 = new VMArray(new StackItem[]
272275
{
273-
CollectionAssert.AreEqual(expected, InvokeBlsAddMethod(alias));
274-
}
276+
StackItem.FromInterface(g1Point),
277+
new ByteString(CreateScalarBytes(1))
278+
});
279+
var pair2 = new VMArray(new StackItem[]
280+
{
281+
StackItem.FromInterface(g1Point),
282+
new ByteString(CreateScalarBytes(2))
283+
});
284+
var pairs = new VMArray(new StackItem[] { pair1, pair2 });
285+
286+
var result = CryptoLib.Bls12381MultiExp(pairs);
287+
var actual = result.GetInterface<G1Projective>();
288+
289+
var expected = new G1Projective(g1Point) * CreateScalar(3);
290+
Assert.AreEqual(new G1Affine(expected).ToCompressed().ToHexString(),
291+
new G1Affine(actual).ToCompressed().ToHexString());
275292
}
276293

277294
[TestMethod]
278-
public void TestBls12MulAliases()
295+
public void TestBls12381MultiExpG2()
279296
{
280-
var expected = InvokeBlsMulMethod("bls12381Mul", false);
281-
foreach (var alias in new[] { "bls12_g1mul", "bls12_g2mul" })
297+
var g2Point = G2Affine.FromCompressed(g2);
298+
var pair = new VMArray(new StackItem[]
282299
{
283-
CollectionAssert.AreEqual(expected, InvokeBlsMulMethod(alias, false));
284-
}
300+
StackItem.FromInterface(new G2Projective(g2Point)),
301+
new ByteString(CreateScalarBytes(5))
302+
});
303+
var pairs = new VMArray(new StackItem[] { pair });
304+
305+
var result = CryptoLib.Bls12381MultiExp(pairs);
306+
var actual = result.GetInterface<G2Projective>();
307+
308+
var expected = new G2Projective(g2Point) * CreateScalar(5);
309+
Assert.AreEqual(new G2Affine(expected).ToCompressed().ToHexString(),
310+
new G2Affine(actual).ToCompressed().ToHexString());
285311
}
286312

287313
[TestMethod]
288-
public void TestBls12PairingAlias()
314+
public void TestBls12381MultiExpReducesScalar()
289315
{
290-
var expected = InvokeBlsPairingMethod("bls12381Pairing");
291-
CollectionAssert.AreEqual(expected, InvokeBlsPairingMethod("bls12_pairing"));
316+
var g1Point = G1Affine.FromCompressed(g1);
317+
var oversized = (BigInteger.One << 260) + 5;
318+
var scalarBytes = CreateScalarBytes(oversized);
319+
var pair = new VMArray(new StackItem[]
320+
{
321+
StackItem.FromInterface(g1Point),
322+
new ByteString(scalarBytes)
323+
});
324+
var pairs = new VMArray(new StackItem[] { pair });
325+
326+
var wide = new byte[Scalar.Size * 2];
327+
System.Array.Copy(scalarBytes, wide, scalarBytes.Length);
328+
var reducedScalar = Scalar.FromBytesWide(wide);
329+
330+
var result = CryptoLib.Bls12381MultiExp(pairs);
331+
var actual = result.GetInterface<G1Projective>();
332+
333+
var expected = new G1Projective(g1Point) * reducedScalar;
334+
Assert.AreEqual(new G1Affine(expected).ToCompressed().ToHexString(),
335+
new G1Affine(actual).ToCompressed().ToHexString());
336+
}
337+
338+
[TestMethod]
339+
public void TestBls12381MultiExpMixedGroupFails()
340+
{
341+
var g1Point = G1Affine.FromCompressed(g1);
342+
var g2Point = G2Affine.FromCompressed(g2);
343+
var pair1 = new VMArray(new StackItem[]
344+
{
345+
StackItem.FromInterface(g1Point),
346+
new ByteString(CreateScalarBytes(1))
347+
});
348+
var pair2 = new VMArray(new StackItem[]
349+
{
350+
StackItem.FromInterface(g2Point),
351+
new ByteString(CreateScalarBytes(1))
352+
});
353+
var pairs = new VMArray(new StackItem[] { pair1, pair2 });
354+
355+
Assert.ThrowsExactly<ArgumentException>(() => CryptoLib.Bls12381MultiExp(pairs));
356+
}
357+
358+
[TestMethod]
359+
public void TestBls12381MultiExpEmptyFails()
360+
{
361+
var pairs = new VMArray();
362+
Assert.ThrowsExactly<ArgumentException>(() => CryptoLib.Bls12381MultiExp(pairs));
292363
}
293364

294365
[TestMethod]
@@ -1153,7 +1224,7 @@ public void TestVerifyWithEd25519()
11531224
{
11541225
// byte[] privateKey = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60".HexToBytes();
11551226
byte[] publicKey = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a".HexToBytes();
1156-
byte[] message = Array.Empty<byte>();
1227+
byte[] message = System.Array.Empty<byte>();
11571228
byte[] signature = ("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155" +
11581229
"5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b").HexToBytes();
11591230

@@ -1169,13 +1240,13 @@ public void TestVerifyWithEd25519()
11691240

11701241
// Test with an invalid signature
11711242
byte[] invalidSignature = new byte[signature.Length];
1172-
Array.Copy(signature, invalidSignature, signature.Length);
1243+
System.Array.Copy(signature, invalidSignature, signature.Length);
11731244
invalidSignature[0] ^= 0x01; // Flip one bit
11741245
Assert.IsFalse(CallVerifyWithEd25519(message, publicKey, invalidSignature));
11751246

11761247
// Test with an invalid public key
11771248
byte[] invalidPublicKey = new byte[publicKey.Length];
1178-
Array.Copy(publicKey, invalidPublicKey, publicKey.Length);
1249+
System.Array.Copy(publicKey, invalidPublicKey, publicKey.Length);
11791250
invalidPublicKey[0] ^= 0x01; // Flip one bit
11801251
Assert.IsFalse(CallVerifyWithEd25519(message, invalidPublicKey, signature));
11811252
}
@@ -1203,61 +1274,22 @@ private bool CallVerifyWithEd25519(byte[] message, byte[] publicKey, byte[] sign
12031274
}
12041275
}
12051276

1206-
private byte[] InvokeBlsAddMethod(string methodName)
1277+
private static byte[] CreateScalarBytes(BigInteger value)
12071278
{
1208-
var snapshotCache = TestBlockchain.GetTestSnapshotCache();
1209-
using ScriptBuilder script = new();
1210-
script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", gt);
1211-
script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", gt);
1212-
script.EmitPush(2);
1213-
script.Emit(OpCode.PACK);
1214-
script.EmitPush(CallFlags.All);
1215-
script.EmitPush(methodName);
1216-
script.EmitPush(NativeContract.CryptoLib.Hash);
1217-
script.EmitSysCall(ApplicationEngine.System_Contract_Call);
1218-
return ExecuteBlsScript(script, snapshotCache);
1279+
if (value < 0)
1280+
throw new ArgumentOutOfRangeException(nameof(value));
1281+
1282+
var bytes = new byte[Scalar.Size];
1283+
var mask = (BigInteger.One << (Scalar.Size * 8)) - BigInteger.One;
1284+
var truncated = value & mask;
1285+
if (!truncated.TryWriteBytes(bytes, out _, isBigEndian: false))
1286+
throw new InvalidOperationException("Unable to encode scalar value.");
1287+
return bytes;
12191288
}
12201289

1221-
private byte[] InvokeBlsMulMethod(string methodName, bool neg)
1222-
{
1223-
var snapshotCache = TestBlockchain.GetTestSnapshotCache();
1224-
using ScriptBuilder script = new();
1225-
byte[] data = new byte[32];
1226-
data[0] = 0x03;
1227-
script.EmitPush(neg);
1228-
script.EmitPush(data);
1229-
script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", gt);
1230-
script.EmitPush(3);
1231-
script.Emit(OpCode.PACK);
1232-
script.EmitPush(CallFlags.All);
1233-
script.EmitPush(methodName);
1234-
script.EmitPush(NativeContract.CryptoLib.Hash);
1235-
script.EmitSysCall(ApplicationEngine.System_Contract_Call);
1236-
return ExecuteBlsScript(script, snapshotCache);
1237-
}
1290+
private static byte[] CreateScalarBytes(uint value) => CreateScalarBytes(new BigInteger(value));
12381291

1239-
private byte[] InvokeBlsPairingMethod(string methodName)
1240-
{
1241-
var snapshotCache = TestBlockchain.GetTestSnapshotCache();
1242-
using ScriptBuilder script = new();
1243-
script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", g2);
1244-
script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", g1);
1245-
script.EmitPush(2);
1246-
script.Emit(OpCode.PACK);
1247-
script.EmitPush(CallFlags.All);
1248-
script.EmitPush(methodName);
1249-
script.EmitPush(NativeContract.CryptoLib.Hash);
1250-
script.EmitSysCall(ApplicationEngine.System_Contract_Call);
1251-
return ExecuteBlsScript(script, snapshotCache);
1252-
}
1292+
private static Scalar CreateScalar(uint value) => Scalar.FromBytes(CreateScalarBytes(value));
12531293

1254-
private byte[] ExecuteBlsScript(ScriptBuilder script, StoreCache snapshotCache)
1255-
{
1256-
using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache,
1257-
settings: TestProtocolSettings.Default);
1258-
engine.LoadScript(script.ToArray());
1259-
Assert.AreEqual(VMState.HALT, engine.Execute());
1260-
return engine.ResultStack.Pop().GetInterface<Gt>().ToArray();
1261-
}
12621294
}
12631295
}

0 commit comments

Comments
 (0)