From a43c7bd6209d5b40ec8ebcbd16425c21ff984709 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Mon, 30 May 2022 11:43:23 +0800 Subject: [PATCH] Add project files --- .editorconfig | 19 + Neo.Cryptography.BLS12_381.sln | 39 + src/Neo.Cryptography.BLS12_381/Bls12.Adder.cs | 38 + src/Neo.Cryptography.BLS12_381/Bls12.cs | 20 + .../ConstantTimeUtility.cs | 31 + src/Neo.Cryptography.BLS12_381/Constants.cs | 7 + src/Neo.Cryptography.BLS12_381/Fp.cs | 465 ++++++++++ src/Neo.Cryptography.BLS12_381/Fp12.cs | 175 ++++ src/Neo.Cryptography.BLS12_381/Fp2.cs | 231 +++++ src/Neo.Cryptography.BLS12_381/Fp6.cs | 263 ++++++ src/Neo.Cryptography.BLS12_381/FpConstants.cs | 73 ++ src/Neo.Cryptography.BLS12_381/G1Affine.cs | 170 ++++ src/Neo.Cryptography.BLS12_381/G1Constants.cs | 44 + .../G1Projective.cs | 283 ++++++ src/Neo.Cryptography.BLS12_381/G2Affine.cs | 214 +++++ src/Neo.Cryptography.BLS12_381/G2Constants.cs | 101 +++ .../G2Prepared.Adder.cs | 40 + src/Neo.Cryptography.BLS12_381/G2Prepared.cs | 20 + .../G2Projective.cs | 366 ++++++++ .../GlobalSuppressions.cs | 8 + src/Neo.Cryptography.BLS12_381/Gt.cs | 109 +++ src/Neo.Cryptography.BLS12_381/GtConstants.cs | 104 +++ .../IMillerLoopDriver.cs | 10 + src/Neo.Cryptography.BLS12_381/INumber.cs | 37 + src/Neo.Cryptography.BLS12_381/MathUtility.cs | 28 + .../MillerLoopResult.cs | 132 +++ .../MillerLoopUtility.cs | 109 +++ .../Neo.Cryptography.BLS12_381.csproj | 12 + .../Properties/AssemblyInfo.cs | 3 + src/Neo.Cryptography.BLS12_381/Scalar.cs | 488 +++++++++++ .../ScalarConstants.cs | 85 ++ .../Neo.Cryptography.BLS12_381.Tests.csproj | 22 + .../Neo.Cryptography.BLS12_381.Tests/UT_Fp.cs | 342 ++++++++ .../UT_Fp12.cs | 336 ++++++++ .../UT_Fp2.cs | 482 +++++++++++ .../UT_Fp6.cs | 168 ++++ .../Neo.Cryptography.BLS12_381.Tests/UT_G1.cs | 592 +++++++++++++ .../Neo.Cryptography.BLS12_381.Tests/UT_G2.cs | 812 ++++++++++++++++++ .../UT_Pairings.cs | 58 ++ .../UT_Scalar.cs | 400 +++++++++ .../Usings.cs | 1 + 41 files changed, 6937 insertions(+) create mode 100644 .editorconfig create mode 100644 Neo.Cryptography.BLS12_381.sln create mode 100644 src/Neo.Cryptography.BLS12_381/Bls12.Adder.cs create mode 100644 src/Neo.Cryptography.BLS12_381/Bls12.cs create mode 100644 src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs create mode 100644 src/Neo.Cryptography.BLS12_381/Constants.cs create mode 100644 src/Neo.Cryptography.BLS12_381/Fp.cs create mode 100644 src/Neo.Cryptography.BLS12_381/Fp12.cs create mode 100644 src/Neo.Cryptography.BLS12_381/Fp2.cs create mode 100644 src/Neo.Cryptography.BLS12_381/Fp6.cs create mode 100644 src/Neo.Cryptography.BLS12_381/FpConstants.cs create mode 100644 src/Neo.Cryptography.BLS12_381/G1Affine.cs create mode 100644 src/Neo.Cryptography.BLS12_381/G1Constants.cs create mode 100644 src/Neo.Cryptography.BLS12_381/G1Projective.cs create mode 100644 src/Neo.Cryptography.BLS12_381/G2Affine.cs create mode 100644 src/Neo.Cryptography.BLS12_381/G2Constants.cs create mode 100644 src/Neo.Cryptography.BLS12_381/G2Prepared.Adder.cs create mode 100644 src/Neo.Cryptography.BLS12_381/G2Prepared.cs create mode 100644 src/Neo.Cryptography.BLS12_381/G2Projective.cs create mode 100644 src/Neo.Cryptography.BLS12_381/GlobalSuppressions.cs create mode 100644 src/Neo.Cryptography.BLS12_381/Gt.cs create mode 100644 src/Neo.Cryptography.BLS12_381/GtConstants.cs create mode 100644 src/Neo.Cryptography.BLS12_381/IMillerLoopDriver.cs create mode 100644 src/Neo.Cryptography.BLS12_381/INumber.cs create mode 100644 src/Neo.Cryptography.BLS12_381/MathUtility.cs create mode 100644 src/Neo.Cryptography.BLS12_381/MillerLoopResult.cs create mode 100644 src/Neo.Cryptography.BLS12_381/MillerLoopUtility.cs create mode 100644 src/Neo.Cryptography.BLS12_381/Neo.Cryptography.BLS12_381.csproj create mode 100644 src/Neo.Cryptography.BLS12_381/Properties/AssemblyInfo.cs create mode 100644 src/Neo.Cryptography.BLS12_381/Scalar.cs create mode 100644 src/Neo.Cryptography.BLS12_381/ScalarConstants.cs create mode 100644 tests/Neo.Cryptography.BLS12_381.Tests/Neo.Cryptography.BLS12_381.Tests.csproj create mode 100644 tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp.cs create mode 100644 tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp12.cs create mode 100644 tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp2.cs create mode 100644 tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp6.cs create mode 100644 tests/Neo.Cryptography.BLS12_381.Tests/UT_G1.cs create mode 100644 tests/Neo.Cryptography.BLS12_381.Tests/UT_G2.cs create mode 100644 tests/Neo.Cryptography.BLS12_381.Tests/UT_Pairings.cs create mode 100644 tests/Neo.Cryptography.BLS12_381.Tests/UT_Scalar.cs create mode 100644 tests/Neo.Cryptography.BLS12_381.Tests/Usings.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..175ec7b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +############################### +# Core EditorConfig Options # +############################### + +# dotnet-format requires version 3.1.37601 +# dotnet tool update -g dotnet-format +# remember to have: git config --global core.autocrlf false #(which is usually default) + +root = true + +# Every file + +[*] +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 +end_of_line = lf + +dotnet_diagnostic.CS1591.severity = silent diff --git a/Neo.Cryptography.BLS12_381.sln b/Neo.Cryptography.BLS12_381.sln new file mode 100644 index 0000000..9088d6e --- /dev/null +++ b/Neo.Cryptography.BLS12_381.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32519.379 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{36DA9DE1-958C-4888-BD69-11C612A6AFCA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{5D525A2B-247C-4FDE-839C-1F5D42DF8AE3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Cryptography.BLS12_381", "src\Neo.Cryptography.BLS12_381\Neo.Cryptography.BLS12_381.csproj", "{5355A12D-A614-4C74-B2F8-C8FB0686D717}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Cryptography.BLS12_381.Tests", "tests\Neo.Cryptography.BLS12_381.Tests\Neo.Cryptography.BLS12_381.Tests.csproj", "{8090336E-33F6-4A1B-BED7-0FEE79E2D1F5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5355A12D-A614-4C74-B2F8-C8FB0686D717}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5355A12D-A614-4C74-B2F8-C8FB0686D717}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5355A12D-A614-4C74-B2F8-C8FB0686D717}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5355A12D-A614-4C74-B2F8-C8FB0686D717}.Release|Any CPU.Build.0 = Release|Any CPU + {8090336E-33F6-4A1B-BED7-0FEE79E2D1F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8090336E-33F6-4A1B-BED7-0FEE79E2D1F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8090336E-33F6-4A1B-BED7-0FEE79E2D1F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8090336E-33F6-4A1B-BED7-0FEE79E2D1F5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {5355A12D-A614-4C74-B2F8-C8FB0686D717} = {36DA9DE1-958C-4888-BD69-11C612A6AFCA} + {8090336E-33F6-4A1B-BED7-0FEE79E2D1F5} = {5D525A2B-247C-4FDE-839C-1F5D42DF8AE3} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {879715CD-5002-4C10-9EDD-A96741813CF7} + EndGlobalSection +EndGlobal diff --git a/src/Neo.Cryptography.BLS12_381/Bls12.Adder.cs b/src/Neo.Cryptography.BLS12_381/Bls12.Adder.cs new file mode 100644 index 0000000..6d46299 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Bls12.Adder.cs @@ -0,0 +1,38 @@ +using static Neo.Cryptography.BLS12_381.MillerLoopUtility; + +namespace Neo.Cryptography.BLS12_381; + +partial class Bls12 +{ + class Adder : IMillerLoopDriver + { + public G2Projective Curve; + public readonly G2Affine Base; + public readonly G1Affine P; + + public Adder(in G1Affine p, in G2Affine q) + { + Curve = new(q); + Base = q; + P = p; + } + + Fp12 IMillerLoopDriver.DoublingStep(in Fp12 f) + { + var coeffs = DoublingStep(ref Curve); + return Ell(in f, in coeffs, in P); + } + + Fp12 IMillerLoopDriver.AdditionStep(in Fp12 f) + { + var coeffs = AdditionStep(ref Curve, in Base); + return Ell(in f, in coeffs, in P); + } + + static Fp12 IMillerLoopDriver.SquareOutput(in Fp12 f) => f.Square(); + + static Fp12 IMillerLoopDriver.Conjugate(in Fp12 f) => f.Conjugate(); + + static Fp12 IMillerLoopDriver.One => Fp12.One; + } +} diff --git a/src/Neo.Cryptography.BLS12_381/Bls12.cs b/src/Neo.Cryptography.BLS12_381/Bls12.cs new file mode 100644 index 0000000..378accf --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Bls12.cs @@ -0,0 +1,20 @@ +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.MillerLoopUtility; + +namespace Neo.Cryptography.BLS12_381; + +public static partial class Bls12 +{ + public static Gt Pairing(in G1Affine p, in G2Affine q) + { + var either_identity = p.IsIdentity | q.IsIdentity; + var p2 = ConditionalSelect(in p, in G1Affine.Generator, either_identity); + var q2 = ConditionalSelect(in q, in G2Affine.Generator, either_identity); + + var adder = new Adder(p2, q2); + + var tmp = MillerLoop(adder); + var tmp2 = new MillerLoopResult(ConditionalSelect(in tmp, in Fp12.One, either_identity)); + return tmp2.FinalExponentiation(); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs b/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs new file mode 100644 index 0000000..cd5d63b --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs @@ -0,0 +1,31 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Neo.Cryptography.BLS12_381; + +public static class ConstantTimeUtility +{ + public static bool ConstantTimeEq(in T a, in T b) where T : unmanaged + { + ReadOnlySpan a_bytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in a), 1)); + ReadOnlySpan b_bytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in b), 1)); + ReadOnlySpan a_u64 = MemoryMarshal.Cast(a_bytes); + ReadOnlySpan b_u64 = MemoryMarshal.Cast(b_bytes); + ulong f = 0; + for (int i = 0; i < a_u64.Length; i++) + f |= a_u64[i] ^ b_u64[i]; + for (int i = a_u64.Length * sizeof(ulong); i < a_bytes.Length; i++) + f |= (ulong)a_bytes[i] ^ a_bytes[i]; + return f == 0; + } + + public static T ConditionalSelect(in T a, in T b, bool choice) where T : unmanaged + { + return choice ? b : a; + } + + public static void ConditionalAssign(this ref T self, in T other, bool choice) where T : unmanaged + { + self = ConditionalSelect(in self, in other, choice); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/Constants.cs b/src/Neo.Cryptography.BLS12_381/Constants.cs new file mode 100644 index 0000000..e591a3f --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Constants.cs @@ -0,0 +1,7 @@ +namespace Neo.Cryptography.BLS12_381; + +static class Constants +{ + public const ulong BLS_X = 0xd201_0000_0001_0000; + public const bool BLS_X_IS_NEGATIVE = true; +} diff --git a/src/Neo.Cryptography.BLS12_381/Fp.cs b/src/Neo.Cryptography.BLS12_381/Fp.cs new file mode 100644 index 0000000..d724ef5 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Fp.cs @@ -0,0 +1,465 @@ +using System.Buffers.Binary; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.FpConstants; +using static Neo.Cryptography.BLS12_381.MathUtility; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Size)] +public readonly struct Fp : IEquatable, INumber +{ + public const int Size = 48; + public const int SizeL = Size / sizeof(ulong); + + private static readonly Fp _zero = new(); + + static int INumber.Size => Size; + public static ref readonly Fp Zero => ref _zero; + public static ref readonly Fp One => ref R; + + public bool IsZero => this == Zero; + + public static Fp FromBytes(ReadOnlySpan data) + { + if (data.Length != Size) + throw new FormatException($"The argument `{nameof(data)}` should contain {Size} bytes."); + + Span tmp = stackalloc ulong[SizeL]; + BinaryPrimitives.TryReadUInt64BigEndian(data[0..8], out tmp[5]); + BinaryPrimitives.TryReadUInt64BigEndian(data[8..16], out tmp[4]); + BinaryPrimitives.TryReadUInt64BigEndian(data[16..24], out tmp[3]); + BinaryPrimitives.TryReadUInt64BigEndian(data[24..32], out tmp[2]); + BinaryPrimitives.TryReadUInt64BigEndian(data[32..40], out tmp[1]); + BinaryPrimitives.TryReadUInt64BigEndian(data[40..48], out tmp[0]); + ReadOnlySpan span = MemoryMarshal.Cast(tmp); + + try + { + return span[0] * R2; + } + finally + { + ulong borrow; + (_, borrow) = Sbb(tmp[0], MODULUS[0], 0); + (_, borrow) = Sbb(tmp[1], MODULUS[1], borrow); + (_, borrow) = Sbb(tmp[2], MODULUS[2], borrow); + (_, borrow) = Sbb(tmp[3], MODULUS[3], borrow); + (_, borrow) = Sbb(tmp[4], MODULUS[4], borrow); + (_, borrow) = Sbb(tmp[5], MODULUS[5], borrow); + if (borrow == 0) + { + // If the element is smaller than MODULUS then the subtraction will underflow. + // Otherwise, throws. + // Why not throw before return? + // Because we want to run the method in a constant time. + throw new FormatException(); + } + } + } + + internal static Fp FromRawUnchecked(ulong[] values) + { + return MemoryMarshal.Cast(values)[0]; + } + + public static Fp Random(RandomNumberGenerator rng) + { + Span buffer = stackalloc byte[Size * 2]; + rng.GetBytes(buffer); + Span d = MemoryMarshal.Cast(buffer); + return d[0] * R2 + d[1] * R3; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ReadOnlySpan GetSpan() + { + return MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in this), 1)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Span GetSpanU64() + { + return MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in this), 1)); + } + + public static bool operator ==(in Fp left, in Fp right) + { + return ConstantTimeEq(in left, in right); + } + + public static bool operator !=(in Fp left, in Fp right) + { + return !(left == right); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not Fp other) return false; + return this == other; + } + + public bool Equals(Fp other) + { + return this == other; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public byte[] ToArray() + { + byte[] result = GC.AllocateUninitializedArray(Size); + TryWrite(result); + return result; + } + + public bool TryWrite(Span buffer) + { + if (buffer.Length < Size) return false; + + ReadOnlySpan u64 = GetSpanU64(); + Fp tmp = MontgomeryReduce(u64[0], u64[1], u64[2], u64[3], u64[4], u64[5], 0, 0, 0, 0, 0, 0); + u64 = tmp.GetSpanU64(); + + BinaryPrimitives.WriteUInt64BigEndian(buffer[0..8], u64[5]); + BinaryPrimitives.WriteUInt64BigEndian(buffer[8..16], u64[4]); + BinaryPrimitives.WriteUInt64BigEndian(buffer[16..24], u64[3]); + BinaryPrimitives.WriteUInt64BigEndian(buffer[24..32], u64[2]); + BinaryPrimitives.WriteUInt64BigEndian(buffer[32..40], u64[1]); + BinaryPrimitives.WriteUInt64BigEndian(buffer[40..48], u64[0]); + + return true; + } + + public override string ToString() + { + return "0x" + Convert.ToHexString(ToArray()).ToLowerInvariant(); + } + + public bool LexicographicallyLargest() + { + ReadOnlySpan s = GetSpanU64(); + Fp tmp = MontgomeryReduce(s[0], s[1], s[2], s[3], s[4], s[5], 0, 0, 0, 0, 0, 0); + ReadOnlySpan t = tmp.GetSpanU64(); + ulong borrow; + + (_, borrow) = Sbb(t[0], 0xdcff_7fff_ffff_d556, 0); + (_, borrow) = Sbb(t[1], 0x0f55_ffff_58a9_ffff, borrow); + (_, borrow) = Sbb(t[2], 0xb398_6950_7b58_7b12, borrow); + (_, borrow) = Sbb(t[3], 0xb23b_a5c2_79c2_895f, borrow); + (_, borrow) = Sbb(t[4], 0x258d_d3db_21a5_d66b, borrow); + (_, borrow) = Sbb(t[5], 0x0d00_88f5_1cbf_f34d, borrow); + + return borrow == 0; + } + + public Fp Sqrt() + { + // We use Shank's method, as p = 3 (mod 4). This means + // we only need to exponentiate by (p + 1) / 4. This only + // works for elements that are actually quadratic residue, + // so we check that we got the correct result at the end. + Fp result = this.PowVartime(P_1_4); + if (result.Square() != this) throw new ArithmeticException(); + return result; + } + + public Fp Invert() + { + if (!TryInvert(out Fp result)) + throw new DivideByZeroException(); + return result; + } + + public bool TryInvert(out Fp result) + { + // Exponentiate by p - 2 + result = this.PowVartime(P_2); + + // Why not return before Pow() if IsZero? + // Because we want to run the method in a constant time. + return !IsZero; + } + + private Fp SubtractP() + { + Fp result; + ReadOnlySpan s = GetSpanU64(); + Span r = result.GetSpanU64(); + ulong borrow; + + (r[0], borrow) = Sbb(s[0], MODULUS[0], 0); + (r[1], borrow) = Sbb(s[1], MODULUS[1], borrow); + (r[2], borrow) = Sbb(s[2], MODULUS[2], borrow); + (r[3], borrow) = Sbb(s[3], MODULUS[3], borrow); + (r[4], borrow) = Sbb(s[4], MODULUS[4], borrow); + (r[5], borrow) = Sbb(s[5], MODULUS[5], borrow); + + borrow = borrow == 0 ? ulong.MinValue : ulong.MaxValue; + r[0] = (s[0] & borrow) | (r[0] & ~borrow); + r[1] = (s[1] & borrow) | (r[1] & ~borrow); + r[2] = (s[2] & borrow) | (r[2] & ~borrow); + r[3] = (s[3] & borrow) | (r[3] & ~borrow); + r[4] = (s[4] & borrow) | (r[4] & ~borrow); + r[5] = (s[5] & borrow) | (r[5] & ~borrow); + + return result; + } + + public static Fp operator +(in Fp a, in Fp b) + { + Fp result; + ReadOnlySpan s = a.GetSpanU64(), r = b.GetSpanU64(); + Span d = result.GetSpanU64(); + + ulong carry = 0; + (d[0], carry) = Adc(s[0], r[0], carry); + (d[1], carry) = Adc(s[1], r[1], carry); + (d[2], carry) = Adc(s[2], r[2], carry); + (d[3], carry) = Adc(s[3], r[3], carry); + (d[4], carry) = Adc(s[4], r[4], carry); + (d[5], _) = Adc(s[5], r[5], carry); + + return result.SubtractP(); + } + + public static Fp operator -(in Fp a) + { + Fp result; + ReadOnlySpan self = a.GetSpanU64(); + Span d = result.GetSpanU64(); + + ulong borrow = 0; + (d[0], borrow) = Sbb(MODULUS[0], self[0], borrow); + (d[1], borrow) = Sbb(MODULUS[1], self[1], borrow); + (d[2], borrow) = Sbb(MODULUS[2], self[2], borrow); + (d[3], borrow) = Sbb(MODULUS[3], self[3], borrow); + (d[4], borrow) = Sbb(MODULUS[4], self[4], borrow); + (d[5], _) = Sbb(MODULUS[5], self[5], borrow); + + ulong mask = a.IsZero ? ulong.MinValue : ulong.MaxValue; + d[0] &= mask; + d[1] &= mask; + d[2] &= mask; + d[3] &= mask; + d[4] &= mask; + d[5] &= mask; + + return result; + } + + public static Fp operator -(in Fp a, in Fp b) + { + return -b + a; + } + + public static Fp SumOfProducts(ReadOnlySpan a, ReadOnlySpan b) + { + int length = a.Length; + if (length != b.Length) + throw new ArgumentException("The lengths of the two arrays must be the same."); + + Fp result; + ReadOnlySpan au = MemoryMarshal.Cast(a); + ReadOnlySpan bu = MemoryMarshal.Cast(b); + Span u = result.GetSpanU64(); + + for (int j = 0; j < 6; j++) + { + ulong carry; + + var (t0, t1, t2, t3, t4, t5, t6) = (u[0], u[1], u[2], u[3], u[4], u[5], 0ul); + for (int i = 0; i < length; i++) + { + (t0, carry) = Mac(t0, au[i * SizeL + j], bu[i * SizeL + 0], 0); + (t1, carry) = Mac(t1, au[i * SizeL + j], bu[i * SizeL + 1], carry); + (t2, carry) = Mac(t2, au[i * SizeL + j], bu[i * SizeL + 2], carry); + (t3, carry) = Mac(t3, au[i * SizeL + j], bu[i * SizeL + 3], carry); + (t4, carry) = Mac(t4, au[i * SizeL + j], bu[i * SizeL + 4], carry); + (t5, carry) = Mac(t5, au[i * SizeL + j], bu[i * SizeL + 5], carry); + (t6, _) = Adc(t6, 0, carry); + } + + ulong k = unchecked(t0 * INV); + (_, carry) = Mac(t0, k, MODULUS[0], 0); + (u[0], carry) = Mac(t1, k, MODULUS[1], carry); + (u[1], carry) = Mac(t2, k, MODULUS[2], carry); + (u[2], carry) = Mac(t3, k, MODULUS[3], carry); + (u[3], carry) = Mac(t4, k, MODULUS[4], carry); + (u[4], carry) = Mac(t5, k, MODULUS[5], carry); + (u[5], _) = Adc(t6, 0, carry); + } + + return result.SubtractP(); + } + + private static Fp MontgomeryReduce(ulong r0, ulong r1, ulong r2, ulong r3, ulong r4, ulong r5, ulong r6, ulong r7, ulong r8, ulong r9, ulong r10, ulong r11) + { + ulong carry, carry2; + + ulong k = unchecked(r0 * INV); + (_, carry) = Mac(r0, k, MODULUS[0], 0); + (r1, carry) = Mac(r1, k, MODULUS[1], carry); + (r2, carry) = Mac(r2, k, MODULUS[2], carry); + (r3, carry) = Mac(r3, k, MODULUS[3], carry); + (r4, carry) = Mac(r4, k, MODULUS[4], carry); + (r5, carry) = Mac(r5, k, MODULUS[5], carry); + (r6, carry2) = Adc(r6, 0, carry); + + k = unchecked(r1 * INV); + (_, carry) = Mac(r1, k, MODULUS[0], 0); + (r2, carry) = Mac(r2, k, MODULUS[1], carry); + (r3, carry) = Mac(r3, k, MODULUS[2], carry); + (r4, carry) = Mac(r4, k, MODULUS[3], carry); + (r5, carry) = Mac(r5, k, MODULUS[4], carry); + (r6, carry) = Mac(r6, k, MODULUS[5], carry); + (r7, carry2) = Adc(r7, carry2, carry); + + k = unchecked(r2 * INV); + (_, carry) = Mac(r2, k, MODULUS[0], 0); + (r3, carry) = Mac(r3, k, MODULUS[1], carry); + (r4, carry) = Mac(r4, k, MODULUS[2], carry); + (r5, carry) = Mac(r5, k, MODULUS[3], carry); + (r6, carry) = Mac(r6, k, MODULUS[4], carry); + (r7, carry) = Mac(r7, k, MODULUS[5], carry); + (r8, carry2) = Adc(r8, carry2, carry); + + k = unchecked(r3 * INV); + (_, carry) = Mac(r3, k, MODULUS[0], 0); + (r4, carry) = Mac(r4, k, MODULUS[1], carry); + (r5, carry) = Mac(r5, k, MODULUS[2], carry); + (r6, carry) = Mac(r6, k, MODULUS[3], carry); + (r7, carry) = Mac(r7, k, MODULUS[4], carry); + (r8, carry) = Mac(r8, k, MODULUS[5], carry); + (r9, carry2) = Adc(r9, carry2, carry); + + k = unchecked(r4 * INV); + (_, carry) = Mac(r4, k, MODULUS[0], 0); + (r5, carry) = Mac(r5, k, MODULUS[1], carry); + (r6, carry) = Mac(r6, k, MODULUS[2], carry); + (r7, carry) = Mac(r7, k, MODULUS[3], carry); + (r8, carry) = Mac(r8, k, MODULUS[4], carry); + (r9, carry) = Mac(r9, k, MODULUS[5], carry); + (r10, carry2) = Adc(r10, carry2, carry); + + k = unchecked(r5 * INV); + (_, carry) = Mac(r5, k, MODULUS[0], 0); + (r6, carry) = Mac(r6, k, MODULUS[1], carry); + (r7, carry) = Mac(r7, k, MODULUS[2], carry); + (r8, carry) = Mac(r8, k, MODULUS[3], carry); + (r9, carry) = Mac(r9, k, MODULUS[4], carry); + (r10, carry) = Mac(r10, k, MODULUS[5], carry); + (r11, _) = Adc(r11, carry2, carry); + + ReadOnlySpan tmp = stackalloc[] { r6, r7, r8, r9, r10, r11 }; + return MemoryMarshal.Cast(tmp)[0].SubtractP(); + } + + public static Fp operator *(in Fp a, in Fp b) + { + ReadOnlySpan s = a.GetSpanU64(), r = b.GetSpanU64(); + Span t = stackalloc ulong[SizeL * 2]; + ulong carry; + + (t[0], carry) = Mac(0, s[0], r[0], 0); + (t[1], carry) = Mac(0, s[0], r[1], carry); + (t[2], carry) = Mac(0, s[0], r[2], carry); + (t[3], carry) = Mac(0, s[0], r[3], carry); + (t[4], carry) = Mac(0, s[0], r[4], carry); + (t[5], t[6]) = Mac(0, s[0], r[5], carry); + + (t[1], carry) = Mac(t[1], s[1], r[0], 0); + (t[2], carry) = Mac(t[2], s[1], r[1], carry); + (t[3], carry) = Mac(t[3], s[1], r[2], carry); + (t[4], carry) = Mac(t[4], s[1], r[3], carry); + (t[5], carry) = Mac(t[5], s[1], r[4], carry); + (t[6], t[7]) = Mac(t[6], s[1], r[5], carry); + + (t[2], carry) = Mac(t[2], s[2], r[0], 0); + (t[3], carry) = Mac(t[3], s[2], r[1], carry); + (t[4], carry) = Mac(t[4], s[2], r[2], carry); + (t[5], carry) = Mac(t[5], s[2], r[3], carry); + (t[6], carry) = Mac(t[6], s[2], r[4], carry); + (t[7], t[8]) = Mac(t[7], s[2], r[5], carry); + (t[3], carry) = Mac(t[3], s[3], r[0], 0); + (t[4], carry) = Mac(t[4], s[3], r[1], carry); + (t[5], carry) = Mac(t[5], s[3], r[2], carry); + (t[6], carry) = Mac(t[6], s[3], r[3], carry); + (t[7], carry) = Mac(t[7], s[3], r[4], carry); + (t[8], t[9]) = Mac(t[8], s[3], r[5], carry); + (t[4], carry) = Mac(t[4], s[4], r[0], 0); + (t[5], carry) = Mac(t[5], s[4], r[1], carry); + (t[6], carry) = Mac(t[6], s[4], r[2], carry); + (t[7], carry) = Mac(t[7], s[4], r[3], carry); + (t[8], carry) = Mac(t[8], s[4], r[4], carry); + (t[9], t[10]) = Mac(t[9], s[4], r[5], carry); + (t[5], carry) = Mac(t[5], s[5], r[0], 0); + (t[6], carry) = Mac(t[6], s[5], r[1], carry); + (t[7], carry) = Mac(t[7], s[5], r[2], carry); + (t[8], carry) = Mac(t[8], s[5], r[3], carry); + (t[9], carry) = Mac(t[9], s[5], r[4], carry); + (t[10], t[11]) = Mac(t[10], s[5], r[5], carry); + + return MontgomeryReduce(t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8], t[9], t[10], t[11]); + } + + public Fp Square() + { + ReadOnlySpan self = GetSpanU64(); + ulong t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11; + ulong carry; + + (t1, carry) = Mac(0, self[0], self[1], 0); + (t2, carry) = Mac(0, self[0], self[2], carry); + (t3, carry) = Mac(0, self[0], self[3], carry); + (t4, carry) = Mac(0, self[0], self[4], carry); + (t5, t6) = Mac(0, self[0], self[5], carry); + + (t3, carry) = Mac(t3, self[1], self[2], 0); + (t4, carry) = Mac(t4, self[1], self[3], carry); + (t5, carry) = Mac(t5, self[1], self[4], carry); + (t6, t7) = Mac(t6, self[1], self[5], carry); + + (t5, carry) = Mac(t5, self[2], self[3], 0); + (t6, carry) = Mac(t6, self[2], self[4], carry); + (t7, t8) = Mac(t7, self[2], self[5], carry); + + (t7, carry) = Mac(t7, self[3], self[4], 0); + (t8, t9) = Mac(t8, self[3], self[5], carry); + + (t9, t10) = Mac(t9, self[4], self[5], 0); + + t11 = t10 >> 63; + t10 = (t10 << 1) | (t9 >> 63); + t9 = (t9 << 1) | (t8 >> 63); + t8 = (t8 << 1) | (t7 >> 63); + t7 = (t7 << 1) | (t6 >> 63); + t6 = (t6 << 1) | (t5 >> 63); + t5 = (t5 << 1) | (t4 >> 63); + t4 = (t4 << 1) | (t3 >> 63); + t3 = (t3 << 1) | (t2 >> 63); + t2 = (t2 << 1) | (t1 >> 63); + t1 <<= 1; + + (t0, carry) = Mac(0, self[0], self[0], 0); + (t1, carry) = Adc(t1, carry, 0); + (t2, carry) = Mac(t2, self[1], self[1], carry); + (t3, carry) = Adc(t3, carry, 0); + (t4, carry) = Mac(t4, self[2], self[2], carry); + (t5, carry) = Adc(t5, carry, 0); + (t6, carry) = Mac(t6, self[3], self[3], carry); + (t7, carry) = Adc(t7, carry, 0); + (t8, carry) = Mac(t8, self[4], self[4], carry); + (t9, carry) = Adc(t9, carry, 0); + (t10, carry) = Mac(t10, self[5], self[5], carry); + (t11, _) = Adc(t11, carry, 0); + + return MontgomeryReduce(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/Fp12.cs b/src/Neo.Cryptography.BLS12_381/Fp12.cs new file mode 100644 index 0000000..4fb38ba --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Fp12.cs @@ -0,0 +1,175 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Security.Cryptography; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Size)] +public readonly struct Fp12 : IEquatable, INumber +{ + [FieldOffset(0)] + public readonly Fp6 C0; + [FieldOffset(Fp6.Size)] + public readonly Fp6 C1; + + public const int Size = Fp6.Size * 2; + + private static readonly Fp12 _zero = new(); + private static readonly Fp12 _one = new(in Fp6.One); + + static int INumber.Size => Size; + public static ref readonly Fp12 Zero => ref _zero; + public static ref readonly Fp12 One => ref _one; + + public bool IsZero => C0.IsZero & C1.IsZero; + + public Fp12(in Fp f) + : this(new Fp6(in f), in Fp6.Zero) + { + } + + public Fp12(in Fp2 f) + : this(new Fp6(in f), in Fp6.Zero) + { + } + + public Fp12(in Fp6 f) + : this(in f, in Fp6.Zero) + { + } + + public Fp12(in Fp6 c0, in Fp6 c1) + { + C0 = c0; + C1 = c1; + } + + public static bool operator ==(in Fp12 a, in Fp12 b) + { + return a.C0 == b.C0 & a.C1 == b.C1; + } + + public static bool operator !=(in Fp12 a, in Fp12 b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not Fp12 other) return false; + return this == other; + } + + public bool Equals(Fp12 other) + { + return this == other; + } + + public override int GetHashCode() + { + return C0.GetHashCode() ^ C1.GetHashCode(); + } + + public static Fp12 Random(RandomNumberGenerator rng) + { + return new(Fp6.Random(rng), Fp6.Random(rng)); + } + + internal Fp12 MulBy_014(in Fp2 c0, in Fp2 c1, in Fp2 c4) + { + var aa = C0.MulBy_01(in c0, in c1); + var bb = C1.MulBy_1(in c4); + var o = c1 + c4; + var _c1 = C1 + C0; + _c1 = _c1.MulBy_01(in c0, in o); + _c1 = _c1 - aa - bb; + var _c0 = bb; + _c0 = _c0.MulByNonresidue(); + _c0 += aa; + + return new Fp12(in _c0, in _c1); + } + + public Fp12 Conjugate() + { + return new Fp12(in C0, -C1); + } + + public Fp12 FrobeniusMap() + { + var c0 = C0.FrobeniusMap(); + var c1 = C1.FrobeniusMap(); + + // c1 = c1 * (u + 1)^((p - 1) / 6) + c1 *= new Fp6(new Fp2( + Fp.FromRawUnchecked(new ulong[] + { + 0x0708_9552_b319_d465, + 0xc669_5f92_b50a_8313, + 0x97e8_3ccc_d117_228f, + 0xa35b_aeca_b2dc_29ee, + 0x1ce3_93ea_5daa_ce4d, + 0x08f2_220f_b0fb_66eb + }), Fp.FromRawUnchecked(new ulong[] + { + 0xb2f6_6aad_4ce5_d646, + 0x5842_a06b_fc49_7cec, + 0xcf48_95d4_2599_d394, + 0xc11b_9cba_40a8_e8d0, + 0x2e38_13cb_e5a0_de89, + 0x110e_efda_8884_7faf + }))); + + return new Fp12(in c0, in c1); + } + + public Fp12 Square() + { + var ab = C0 * C1; + var c0c1 = C0 + C1; + var c0 = C1.MulByNonresidue(); + c0 += C0; + c0 *= c0c1; + c0 -= ab; + var c1 = ab + ab; + c0 -= ab.MulByNonresidue(); + + return new Fp12(in c0, in c1); + } + + public Fp12 Invert() + { + Fp6 t = (C0.Square() - C1.Square().MulByNonresidue()).Invert(); + return new Fp12(C0 * t, C1 * -t); + } + + public static Fp12 operator -(in Fp12 a) + { + return new Fp12(-a.C0, -a.C1); + } + + public static Fp12 operator +(in Fp12 a, in Fp12 b) + { + return new Fp12(a.C0 + b.C0, a.C1 + b.C1); + } + + public static Fp12 operator -(in Fp12 a, in Fp12 b) + { + return new Fp12(a.C0 - b.C0, a.C1 - b.C1); + } + + public static Fp12 operator *(in Fp12 a, in Fp12 b) + { + var aa = a.C0 * b.C0; + var bb = a.C1 * b.C1; + var o = b.C0 + b.C1; + var c1 = a.C1 + a.C0; + c1 *= o; + c1 -= aa; + c1 -= bb; + var c0 = bb.MulByNonresidue(); + c0 += aa; + + return new Fp12(in c0, in c1); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/Fp2.cs b/src/Neo.Cryptography.BLS12_381/Fp2.cs new file mode 100644 index 0000000..49adf3e --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Fp2.cs @@ -0,0 +1,231 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Size)] +public readonly struct Fp2 : IEquatable, INumber +{ + [FieldOffset(0)] + public readonly Fp C0; + [FieldOffset(Fp.Size)] + public readonly Fp C1; + + public const int Size = Fp.Size * 2; + + private static readonly Fp2 _zero = new(); + private static readonly Fp2 _one = new(in Fp.One); + + static int INumber.Size => Size; + public static ref readonly Fp2 Zero => ref _zero; + public static ref readonly Fp2 One => ref _one; + + public bool IsZero => C0.IsZero & C1.IsZero; + + public Fp2(in Fp f) + : this(in f, in Fp.Zero) + { + } + + public Fp2(in Fp c0, in Fp c1) + { + C0 = c0; + C1 = c1; + } + + public static Fp2 Random(RandomNumberGenerator rng) + { + return new(Fp.Random(rng), Fp.Random(rng)); + } + + public static bool operator ==(in Fp2 a, in Fp2 b) + { + return a.C0 == b.C0 & a.C1 == b.C1; + } + + public static bool operator !=(in Fp2 a, in Fp2 b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not Fp2 other) return false; + return this == other; + } + + public bool Equals(Fp2 other) + { + return this == other; + } + + public override int GetHashCode() + { + return C0.GetHashCode() ^ C1.GetHashCode(); + } + + public Fp2 FrobeniusMap() + { + // This is always just a conjugation. If you're curious why, here's + // an article about it: https://alicebob.cryptoland.net/the-frobenius-endomorphism-with-finite-fields/ + return Conjugate(); + } + + public Fp2 Conjugate() + { + return new(in C0, -C1); + } + + public Fp2 MulByNonresidue() + { + // Multiply a + bu by u + 1, getting + // au + a + bu^2 + bu + // and because u^2 = -1, we get + // (a - b) + (a + b)u + + return new(C0 - C1, C0 + C1); + } + + public bool LexicographicallyLargest() + { + // If this element's c1 coefficient is lexicographically largest + // then it is lexicographically largest. Otherwise, in the event + // the c1 coefficient is zero and the c0 coefficient is + // lexicographically largest, then this element is lexicographically + // largest. + + return C1.LexicographicallyLargest() | (C1.IsZero & C0.LexicographicallyLargest()); + } + + public Fp2 Square() + { + // Complex squaring: + // + // v0 = c0 * c1 + // c0' = (c0 + c1) * (c0 + \beta*c1) - v0 - \beta * v0 + // c1' = 2 * v0 + // + // In BLS12-381's F_{p^2}, our \beta is -1 so we + // can modify this formula: + // + // c0' = (c0 + c1) * (c0 - c1) + // c1' = 2 * c0 * c1 + + var a = C0 + C1; + var b = C0 - C1; + var c = C0 + C0; + + return new(a * b, c * C1); + } + + public static Fp2 operator *(in Fp2 a, in Fp2 b) + { + // F_{p^2} x F_{p^2} multiplication implemented with operand scanning (schoolbook) + // computes the result as: + // + // a·b = (a_0 b_0 + a_1 b_1 β) + (a_0 b_1 + a_1 b_0)i + // + // In BLS12-381's F_{p^2}, our β is -1, so the resulting F_{p^2} element is: + // + // c_0 = a_0 b_0 - a_1 b_1 + // c_1 = a_0 b_1 + a_1 b_0 + // + // Each of these is a "sum of products", which we can compute efficiently. + + return new( + Fp.SumOfProducts(stackalloc[] { a.C0, -a.C1 }, stackalloc[] { b.C0, b.C1 }), + Fp.SumOfProducts(stackalloc[] { a.C0, a.C1 }, stackalloc[] { b.C1, b.C0 }) + ); + } + + public static Fp2 operator +(in Fp2 a, in Fp2 b) + { + return new(a.C0 + b.C0, a.C1 + b.C1); + } + + public static Fp2 operator -(in Fp2 a, in Fp2 b) + { + return new(a.C0 - b.C0, a.C1 - b.C1); + } + + public static Fp2 operator -(in Fp2 a) + { + return new(-a.C0, -a.C1); + } + + public Fp2 Sqrt() + { + // Algorithm 9, https://eprint.iacr.org/2012/685.pdf + // with constant time modifications. + + // a1 = self^((p - 3) / 4) + var a1 = this.PowVartime(new ulong[] + { + 0xee7f_bfff_ffff_eaaa, + 0x07aa_ffff_ac54_ffff, + 0xd9cc_34a8_3dac_3d89, + 0xd91d_d2e1_3ce1_44af, + 0x92c6_e9ed_90d2_eb35, + 0x0680_447a_8e5f_f9a6 + }); + + // alpha = a1^2 * self = self^((p - 3) / 2 + 1) = self^((p - 1) / 2) + var alpha = a1.Square() * this; + + // x0 = self^((p + 1) / 4) + var x0 = a1 * this; + + // (1 + alpha)^((q - 1) // 2) * x0 + var sqrt = (alpha + One).PowVartime(new ulong[] { + 0xdcff_7fff_ffff_d555, + 0x0f55_ffff_58a9_ffff, + 0xb398_6950_7b58_7b12, + 0xb23b_a5c2_79c2_895f, + 0x258d_d3db_21a5_d66b, + 0x0d00_88f5_1cbf_f34d, + }) * x0; + + // In the event that alpha = -1, the element is order p - 1 and so + // we're just trying to get the square of an element of the subfield + // Fp. This is given by x0 * u, since u = sqrt(-1). Since the element + // x0 = a + bu has b = 0, the solution is therefore au. + sqrt = ConditionalSelect(in sqrt, new(-x0.C1, in x0.C0), alpha == -One); + + sqrt = ConditionalSelect(in sqrt, in Zero, IsZero); + + // Only return the result if it's really the square root (and so + // self is actually quadratic nonresidue) + if (sqrt.Square() != this) throw new ArithmeticException(); + return sqrt; + } + + public Fp2 Invert() + { + if (!TryInvert(out Fp2 result)) + throw new DivideByZeroException(); + return result; + } + + public bool TryInvert(out Fp2 result) + { + // We wish to find the multiplicative inverse of a nonzero + // element a + bu in Fp2. We leverage an identity + // + // (a + bu)(a - bu) = a^2 + b^2 + // + // which holds because u^2 = -1. This can be rewritten as + // + // (a + bu)(a - bu)/(a^2 + b^2) = 1 + // + // because a^2 + b^2 = 0 has no nonzero solutions for (a, b). + // This gives that (a - bu)/(a^2 + b^2) is the inverse + // of (a + bu). Importantly, this can be computing using + // only a single inversion in Fp. + + bool s = (C0.Square() + C1.Square()).TryInvert(out Fp t); + result = new Fp2(C0 * t, C1 * -t); + return s; + } +} diff --git a/src/Neo.Cryptography.BLS12_381/Fp6.cs b/src/Neo.Cryptography.BLS12_381/Fp6.cs new file mode 100644 index 0000000..0c54125 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Fp6.cs @@ -0,0 +1,263 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Security.Cryptography; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Size)] +public readonly struct Fp6 : IEquatable, INumber +{ + [FieldOffset(0)] + public readonly Fp2 C0; + [FieldOffset(Fp2.Size)] + public readonly Fp2 C1; + [FieldOffset(Fp2.Size * 2)] + public readonly Fp2 C2; + + public const int Size = Fp2.Size * 3; + + private static readonly Fp6 _zero = new(); + private static readonly Fp6 _one = new(in Fp2.One); + + static int INumber.Size => Size; + public static ref readonly Fp6 Zero => ref _zero; + public static ref readonly Fp6 One => ref _one; + + public bool IsZero => C0.IsZero & C1.IsZero & C2.IsZero; + + public Fp6(in Fp f) + : this(new Fp2(in f), in Fp2.Zero, in Fp2.Zero) + { + } + + public Fp6(in Fp2 f) + : this(in f, in Fp2.Zero, in Fp2.Zero) + { + } + + public Fp6(in Fp2 c0, in Fp2 c1, in Fp2 c2) + { + C0 = c0; + C1 = c1; + C2 = c2; + } + + public static bool operator ==(in Fp6 a, in Fp6 b) + { + return a.C0 == b.C0 & a.C1 == b.C1 & a.C2 == b.C2; + } + + public static bool operator !=(in Fp6 a, in Fp6 b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not Fp6 other) return false; + return this == other; + } + + public bool Equals(Fp6 other) + { + return this == other; + } + + public override int GetHashCode() + { + return C0.GetHashCode() ^ C1.GetHashCode() ^ C2.GetHashCode(); + } + + public static Fp6 Random(RandomNumberGenerator rng) + { + return new(Fp2.Random(rng), Fp2.Random(rng), Fp2.Random(rng)); + } + + internal Fp6 MulBy_1(in Fp2 c1) + { + var b_b = C1 * c1; + + var t1 = (C1 + C2) * c1 - b_b; + t1 = t1.MulByNonresidue(); + + var t2 = (C0 + C1) * c1 - b_b; + + return new Fp6(in t1, in t2, in b_b); + } + + internal Fp6 MulBy_01(in Fp2 c0, in Fp2 c1) + { + var a_a = C0 * c0; + var b_b = C1 * c1; + + var t1 = (C1 + C2) * c1 - b_b; + t1 = t1.MulByNonresidue() + a_a; + + var t2 = (c0 + c1) * (C0 + C1) - a_a - b_b; + + var t3 = (C0 + C2) * c0 - a_a + b_b; + + return new Fp6(in t1, in t2, in t3); + } + + public Fp6 MulByNonresidue() + { + // Given a + bv + cv^2, this produces + // av + bv^2 + cv^3 + // but because v^3 = u + 1, we have + // c(u + 1) + av + v^2 + + return new Fp6(C2.MulByNonresidue(), in C0, in C1); + } + + public Fp6 FrobeniusMap() + { + var c0 = C0.FrobeniusMap(); + var c1 = C1.FrobeniusMap(); + var c2 = C2.FrobeniusMap(); + + // c1 = c1 * (u + 1)^((p - 1) / 3) + c1 *= new Fp2(in Fp.Zero, Fp.FromRawUnchecked(new ulong[] + { + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741 + })); + + // c2 = c2 * (u + 1)^((2p - 2) / 3) + c2 *= new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x890d_c9e4_8675_45c3, + 0x2af3_2253_3285_a5d5, + 0x5088_0866_309b_7e2c, + 0xa20d_1b8c_7e88_1024, + 0x14e4_f04f_e2db_9068, + 0x14e5_6d3f_1564_853a + }), in Fp.Zero); + + return new Fp6(c0, c1, c2); + } + + public Fp6 Square() + { + var s0 = C0.Square(); + var ab = C0 * C1; + var s1 = ab + ab; + var s2 = (C0 - C1 + C2).Square(); + var bc = C1 * C2; + var s3 = bc + bc; + var s4 = C2.Square(); + + return new Fp6( + s3.MulByNonresidue() + s0, + s4.MulByNonresidue() + s1, + s1 + s2 + s3 - s0 - s4 + ); + } + + public Fp6 Invert() + { + var c0 = (C1 * C2).MulByNonresidue(); + c0 = C0.Square() - c0; + + var c1 = C2.Square().MulByNonresidue(); + c1 -= C0 * C1; + + var c2 = C1.Square(); + c2 -= C0 * C2; + + var t = (C1 * c2 + C2 * c1).MulByNonresidue(); + t += C0 * c0; + + t = t.Invert(); + return new Fp6(t * c0, t * c1, t * c2); + } + + public static Fp6 operator -(in Fp6 a) + { + return new Fp6(-a.C0, -a.C1, -a.C2); + } + + public static Fp6 operator +(in Fp6 a, in Fp6 b) + { + return new Fp6(a.C0 + b.C0, a.C1 + b.C1, a.C2 + b.C2); + } + + public static Fp6 operator -(in Fp6 a, in Fp6 b) + { + return new Fp6(a.C0 - b.C0, a.C1 - b.C1, a.C2 - b.C2); + } + + public static Fp6 operator *(in Fp6 a, in Fp6 b) + { + // The intuition for this algorithm is that we can look at F_p^6 as a direct + // extension of F_p^2, and express the overall operations down to the base field + // F_p instead of only over F_p^2. This enables us to interleave multiplications + // and reductions, ensuring that we don't require double-width intermediate + // representations (with around twice as many limbs as F_p elements). + + // We want to express the multiplication c = a x b, where a = (a_0, a_1, a_2) is + // an element of F_p^6, and a_i = (a_i,0, a_i,1) is an element of F_p^2. The fully + // expanded multiplication is given by (2022-376 §5): + // + // c_0,0 = a_0,0 b_0,0 - a_0,1 b_0,1 + a_1,0 b_2,0 - a_1,1 b_2,1 + a_2,0 b_1,0 - a_2,1 b_1,1 + // - a_1,0 b_2,1 - a_1,1 b_2,0 - a_2,0 b_1,1 - a_2,1 b_1,0. + // = a_0,0 b_0,0 - a_0,1 b_0,1 + a_1,0 (b_2,0 - b_2,1) - a_1,1 (b_2,0 + b_2,1) + // + a_2,0 (b_1,0 - b_1,1) - a_2,1 (b_1,0 + b_1,1). + // + // c_0,1 = a_0,0 b_0,1 + a_0,1 b_0,0 + a_1,0 b_2,1 + a_1,1 b_2,0 + a_2,0 b_1,1 + a_2,1 b_1,0 + // + a_1,0 b_2,0 - a_1,1 b_2,1 + a_2,0 b_1,0 - a_2,1 b_1,1. + // = a_0,0 b_0,1 + a_0,1 b_0,0 + a_1,0(b_2,0 + b_2,1) + a_1,1(b_2,0 - b_2,1) + // + a_2,0(b_1,0 + b_1,1) + a_2,1(b_1,0 - b_1,1). + // + // c_1,0 = a_0,0 b_1,0 - a_0,1 b_1,1 + a_1,0 b_0,0 - a_1,1 b_0,1 + a_2,0 b_2,0 - a_2,1 b_2,1 + // - a_2,0 b_2,1 - a_2,1 b_2,0. + // = a_0,0 b_1,0 - a_0,1 b_1,1 + a_1,0 b_0,0 - a_1,1 b_0,1 + a_2,0(b_2,0 - b_2,1) + // - a_2,1(b_2,0 + b_2,1). + // + // c_1,1 = a_0,0 b_1,1 + a_0,1 b_1,0 + a_1,0 b_0,1 + a_1,1 b_0,0 + a_2,0 b_2,1 + a_2,1 b_2,0 + // + a_2,0 b_2,0 - a_2,1 b_2,1 + // = a_0,0 b_1,1 + a_0,1 b_1,0 + a_1,0 b_0,1 + a_1,1 b_0,0 + a_2,0(b_2,0 + b_2,1) + // + a_2,1(b_2,0 - b_2,1). + // + // c_2,0 = a_0,0 b_2,0 - a_0,1 b_2,1 + a_1,0 b_1,0 - a_1,1 b_1,1 + a_2,0 b_0,0 - a_2,1 b_0,1. + // c_2,1 = a_0,0 b_2,1 + a_0,1 b_2,0 + a_1,0 b_1,1 + a_1,1 b_1,0 + a_2,0 b_0,1 + a_2,1 b_0,0. + // + // Each of these is a "sum of products", which we can compute efficiently. + + var b10_p_b11 = b.C1.C0 + b.C1.C1; + var b10_m_b11 = b.C1.C0 - b.C1.C1; + var b20_p_b21 = b.C2.C0 + b.C2.C1; + var b20_m_b21 = b.C2.C0 - b.C2.C1; + + return new Fp6(new Fp2( + Fp.SumOfProducts( + stackalloc[] { a.C0.C0, -a.C0.C1, a.C1.C0, -a.C1.C1, a.C2.C0, -a.C2.C1 }, + stackalloc[] { b.C0.C0, b.C0.C1, b20_m_b21, b20_p_b21, b10_m_b11, b10_p_b11 } + ), + Fp.SumOfProducts( + stackalloc[] { a.C0.C0, a.C0.C1, a.C1.C0, a.C1.C1, a.C2.C0, a.C2.C1 }, + stackalloc[] { b.C0.C1, b.C0.C0, b20_p_b21, b20_m_b21, b10_p_b11, b10_m_b11 } + )), new Fp2( + Fp.SumOfProducts( + stackalloc[] { a.C0.C0, -a.C0.C1, a.C1.C0, -a.C1.C1, a.C2.C0, -a.C2.C1 }, + stackalloc[] { b.C1.C0, b.C1.C1, b.C0.C0, b.C0.C1, b20_m_b21, b20_p_b21 } + ), + Fp.SumOfProducts( + stackalloc[] { a.C0.C0, a.C0.C1, a.C1.C0, a.C1.C1, a.C2.C0, a.C2.C1 }, + stackalloc[] { b.C1.C1, b.C1.C0, b.C0.C1, b.C0.C0, b20_p_b21, b20_m_b21 } + )), new Fp2( + Fp.SumOfProducts( + stackalloc[] { a.C0.C0, -a.C0.C1, a.C1.C0, -a.C1.C1, a.C2.C0, -a.C2.C1 }, + stackalloc[] { b.C2.C0, b.C2.C1, b.C1.C0, b.C1.C1, b.C0.C0, b.C0.C1 } + ), + Fp.SumOfProducts( + stackalloc[] { a.C0.C0, a.C0.C1, a.C1.C0, a.C1.C1, a.C2.C0, a.C2.C1 }, + stackalloc[] { b.C2.C1, b.C2.C0, b.C1.C1, b.C1.C0, b.C0.C1, b.C0.C0 } + )) + ); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/FpConstants.cs b/src/Neo.Cryptography.BLS12_381/FpConstants.cs new file mode 100644 index 0000000..1a07b76 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/FpConstants.cs @@ -0,0 +1,73 @@ +namespace Neo.Cryptography.BLS12_381; + +static class FpConstants +{ + // p = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 + public static readonly ulong[] MODULUS = + { + 0xb9fe_ffff_ffff_aaab, + 0x1eab_fffe_b153_ffff, + 0x6730_d2a0_f6b0_f624, + 0x6477_4b84_f385_12bf, + 0x4b1b_a7b6_434b_acd7, + 0x1a01_11ea_397f_e69a + }; + + // p - 2 + public static readonly ulong[] P_2 = + { + 0xb9fe_ffff_ffff_aaa9, + 0x1eab_fffe_b153_ffff, + 0x6730_d2a0_f6b0_f624, + 0x6477_4b84_f385_12bf, + 0x4b1b_a7b6_434b_acd7, + 0x1a01_11ea_397f_e69a + }; + + // (p + 1) / 4 + public static readonly ulong[] P_1_4 = + { + 0xee7f_bfff_ffff_eaab, + 0x07aa_ffff_ac54_ffff, + 0xd9cc_34a8_3dac_3d89, + 0xd91d_d2e1_3ce1_44af, + 0x92c6_e9ed_90d2_eb35, + 0x0680_447a_8e5f_f9a6 + }; + + // INV = -(p^{-1} mod 2^64) mod 2^64 + public const ulong INV = 0x89f3_fffc_fffc_fffd; + + // R = 2^384 mod p + public static readonly Fp R = Fp.FromRawUnchecked(new ulong[] + { + 0x7609_0000_0002_fffd, + 0xebf4_000b_c40c_0002, + 0x5f48_9857_53c7_58ba, + 0x77ce_5853_7052_5745, + 0x5c07_1a97_a256_ec6d, + 0x15f6_5ec3_fa80_e493 + }); + + // R2 = 2^(384*2) mod p + public static readonly Fp R2 = Fp.FromRawUnchecked(new ulong[] + { + 0xf4df_1f34_1c34_1746, + 0x0a76_e6a6_09d1_04f1, + 0x8de5_476c_4c95_b6d5, + 0x67eb_88a9_939d_83c0, + 0x9a79_3e85_b519_952d, + 0x1198_8fe5_92ca_e3aa + }); + + // R3 = 2^(384*3) mod p + public static readonly Fp R3 = Fp.FromRawUnchecked(new ulong[] + { + 0xed48_ac6b_d94c_a1e0, + 0x315f_831e_03a7_adf8, + 0x9a53_352a_615e_29dd, + 0x34c0_4e5e_921e_1761, + 0x2512_d435_6572_4728, + 0x0aa6_3460_9175_5d4d + }); +} diff --git a/src/Neo.Cryptography.BLS12_381/G1Affine.cs b/src/Neo.Cryptography.BLS12_381/G1Affine.cs new file mode 100644 index 0000000..31168cf --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G1Affine.cs @@ -0,0 +1,170 @@ +using System.Diagnostics.CodeAnalysis; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.G1Constants; + +namespace Neo.Cryptography.BLS12_381; + +public readonly struct G1Affine : IEquatable +{ + public readonly Fp X; + public readonly Fp Y; + public readonly bool Infinity; + + public static readonly G1Affine Identity = new(in Fp.Zero, in Fp.One, true); + public static readonly G1Affine Generator = new(in GeneratorX, in GeneratorY, false); + + public bool IsIdentity => Infinity; + public bool IsTorsionFree => -new G1Projective(this).MulByX().MulByX() == new G1Projective(Endomorphism()); + public bool IsOnCurve => ((Y.Square() - (X.Square() * X)) == B) | Infinity; + + public G1Affine(in Fp x, in Fp y) + : this(in x, in y, false) + { + } + + private G1Affine(in Fp x, in Fp y, bool infinity) + { + X = x; + Y = y; + Infinity = infinity; + } + + public G1Affine(in G1Projective p) + { + bool s = p.Z.TryInvert(out Fp zinv); + + zinv = ConditionalSelect(in Fp.Zero, in zinv, s); + Fp x = p.X * zinv; + Fp y = p.Y * zinv; + + G1Affine tmp = new(in x, in y, false); + this = ConditionalSelect(in tmp, in Identity, !s); + } + + public static G1Affine FromUncompressed(ReadOnlySpan data) + { + return FromBytes(data, false, true); + } + + public static G1Affine FromCompressed(ReadOnlySpan data) + { + return FromBytes(data, true, true); + } + + private static G1Affine FromBytes(ReadOnlySpan data, bool compressed, bool check) + { + bool compression_flag_set = (data[0] & 0x80) != 0; + bool infinity_flag_set = (data[0] & 0x40) != 0; + bool sort_flag_set = (data[0] & 0x20) != 0; + byte[] tmp = data[0..48].ToArray(); + tmp[0] &= 0b0001_1111; + Fp x = Fp.FromBytes(tmp); + if (compressed) + { + Fp y = ((x.Square() * x) + B).Sqrt(); + y = ConditionalSelect(in y, -y, y.LexicographicallyLargest() ^ sort_flag_set); + G1Affine result = new(in x, in y, infinity_flag_set); + result = ConditionalSelect(in result, in Identity, infinity_flag_set); + if (check) + { + bool _checked = (!infinity_flag_set | (infinity_flag_set & !sort_flag_set & x.IsZero)) + & compression_flag_set; + _checked &= result.IsTorsionFree; + if (!_checked) throw new FormatException(); + } + return result; + } + else + { + Fp y = Fp.FromBytes(data[48..96]); + G1Affine result = ConditionalSelect(new(in x, in y, infinity_flag_set), in Identity, infinity_flag_set); + if (check) + { + bool _checked = (!infinity_flag_set | (infinity_flag_set & x.IsZero & y.IsZero)) + & !compression_flag_set + & !sort_flag_set; + _checked &= result.IsOnCurve & result.IsTorsionFree; + if (!_checked) throw new FormatException(); + } + return result; + } + } + + public static bool operator ==(in G1Affine a, in G1Affine b) + { + return (a.Infinity & b.Infinity) | (!a.Infinity & !b.Infinity & a.X == b.X & a.Y == b.Y); + } + + public static bool operator !=(in G1Affine a, in G1Affine b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not G1Affine other) return false; + return this == other; + } + + public bool Equals(G1Affine other) + { + return this == other; + } + + public override int GetHashCode() + { + if (Infinity) return Infinity.GetHashCode(); + return X.GetHashCode() ^ Y.GetHashCode(); + } + + public static G1Affine operator -(in G1Affine p) + { + return new G1Affine(in p.X, ConditionalSelect(-p.Y, in Fp.One, p.Infinity), p.Infinity); + } + + public byte[] ToCompressed() + { + byte[] res = ConditionalSelect(in X, in Fp.Zero, Infinity).ToArray(); + + // This point is in compressed form, so we set the most significant bit. + res[0] |= 0x80; + + // Is this point at infinity? If so, set the second-most significant bit. + res[0] |= ConditionalSelect((byte)0, (byte)0x40, Infinity); + + // Is the y-coordinate the lexicographically largest of the two associated with the + // x-coordinate? If so, set the third-most significant bit so long as this is not + // the point at infinity. + res[0] |= ConditionalSelect((byte)0, (byte)0x20, !Infinity & Y.LexicographicallyLargest()); + + return res; + } + + public byte[] ToUncompressed() + { + byte[] res = GC.AllocateUninitializedArray(96); + + ConditionalSelect(in X, in Fp.Zero, Infinity).TryWrite(res.AsSpan(0..48)); + ConditionalSelect(in Y, in Fp.Zero, Infinity).TryWrite(res.AsSpan(48..96)); + + // Is this point at infinity? If so, set the second-most significant bit. + res[0] |= ConditionalSelect((byte)0, (byte)0x40, Infinity); + + return res; + } + + public G1Projective ToCurve() + { + return new(this); + } + + private G1Affine Endomorphism() + { + return new(X * BETA, in Y, Infinity); + } + + public static G1Projective operator *(in G1Affine a, in Scalar b) + { + return new G1Projective(in a) * b.ToArray(); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/G1Constants.cs b/src/Neo.Cryptography.BLS12_381/G1Constants.cs new file mode 100644 index 0000000..400617f --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G1Constants.cs @@ -0,0 +1,44 @@ +namespace Neo.Cryptography.BLS12_381; + +static class G1Constants +{ + public static readonly Fp GeneratorX = Fp.FromRawUnchecked(new ulong[] + { + 0x5cb3_8790_fd53_0c16, + 0x7817_fc67_9976_fff5, + 0x154f_95c7_143b_a1c1, + 0xf0ae_6acd_f3d0_e747, + 0xedce_6ecc_21db_f440, + 0x1201_7741_9e0b_fb75 + }); + + public static readonly Fp GeneratorY = Fp.FromRawUnchecked(new ulong[] + { + 0xbaac_93d5_0ce7_2271, + 0x8c22_631a_7918_fd8e, + 0xdd59_5f13_5707_25ce, + 0x51ac_5829_5040_5194, + 0x0e1c_8c3f_ad00_59c0, + 0x0bbc_3efc_5008_a26a + }); + + public static readonly Fp B = Fp.FromRawUnchecked(new ulong[] + { + 0xaa27_0000_000c_fff3, + 0x53cc_0032_fc34_000a, + 0x478f_e97a_6b0a_807f, + 0xb1d3_7ebe_e6ba_24d7, + 0x8ec9_733b_bf78_ab2f, + 0x09d6_4551_3d83_de7e + }); + + public static readonly Fp BETA = Fp.FromRawUnchecked(new ulong[] + { + 0x30f1_361b_798a_64e8, + 0xf3b8_ddab_7ece_5a2a, + 0x16a8_ca3a_c615_77f7, + 0xc26a_2ff8_74fd_029b, + 0x3636_b766_6070_1c6e, + 0x051b_a4ab_241b_6160 + }); +} diff --git a/src/Neo.Cryptography.BLS12_381/G1Projective.cs b/src/Neo.Cryptography.BLS12_381/G1Projective.cs new file mode 100644 index 0000000..0d9eba0 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G1Projective.cs @@ -0,0 +1,283 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using static Neo.Cryptography.BLS12_381.Constants; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.G1Constants; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Fp.Size * 3)] +public readonly struct G1Projective : IEquatable +{ + [FieldOffset(0)] + public readonly Fp X; + [FieldOffset(Fp.Size)] + public readonly Fp Y; + [FieldOffset(Fp.Size * 2)] + public readonly Fp Z; + + public static readonly G1Projective Identity = new(in Fp.Zero, in Fp.One, in Fp.Zero); + public static readonly G1Projective Generator = new(in GeneratorX, in GeneratorY, in Fp.One); + + public bool IsIdentity => Z.IsZero; + public bool IsOnCurve => ((Y.Square() * Z) == (X.Square() * X + Z.Square() * Z * B)) | Z.IsZero; + + public G1Projective(in Fp x, in Fp y, in Fp z) + { + X = x; + Y = y; + Z = z; + } + + public G1Projective(in G1Affine p) + : this(in p.X, in p.Y, ConditionalSelect(in Fp.One, in Fp.Zero, p.Infinity)) + { + } + + public static bool operator ==(in G1Projective a, in G1Projective b) + { + // Is (xz, yz, z) equal to (x'z', y'z', z') when converted to affine? + + Fp x1 = a.X * b.Z; + Fp x2 = b.X * a.Z; + + Fp y1 = a.Y * b.Z; + Fp y2 = b.Y * a.Z; + + bool self_is_zero = a.Z.IsZero; + bool other_is_zero = b.Z.IsZero; + + // Both point at infinity. Or neither point at infinity, coordinates are the same. + return (self_is_zero & other_is_zero) | ((!self_is_zero) & (!other_is_zero) & x1 == x2 & y1 == y2); + } + + public static bool operator !=(in G1Projective a, in G1Projective b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not G1Projective other) return false; + return this == other; + } + + public bool Equals(G1Projective other) + { + return this == other; + } + + public override int GetHashCode() + { + return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); + } + + public static G1Projective operator -(in G1Projective p) + { + return new G1Projective(in p.X, -p.Y, in p.Z); + } + + private static Fp MulBy3B(in Fp a) + { + Fp b = a + a; + b += b; + return b + b + b; + } + + public G1Projective Double() + { + Fp t0 = Y.Square(); + Fp z3 = t0 + t0; + z3 += z3; + z3 += z3; + Fp t1 = Y * Z; + Fp t2 = Z.Square(); + t2 = MulBy3B(in t2); + Fp x3 = t2 * z3; + Fp y3 = t0 + t2; + z3 = t1 * z3; + t1 = t2 + t2; + t2 = t1 + t2; + t0 -= t2; + y3 = t0 * y3; + y3 = x3 + y3; + t1 = X * Y; + x3 = t0 * t1; + x3 += x3; + + G1Projective tmp = new(in x3, in y3, in z3); + return ConditionalSelect(in tmp, in Identity, IsIdentity); + } + + public static G1Projective operator +(in G1Projective a, in G1Projective b) + { + Fp t0 = a.X * b.X; + Fp t1 = a.Y * b.Y; + Fp t2 = a.Z * b.Z; + Fp t3 = a.X + a.Y; + Fp t4 = b.X + b.Y; + t3 *= t4; + t4 = t0 + t1; + t3 -= t4; + t4 = a.Y + a.Z; + Fp x3 = b.Y + b.Z; + t4 *= x3; + x3 = t1 + t2; + t4 -= x3; + x3 = a.X + a.Z; + Fp y3 = b.X + b.Z; + x3 *= y3; + y3 = t0 + t2; + y3 = x3 - y3; + x3 = t0 + t0; + t0 = x3 + t0; + t2 = MulBy3B(in t2); + Fp z3 = t1 + t2; + t1 -= t2; + y3 = MulBy3B(in y3); + x3 = t4 * y3; + t2 = t3 * t1; + x3 = t2 - x3; + y3 *= t0; + t1 *= z3; + y3 = t1 + y3; + t0 *= t3; + z3 *= t4; + z3 += t0; + + return new G1Projective(in x3, in y3, in z3); + } + + public static G1Projective operator +(in G1Projective a, in G1Affine b) + { + Fp t0 = a.X * b.X; + Fp t1 = a.Y * b.Y; + Fp t3 = b.X + b.Y; + Fp t4 = a.X + a.Y; + t3 *= t4; + t4 = t0 + t1; + t3 -= t4; + t4 = b.Y * a.Z; + t4 += a.Y; + Fp y3 = b.X * a.Z; + y3 += a.X; + Fp x3 = t0 + t0; + t0 = x3 + t0; + Fp t2 = MulBy3B(in a.Z); + Fp z3 = t1 + t2; + t1 -= t2; + y3 = MulBy3B(in y3); + x3 = t4 * y3; + t2 = t3 * t1; + x3 = t2 - x3; + y3 *= t0; + t1 *= z3; + y3 = t1 + y3; + t0 *= t3; + z3 *= t4; + z3 += t0; + + G1Projective tmp = new(in x3, in y3, in z3); + return ConditionalSelect(in tmp, in a, b.IsIdentity); + } + + public static G1Projective operator +(in G1Affine a, in G1Projective b) + { + return b + a; + } + + public static G1Projective operator -(in G1Projective a, in G1Projective b) + { + return a + -b; + } + + public static G1Projective operator -(in G1Projective a, in G1Affine b) + { + return a + -b; + } + + public static G1Projective operator -(in G1Affine a, in G1Projective b) + { + return -b + a; + } + + public static G1Projective operator *(in G1Projective a, byte[] b) + { + int length = b.Length; + if (length != 32) + throw new ArgumentException($"The argument {nameof(b)} should be 32 bytes."); + + G1Projective acc = Identity; + + foreach (bool bit in b + .SelectMany(p => Enumerable.Range(0, 8).Select(q => ((p >> q) & 1) == 1)) + .Reverse() + .Skip(1)) + { + acc = acc.Double(); + acc = ConditionalSelect(in acc, acc + a, bit); + } + + return acc; + } + + public static G1Projective operator *(in G1Projective a, in Scalar b) + { + return a * b.ToArray(); + } + + internal G1Projective MulByX() + { + G1Projective xself = Identity; + + ulong x = BLS_X >> 1; + G1Projective tmp = this; + while (x > 0) + { + tmp = tmp.Double(); + + if (x % 2 == 1) + { + xself += tmp; + } + x >>= 1; + } + + if (BLS_X_IS_NEGATIVE) + { + xself = -xself; + } + return xself; + } + + public G1Projective ClearCofactor() + { + return this - MulByX(); + } + + public static void BatchNormalize(ReadOnlySpan p, Span q) + { + int length = p.Length; + if (length != q.Length) + throw new ArgumentException($"{nameof(p)} and {nameof(q)} must have the same length."); + + Span x = stackalloc Fp[length]; + Fp acc = Fp.One; + for (int i = 0; i < length; i++) + { + x[i] = acc; + acc = ConditionalSelect(acc * p[i].Z, in acc, p[i].IsIdentity); + } + + acc = acc.Invert(); + + for (int i = length - 1; i >= 0; i--) + { + bool skip = p[i].IsIdentity; + Fp tmp = x[i] * acc; + acc = ConditionalSelect(acc * p[i].Z, in acc, skip); + G1Affine qi = new(p[i].X * tmp, p[i].Y * tmp); + q[i] = ConditionalSelect(in qi, in G1Affine.Identity, skip); + } + } +} diff --git a/src/Neo.Cryptography.BLS12_381/G2Affine.cs b/src/Neo.Cryptography.BLS12_381/G2Affine.cs new file mode 100644 index 0000000..35c9456 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G2Affine.cs @@ -0,0 +1,214 @@ +using System.Diagnostics.CodeAnalysis; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.G2Constants; + +namespace Neo.Cryptography.BLS12_381; + +public readonly struct G2Affine : IEquatable +{ + public readonly Fp2 X; + public readonly Fp2 Y; + public readonly bool Infinity; + + public static readonly G2Affine Identity = new(in Fp2.Zero, in Fp2.One, true); + public static readonly G2Affine Generator = new(in GeneratorX, in GeneratorY, false); + + public bool IsIdentity => Infinity; + public bool IsTorsionFree + { + get + { + // Algorithm from Section 4 of https://eprint.iacr.org/2021/1130 + // Updated proof of correctness in https://eprint.iacr.org/2022/352 + // + // Check that psi(P) == [x] P + var p = new G2Projective(this); + return p.Psi() == p.MulByX(); + } + } + public bool IsOnCurve => ((Y.Square() - X.Square() * X) == B) | Infinity; // y^2 - x^3 ?= 4(u + 1) + + public G2Affine(in Fp2 x, in Fp2 y) + : this(in x, in y, false) + { + } + + private G2Affine(in Fp2 x, in Fp2 y, bool infinity) + { + X = x; + Y = y; + Infinity = infinity; + } + + public G2Affine(in G2Projective p) + { + bool s = p.Z.TryInvert(out Fp2 zinv); + + zinv = ConditionalSelect(in Fp2.Zero, in zinv, s); + Fp2 x = p.X * zinv; + Fp2 y = p.Y * zinv; + + G2Affine tmp = new(in x, in y, false); + this = ConditionalSelect(in tmp, in Identity, !s); + } + + public static bool operator ==(in G2Affine a, in G2Affine b) + { + // The only cases in which two points are equal are + // 1. infinity is set on both + // 2. infinity is not set on both, and their coordinates are equal + + return (a.Infinity & b.Infinity) | (!a.Infinity & !b.Infinity & a.X == b.X & a.Y == b.Y); + } + + public static bool operator !=(in G2Affine a, in G2Affine b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not G2Affine other) return false; + return this == other; + } + + public bool Equals(G2Affine other) + { + return this == other; + } + + public override int GetHashCode() + { + if (Infinity) return Infinity.GetHashCode(); + return X.GetHashCode() ^ Y.GetHashCode(); + } + + public static G2Affine operator -(in G2Affine a) + { + return new G2Affine( + in a.X, + ConditionalSelect(-a.Y, in Fp2.One, a.Infinity), + a.Infinity + ); + } + + public static G2Projective operator *(in G2Affine a, in Scalar b) + { + return new G2Projective(a) * b.ToArray(); + } + + public byte[] ToCompressed() + { + // Strictly speaking, self.x is zero already when self.infinity is true, but + // to guard against implementation mistakes we do not assume this. + var x = ConditionalSelect(in X, in Fp2.Zero, Infinity); + + var res = GC.AllocateUninitializedArray(96); + + x.C1.TryWrite(res.AsSpan(0..48)); + x.C0.TryWrite(res.AsSpan(48..96)); + + // This point is in compressed form, so we set the most significant bit. + res[0] |= 0x80; + + // Is this point at infinity? If so, set the second-most significant bit. + res[0] |= ConditionalSelect((byte)0, (byte)0x40, Infinity); + + // Is the y-coordinate the lexicographically largest of the two associated with the + // x-coordinate? If so, set the third-most significant bit so long as this is not + // the point at infinity. + res[0] |= ConditionalSelect((byte)0, (byte)0x20, !Infinity & Y.LexicographicallyLargest()); + + return res; + } + + public byte[] ToUncompressed() + { + var res = GC.AllocateUninitializedArray(192); + + var x = ConditionalSelect(in X, in Fp2.Zero, Infinity); + var y = ConditionalSelect(in Y, in Fp2.Zero, Infinity); + + x.C1.TryWrite(res.AsSpan(0..48)); + x.C0.TryWrite(res.AsSpan(48..96)); + y.C1.TryWrite(res.AsSpan(96..144)); + y.C0.TryWrite(res.AsSpan(144..192)); + + // Is this point at infinity? If so, set the second-most significant bit. + res[0] |= ConditionalSelect((byte)0, (byte)0x40, Infinity); + + return res; + } + + public static G2Affine FromUncompressed(ReadOnlySpan bytes) + { + return FromBytes(bytes, false, true); + } + + public static G2Affine FromCompressed(ReadOnlySpan bytes) + { + return FromBytes(bytes, true, true); + } + + private static G2Affine FromBytes(ReadOnlySpan bytes, bool compressed, bool check) + { + // Obtain the three flags from the start of the byte sequence + bool compression_flag_set = (bytes[0] & 0x80) != 0; + bool infinity_flag_set = (bytes[0] & 0x40) != 0; + bool sort_flag_set = (bytes[0] & 0x20) != 0; + + // Attempt to obtain the x-coordinate + var tmp = bytes[0..48].ToArray(); + tmp[0] &= 0b0001_1111; + var xc1 = Fp.FromBytes(tmp); + var xc0 = Fp.FromBytes(bytes[48..96]); + var x = new Fp2(in xc0, in xc1); + + if (compressed) + { + // Recover a y-coordinate given x by y = sqrt(x^3 + 4) + var y = ((x.Square() * x) + B).Sqrt(); + y = ConditionalSelect(in y, -y, y.LexicographicallyLargest() ^ sort_flag_set); + G2Affine result = new(in x, in y, infinity_flag_set); + result = ConditionalSelect(in result, in Identity, infinity_flag_set); + if (check) + { + bool _checked = (!infinity_flag_set | (infinity_flag_set & !sort_flag_set & x.IsZero)) + & compression_flag_set; + _checked &= result.IsTorsionFree; + if (!_checked) throw new FormatException(); + } + return result; + } + else + { + // Attempt to obtain the y-coordinate + var yc1 = Fp.FromBytes(bytes[96..144]); + var yc0 = Fp.FromBytes(bytes[144..192]); + var y = new Fp2(in yc0, in yc1); + + // Create a point representing this value + var p = ConditionalSelect(new G2Affine(in x, in y, infinity_flag_set), in Identity, infinity_flag_set); + + if (check) + { + bool _checked = + // If the infinity flag is set, the x and y coordinates should have been zero. + ((!infinity_flag_set) | (infinity_flag_set & x.IsZero & y.IsZero)) & + // The compression flag should not have been set, as this is an uncompressed element + (!compression_flag_set) & + // The sort flag should not have been set, as this is an uncompressed element + (!sort_flag_set); + _checked &= p.IsOnCurve & p.IsTorsionFree; + if (!_checked) throw new FormatException(); + } + + return p; + } + } + + public G2Projective ToCurve() + { + return new(this); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/G2Constants.cs b/src/Neo.Cryptography.BLS12_381/G2Constants.cs new file mode 100644 index 0000000..ba2706b --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G2Constants.cs @@ -0,0 +1,101 @@ +namespace Neo.Cryptography.BLS12_381; + +static class G2Constants +{ + public static readonly Fp2 GeneratorX = new(Fp.FromRawUnchecked(new ulong[] + { + 0xf5f2_8fa2_0294_0a10, + 0xb3f5_fb26_87b4_961a, + 0xa1a8_93b5_3e2a_e580, + 0x9894_999d_1a3c_aee9, + 0x6f67_b763_1863_366b, + 0x0581_9192_4350_bcd7 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xa5a9_c075_9e23_f606, + 0xaaa0_c59d_bccd_60c3, + 0x3bb1_7e18_e286_7806, + 0x1b1a_b6cc_8541_b367, + 0xc2b6_ed0e_f215_8547, + 0x1192_2a09_7360_edf3 + })); + + public static readonly Fp2 GeneratorY = new(Fp.FromRawUnchecked(new ulong[] + { + 0x4c73_0af8_6049_4c4a, + 0x597c_fa1f_5e36_9c5a, + 0xe7e6_856c_aa0a_635a, + 0xbbef_b5e9_6e0d_495f, + 0x07d3_a975_f0ef_25a2, + 0x0083_fd8e_7e80_dae5 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xadc0_fc92_df64_b05d, + 0x18aa_270a_2b14_61dc, + 0x86ad_ac6a_3be4_eba0, + 0x7949_5c4e_c93d_a33a, + 0xe717_5850_a43c_caed, + 0x0b2b_c2a1_63de_1bf2 + })); + + public static readonly Fp2 B = new(Fp.FromRawUnchecked(new ulong[] + { + 0xaa27_0000_000c_fff3, + 0x53cc_0032_fc34_000a, + 0x478f_e97a_6b0a_807f, + 0xb1d3_7ebe_e6ba_24d7, + 0x8ec9_733b_bf78_ab2f, + 0x09d6_4551_3d83_de7e + }), Fp.FromRawUnchecked(new ulong[] + { + 0xaa27_0000_000c_fff3, + 0x53cc_0032_fc34_000a, + 0x478f_e97a_6b0a_807f, + 0xb1d3_7ebe_e6ba_24d7, + 0x8ec9_733b_bf78_ab2f, + 0x09d6_4551_3d83_de7e + })); + + public static readonly Fp2 B3 = B + B + B; + + // 1 / ((u+1) ^ ((q-1)/3)) + public static readonly Fp2 PsiCoeffX = new(in Fp.Zero, Fp.FromRawUnchecked(new ulong[] + { + 0x890dc9e4867545c3, + 0x2af322533285a5d5, + 0x50880866309b7e2c, + 0xa20d1b8c7e881024, + 0x14e4f04fe2db9068, + 0x14e56d3f1564853a + })); + + // 1 / ((u+1) ^ (p-1)/2) + public static readonly Fp2 PsiCoeffY = new(Fp.FromRawUnchecked(new ulong[] + { + 0x3e2f585da55c9ad1, + 0x4294213d86c18183, + 0x382844c88b623732, + 0x92ad2afd19103e18, + 0x1d794e4fac7cf0b9, + 0x0bd592fc7d825ec8 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x7bcfa7a25aa30fda, + 0xdc17dec12a927e7c, + 0x2f088dd86b4ebef1, + 0xd1ca2087da74d4a7, + 0x2da2596696cebc1d, + 0x0e2b7eedbbfd87d2 + })); + + // 1 / 2 ^ ((q-1)/3) + public static readonly Fp2 Psi2CoeffX = new(Fp.FromRawUnchecked(new ulong[] + { + 0xcd03c9e48671f071, + 0x5dab22461fcda5d2, + 0x587042afd3851b95, + 0x8eb60ebe01bacb9e, + 0x03f97d6e83d050d2, + 0x18f0206554638741 + }), in Fp.Zero); +} diff --git a/src/Neo.Cryptography.BLS12_381/G2Prepared.Adder.cs b/src/Neo.Cryptography.BLS12_381/G2Prepared.Adder.cs new file mode 100644 index 0000000..e980bd4 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G2Prepared.Adder.cs @@ -0,0 +1,40 @@ +using static Neo.Cryptography.BLS12_381.MillerLoopUtility; + +namespace Neo.Cryptography.BLS12_381; + +partial class G2Prepared +{ + class Adder : IMillerLoopDriver + { + public G2Projective Curve; + public readonly G2Affine Base; + public readonly List<(Fp2, Fp2, Fp2)> Coeffs; + + public Adder(in G2Affine q) + { + Curve = new G2Projective(in q); + Base = q; + Coeffs = new(68); + } + + object? IMillerLoopDriver.DoublingStep(in object? f) + { + var coeffs = DoublingStep(ref Curve); + Coeffs.Add(coeffs); + return null; + } + + object? IMillerLoopDriver.AdditionStep(in object? f) + { + var coeffs = AdditionStep(ref Curve, in Base); + Coeffs.Add(coeffs); + return null; + } + + static object? IMillerLoopDriver.SquareOutput(in object? f) => null; + + static object? IMillerLoopDriver.Conjugate(in object? f) => null; + + static object? IMillerLoopDriver.One => null; + } +} diff --git a/src/Neo.Cryptography.BLS12_381/G2Prepared.cs b/src/Neo.Cryptography.BLS12_381/G2Prepared.cs new file mode 100644 index 0000000..36dae93 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G2Prepared.cs @@ -0,0 +1,20 @@ +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.MillerLoopUtility; + +namespace Neo.Cryptography.BLS12_381; + +partial class G2Prepared +{ + public readonly bool Infinity; + public readonly List<(Fp2, Fp2, Fp2)> Coeffs; + + public G2Prepared(in G2Affine q) + { + Infinity = q.IsIdentity; + var q2 = ConditionalSelect(in q, in G2Affine.Generator, Infinity); + var adder = new Adder(q2); + MillerLoop(adder); + Coeffs = adder.Coeffs; + if (Coeffs.Count != 68) throw new InvalidOperationException(); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/G2Projective.cs b/src/Neo.Cryptography.BLS12_381/G2Projective.cs new file mode 100644 index 0000000..4f6bc30 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G2Projective.cs @@ -0,0 +1,366 @@ +using System.Buffers.Binary; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using static Neo.Cryptography.BLS12_381.Constants; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.G2Constants; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Fp2.Size * 3)] +public readonly struct G2Projective : IEquatable +{ + [FieldOffset(0)] + public readonly Fp2 X; + [FieldOffset(Fp2.Size)] + public readonly Fp2 Y; + [FieldOffset(Fp2.Size * 2)] + public readonly Fp2 Z; + + public static readonly G2Projective Identity = new(in Fp2.Zero, in Fp2.One, in Fp2.Zero); + public static readonly G2Projective Generator = new(in GeneratorX, in GeneratorY, in Fp2.One); + + public bool IsIdentity => Z.IsZero; + public bool IsOnCurve => ((Y.Square() * Z) == (X.Square() * X + Z.Square() * Z * B)) | Z.IsZero; // Y^2 Z = X^3 + b Z^3 + + public G2Projective(in Fp2 x, in Fp2 y, in Fp2 z) + { + X = x; + Y = y; + Z = z; + } + + public G2Projective(in G2Affine p) + { + X = p.X; + Y = p.Y; + Z = ConditionalSelect(in Fp2.One, in Fp2.Zero, p.Infinity); + } + + public static bool operator ==(in G2Projective a, in G2Projective b) + { + // Is (xz, yz, z) equal to (x'z', y'z', z') when converted to affine? + + var x1 = a.X * b.Z; + var x2 = b.X * a.Z; + + var y1 = a.Y * b.Z; + var y2 = b.Y * a.Z; + + var self_is_zero = a.Z.IsZero; + var other_is_zero = b.Z.IsZero; + + return (self_is_zero & other_is_zero) // Both point at infinity + | ((!self_is_zero) & (!other_is_zero) & x1 == x2 & y1 == y2); + // Neither point at infinity, coordinates are the same + } + + public static bool operator !=(in G2Projective a, in G2Projective b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not G2Projective other) return false; + return this == other; + } + + public bool Equals(G2Projective other) + { + return this == other; + } + + public override int GetHashCode() + { + return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); + } + + public static G2Projective operator -(in G2Projective a) + { + return new(in a.X, -a.Y, in a.Z); + } + + public static G2Projective operator +(in G2Projective a, in G2Projective b) + { + // Algorithm 7, https://eprint.iacr.org/2015/1060.pdf + + var t0 = a.X * b.X; + var t1 = a.Y * b.Y; + var t2 = a.Z * b.Z; + var t3 = a.X + a.Y; + var t4 = b.X + b.Y; + t3 *= t4; + t4 = t0 + t1; + t3 -= t4; + t4 = a.Y + a.Z; + var x3 = b.Y + b.Z; + t4 *= x3; + x3 = t1 + t2; + t4 -= x3; + x3 = a.X + a.Z; + var y3 = b.X + b.Z; + x3 *= y3; + y3 = t0 + t2; + y3 = x3 - y3; + x3 = t0 + t0; + t0 = x3 + t0; + t2 = MulBy3B(t2); + var z3 = t1 + t2; + t1 -= t2; + y3 = MulBy3B(y3); + x3 = t4 * y3; + t2 = t3 * t1; + x3 = t2 - x3; + y3 *= t0; + t1 *= z3; + y3 = t1 + y3; + t0 *= t3; + z3 *= t4; + z3 += t0; + + return new G2Projective(in x3, in y3, in z3); + } + + public static G2Projective operator +(in G2Affine a, in G2Projective b) + { + return b + a; + } + + public static G2Projective operator +(in G2Projective a, in G2Affine b) + { + // Algorithm 8, https://eprint.iacr.org/2015/1060.pdf + + var t0 = a.X * b.X; + var t1 = a.Y * b.Y; + var t3 = b.X + b.Y; + var t4 = a.X + a.Y; + t3 *= t4; + t4 = t0 + t1; + t3 -= t4; + t4 = b.Y * a.Z; + t4 += a.Y; + var y3 = b.X * a.Z; + y3 += a.X; + var x3 = t0 + t0; + t0 = x3 + t0; + var t2 = MulBy3B(a.Z); + var z3 = t1 + t2; + t1 -= t2; + y3 = MulBy3B(y3); + x3 = t4 * y3; + t2 = t3 * t1; + x3 = t2 - x3; + y3 *= t0; + t1 *= z3; + y3 = t1 + y3; + t0 *= t3; + z3 *= t4; + z3 += t0; + + var tmp = new G2Projective(in x3, in y3, in z3); + + return ConditionalSelect(in tmp, in a, b.IsIdentity); + } + + public static G2Projective operator -(in G2Projective a, in G2Projective b) + { + return a + -b; + } + + public static G2Projective operator -(in G2Affine a, in G2Projective b) + { + return a + -b; + } + + public static G2Projective operator -(in G2Projective a, in G2Affine b) + { + return a + -b; + } + + public static G2Projective operator *(in G2Projective a, in Scalar b) + { + return a * b.ToArray(); + } + + public static G2Projective operator *(in G2Projective a, byte[] b) + { + var acc = Identity; + + // This is a simple double-and-add implementation of point + // multiplication, moving from most significant to least + // significant bit of the scalar. + // + // We skip the leading bit because it's always unset for Fq + // elements. + foreach (bool bit in b + .SelectMany(p => Enumerable.Range(0, 8).Select(q => ((p >> q) & 1) == 1)) + .Reverse() + .Skip(1)) + { + acc = acc.Double(); + acc = ConditionalSelect(in acc, acc + a, bit); + } + + return acc; + } + + private static Fp2 MulBy3B(Fp2 x) + { + return x * B3; + } + + public G2Projective Double() + { + // Algorithm 9, https://eprint.iacr.org/2015/1060.pdf + + var t0 = Y.Square(); + var z3 = t0 + t0; + z3 += z3; + z3 += z3; + var t1 = Y * Z; + var t2 = Z.Square(); + t2 = MulBy3B(t2); + var x3 = t2 * z3; + var y3 = t0 + t2; + z3 = t1 * z3; + t1 = t2 + t2; + t2 = t1 + t2; + t0 -= t2; + y3 = t0 * y3; + y3 = x3 + y3; + t1 = X * Y; + x3 = t0 * t1; + x3 += x3; + + var tmp = new G2Projective(in x3, in y3, in z3); + + return ConditionalSelect(in tmp, in Identity, IsIdentity); + } + + internal G2Projective Psi() + { + return new G2Projective( + // x = frobenius(x)/((u+1)^((p-1)/3)) + X.FrobeniusMap() * PsiCoeffX, + // y = frobenius(y)/(u+1)^((p-1)/2) + Y.FrobeniusMap() * PsiCoeffY, + // z = frobenius(z) + Z.FrobeniusMap() + ); + } + + internal G2Projective Psi2() + { + return new G2Projective( + // x = frobenius^2(x)/2^((p-1)/3); note that q^2 is the order of the field. + X * Psi2CoeffX, + // y = -frobenius^2(y); note that q^2 is the order of the field. + -Y, + // z = z + in Z + ); + } + + internal G2Projective MulByX() + { + var xself = Identity; + // NOTE: in BLS12-381 we can just skip the first bit. + var x = BLS_X >> 1; + var acc = this; + while (x != 0) + { + acc = acc.Double(); + if (x % 2 == 1) + { + xself += acc; + } + x >>= 1; + } + // finally, flip the sign + if (BLS_X_IS_NEGATIVE) + { + xself = -xself; + } + return xself; + } + + public G2Projective ClearCofactor() + { + var t1 = MulByX(); // [x] P + var t2 = Psi(); // psi(P) + + return Double().Psi2() // psi^2(2P) + + (t1 + t2).MulByX() // psi^2(2P) + [x^2] P + [x] psi(P) + - t1 // psi^2(2P) + [x^2 - x] P + [x] psi(P) + - t2 // psi^2(2P) + [x^2 - x] P + [x - 1] psi(P) + - this; // psi^2(2P) + [x^2 - x - 1] P + [x - 1] psi(P) + } + + public static void BatchNormalize(ReadOnlySpan p, Span q) + { + int length = p.Length; + if (length != q.Length) + throw new ArgumentException($"{nameof(p)} and {nameof(q)} must have the same length."); + + Span x = stackalloc Fp2[length]; + Fp2 acc = Fp2.One; + for (int i = 0; i < length; i++) + { + // We use the `x` field of `G2Affine` to store the product + // of previous z-coordinates seen. + x[i] = acc; + + // We will end up skipping all identities in p + acc = ConditionalSelect(acc * p[i].Z, in acc, p[i].IsIdentity); + } + + // This is the inverse, as all z-coordinates are nonzero and the ones + // that are not are skipped. + acc = acc.Invert(); + + for (int i = length - 1; i >= 0; i--) + { + bool skip = p[i].IsIdentity; + + // Compute tmp = 1/z + var tmp = x[i] * acc; + + // Cancel out z-coordinate in denominator of `acc` + acc = ConditionalSelect(acc * p[i].Z, in acc, skip); + + // Set the coordinates to the correct value + G2Affine qi = new(p[i].X * tmp, p[i].Y * tmp); + + q[i] = ConditionalSelect(in qi, in G2Affine.Identity, skip); + } + } + + public static G2Projective Random(RandomNumberGenerator rng) + { + Span buffer = stackalloc byte[sizeof(uint)]; + while (true) + { + var x = Fp2.Random(rng); + rng.GetBytes(buffer); + var flip_sign = BinaryPrimitives.ReadUInt32LittleEndian(buffer) % 2 != 0; + + // Obtain the corresponding y-coordinate given x as y = sqrt(x^3 + 4) + var y = ((x.Square() * x) + B).Sqrt(); + + G2Affine p; + try + { + p = new G2Affine(in x, flip_sign ? -y : y); + } + catch + { + continue; + } + + var result = p.ToCurve().ClearCofactor(); + if (!result.IsIdentity) return result; + } + } +} diff --git a/src/Neo.Cryptography.BLS12_381/GlobalSuppressions.cs b/src/Neo.Cryptography.BLS12_381/GlobalSuppressions.cs new file mode 100644 index 0000000..86f6ba7 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Usage", "CA2219")] diff --git a/src/Neo.Cryptography.BLS12_381/Gt.cs b/src/Neo.Cryptography.BLS12_381/Gt.cs new file mode 100644 index 0000000..38dd813 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Gt.cs @@ -0,0 +1,109 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.GtConstants; + +namespace Neo.Cryptography.BLS12_381; + +public readonly struct Gt : IEquatable +{ + public readonly Fp12 Value; + + public static readonly Gt Identity = new(in Fp12.One); + public static readonly Gt Generator = new(in GeneratorValue); + + public bool IsIdentity => this == Identity; + + public Gt(in Fp12 f) + { + Value = f; + } + + public static bool operator ==(in Gt a, in Gt b) + { + return a.Value == b.Value; + } + + public static bool operator !=(in Gt a, in Gt b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not Gt other) return false; + return this == other; + } + + public bool Equals(Gt other) + { + return this == other; + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } + + public static Gt Random(RandomNumberGenerator rng) + { + while (true) + { + var inner = Fp12.Random(rng); + + // Not all elements of Fp12 are elements of the prime-order multiplicative + // subgroup. We run the random element through final_exponentiation to obtain + // a valid element, which requires that it is non-zero. + if (!inner.IsZero) + { + ref MillerLoopResult result = ref Unsafe.As(ref inner); + return result.FinalExponentiation(); + } + } + } + + public Gt Double() + { + return new(Value.Square()); + } + + public static Gt operator -(in Gt a) + { + // The element is unitary, so we just conjugate. + return new(a.Value.Conjugate()); + } + + public static Gt operator +(in Gt a, in Gt b) + { + return new(a.Value * b.Value); + } + + public static Gt operator -(in Gt a, in Gt b) + { + return a + -b; + } + + public static Gt operator *(in Gt a, in Scalar b) + { + var acc = Identity; + + // This is a simple double-and-add implementation of group element + // multiplication, moving from most significant to least + // significant bit of the scalar. + // + // We skip the leading bit because it's always unset for Fq + // elements. + foreach (bool bit in b + .ToArray() + .SelectMany(p => Enumerable.Range(0, 8).Select(q => ((p >> q) & 1) == 1)) + .Reverse() + .Skip(1)) + { + acc = acc.Double(); + acc = ConditionalSelect(in acc, acc + a, bit); + } + + return acc; + } +} diff --git a/src/Neo.Cryptography.BLS12_381/GtConstants.cs b/src/Neo.Cryptography.BLS12_381/GtConstants.cs new file mode 100644 index 0000000..4585823 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/GtConstants.cs @@ -0,0 +1,104 @@ +namespace Neo.Cryptography.BLS12_381; + +static class GtConstants +{ + // pairing(&G1Affine.generator(), &G2Affine.generator()) + public static readonly Fp12 GeneratorValue = new(new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x1972_e433_a01f_85c5, + 0x97d3_2b76_fd77_2538, + 0xc8ce_546f_c96b_cdf9, + 0xcef6_3e73_66d4_0614, + 0xa611_3427_8184_3780, + 0x13f3_448a_3fc6_d825 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xd263_31b0_2e9d_6995, + 0x9d68_a482_f779_7e7d, + 0x9c9b_2924_8d39_ea92, + 0xf480_1ca2_e131_07aa, + 0xa16c_0732_bdbc_b066, + 0x083c_a4af_ba36_0478 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x59e2_61db_0916_b641, + 0x2716_b6f4_b23e_960d, + 0xc8e5_5b10_a0bd_9c45, + 0x0bdb_0bd9_9c4d_eda8, + 0x8cf8_9ebf_57fd_aac5, + 0x12d6_b792_9e77_7a5e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x5fc8_5188_b0e1_5f35, + 0x34a0_6e3a_8f09_6365, + 0xdb31_26a6_e02a_d62c, + 0xfc6f_5aa9_7d9a_990b, + 0xa12f_55f5_eb89_c210, + 0x1723_703a_926f_8889 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x9358_8f29_7182_8778, + 0x43f6_5b86_11ab_7585, + 0x3183_aaf5_ec27_9fdf, + 0xfa73_d7e1_8ac9_9df6, + 0x64e1_76a6_a64c_99b0, + 0x179f_a78c_5838_8f1f + }), Fp.FromRawUnchecked(new ulong[] + { + 0x672a_0a11_ca2a_ef12, + 0x0d11_b9b5_2aa3_f16b, + 0xa444_12d0_699d_056e, + 0xc01d_0177_221a_5ba5, + 0x66e0_cede_6c73_5529, + 0x05f5_a71e_9fdd_c339 + }))), new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xd30a_88a1_b062_c679, + 0x5ac5_6a5d_35fc_8304, + 0xd0c8_34a6_a81f_290d, + 0xcd54_30c2_da37_07c7, + 0xf0c2_7ff7_8050_0af0, + 0x0924_5da6_e2d7_2eae + }), + Fp.FromRawUnchecked(new ulong[] + { + 0x9f2e_0676_791b_5156, + 0xe2d1_c823_4918_fe13, + 0x4c9e_459f_3c56_1bf4, + 0xa3e8_5e53_b9d3_e3c1, + 0x820a_121e_21a7_0020, + 0x15af_6183_41c5_9acc + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x7c95_658c_2499_3ab1, + 0x73eb_3872_1ca8_86b9, + 0x5256_d749_4774_34bc, + 0x8ba4_1902_ea50_4a8b, + 0x04a3_d3f8_0c86_ce6d, + 0x18a6_4a87_fb68_6eaa + }), Fp.FromRawUnchecked(new ulong[] + { + 0xbb83_e71b_b920_cf26, + 0x2a52_77ac_92a7_3945, + 0xfc0e_e59f_94f0_46a0, + 0x7158_cdf3_7860_58f7, + 0x7cc1_061b_82f9_45f6, + 0x03f8_47aa_9fdb_e567 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x8078_dba5_6134_e657, + 0x1cd7_ec9a_4399_8a6e, + 0xb1aa_599a_1a99_3766, + 0xc9a0_f62f_0842_ee44, + 0x8e15_9be3_b605_dffa, + 0x0c86_ba0d_4af1_3fc2 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xe80f_f2a0_6a52_ffb1, + 0x7694_ca48_721a_906c, + 0x7583_183e_03b0_8514, + 0xf567_afdd_40ce_e4e2, + 0x9a6d_96d2_e526_a5fc, + 0x197e_9f49_861f_2242 + })))); +} diff --git a/src/Neo.Cryptography.BLS12_381/IMillerLoopDriver.cs b/src/Neo.Cryptography.BLS12_381/IMillerLoopDriver.cs new file mode 100644 index 0000000..33dbaca --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/IMillerLoopDriver.cs @@ -0,0 +1,10 @@ +namespace Neo.Cryptography.BLS12_381; + +interface IMillerLoopDriver +{ + public T DoublingStep(in T f); + public T AdditionStep(in T f); + public static abstract T SquareOutput(in T f); + public static abstract T Conjugate(in T f); + public static abstract T One { get; } +} diff --git a/src/Neo.Cryptography.BLS12_381/INumber.cs b/src/Neo.Cryptography.BLS12_381/INumber.cs new file mode 100644 index 0000000..ba3266e --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/INumber.cs @@ -0,0 +1,37 @@ +namespace Neo.Cryptography.BLS12_381; + +interface INumber where T : unmanaged, INumber +{ + static abstract int Size { get; } + static abstract ref readonly T Zero { get; } + static abstract ref readonly T One { get; } + + static abstract T operator -(in T x); + static abstract T operator +(in T x, in T y); + static abstract T operator -(in T x, in T y); + static abstract T operator *(in T x, in T y); + + abstract T Square(); +} + +static class NumberExtensions +{ + public static T PowVartime(this T self, ulong[] by) where T : unmanaged, INumber + { + // Although this is labeled "vartime", it is only + // variable time with respect to the exponent. + var res = T.One; + for (int j = by.Length - 1; j >= 0; j--) + { + for (int i = 63; i >= 0; i--) + { + res = res.Square(); + if (((by[j] >> i) & 1) == 1) + { + res *= self; + } + } + } + return res; + } +} diff --git a/src/Neo.Cryptography.BLS12_381/MathUtility.cs b/src/Neo.Cryptography.BLS12_381/MathUtility.cs new file mode 100644 index 0000000..835b42f --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/MathUtility.cs @@ -0,0 +1,28 @@ +namespace Neo.Cryptography.BLS12_381; + +static class MathUtility +{ + public static (ulong result, ulong carry) Adc(ulong a, ulong b, ulong carry) + { + ulong result = unchecked(a + b + carry); + carry = ((a & b) | ((a | b) & (~result))) >> 63; + return (result, carry); + } + + public static (ulong result, ulong borrow) Sbb(ulong a, ulong b, ulong borrow) + { + ulong result = unchecked(a - b - borrow); + borrow = (((~a) & b) | (~(a ^ b)) & result) >> 63; + return (result, borrow); + } + + public static (ulong low, ulong high) Mac(ulong z, ulong x, ulong y, ulong carry) + { + ulong high = Math.BigMul(x, y, out ulong low); + (low, carry) = Adc(low, carry, 0); + (high, _) = Adc(high, 0, carry); + (low, carry) = Adc(low, z, 0); + (high, _) = Adc(high, 0, carry); + return (low, high); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/MillerLoopResult.cs b/src/Neo.Cryptography.BLS12_381/MillerLoopResult.cs new file mode 100644 index 0000000..bab6e99 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/MillerLoopResult.cs @@ -0,0 +1,132 @@ +using System.Runtime.InteropServices; +using static Neo.Cryptography.BLS12_381.Constants; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Fp12.Size)] +readonly struct MillerLoopResult +{ + [FieldOffset(0)] + private readonly Fp12 v; + + public MillerLoopResult(in Fp12 v) + { + this.v = v; + } + + public Gt FinalExponentiation() + { + static (Fp2, Fp2) Fp4Square(in Fp2 a, in Fp2 b) + { + var t0 = a.Square(); + var t1 = b.Square(); + var t2 = t1.MulByNonresidue(); + var c0 = t2 + t0; + t2 = a + b; + t2 = t2.Square(); + t2 -= t0; + var c1 = t2 - t1; + + return (c0, c1); + } + + static Fp12 CyclotomicSquare(in Fp12 f) + { + var z0 = f.C0.C0; + var z4 = f.C0.C1; + var z3 = f.C0.C2; + var z2 = f.C1.C0; + var z1 = f.C1.C1; + var z5 = f.C1.C2; + + var (t0, t1) = Fp4Square(in z0, in z1); + + // For A + z0 = t0 - z0; + z0 = z0 + z0 + t0; + + z1 = t1 + z1; + z1 = z1 + z1 + t1; + + (t0, t1) = Fp4Square(in z2, in z3); + var (t2, t3) = Fp4Square(in z4, in z5); + + // For C + z4 = t0 - z4; + z4 = z4 + z4 + t0; + + z5 = t1 + z5; + z5 = z5 + z5 + t1; + + // For B + t0 = t3.MulByNonresidue(); + z2 = t0 + z2; + z2 = z2 + z2 + t0; + + z3 = t2 - z3; + z3 = z3 + z3 + t2; + + return new Fp12(new Fp6(in z0, in z4, in z3), new Fp6(in z2, in z1, in z5)); + } + + static Fp12 CycolotomicExp(in Fp12 f) + { + var x = BLS_X; + var tmp = Fp12.One; + var found_one = false; + foreach (bool i in Enumerable.Range(0, 64).Select(b => ((x >> b) & 1) == 1).Reverse()) + { + if (found_one) + tmp = CyclotomicSquare(tmp); + else + found_one = i; + + if (i) + tmp *= f; + } + + return tmp.Conjugate(); + } + + var f = v; + var t0 = f + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap(); + var t1 = f.Invert(); + var t2 = t0 * t1; + t1 = t2; + t2 = t2.FrobeniusMap().FrobeniusMap(); + t2 *= t1; + t1 = CyclotomicSquare(t2).Conjugate(); + var t3 = CycolotomicExp(t2); + var t4 = CyclotomicSquare(t3); + var t5 = t1 * t3; + t1 = CycolotomicExp(t5); + t0 = CycolotomicExp(t1); + var t6 = CycolotomicExp(t0); + t6 *= t4; + t4 = CycolotomicExp(t6); + t5 = t5.Conjugate(); + t4 *= t5 * t2; + t5 = t2.Conjugate(); + t1 *= t2; + t1 = t1.FrobeniusMap().FrobeniusMap().FrobeniusMap(); + t6 *= t5; + t6 = t6.FrobeniusMap(); + t3 *= t0; + t3 = t3.FrobeniusMap().FrobeniusMap(); + t3 *= t1; + t3 *= t6; + f = t3 * t4; + return new Gt(f); + } + + public static MillerLoopResult operator +(in MillerLoopResult a, in MillerLoopResult b) + { + return new(a.v * b.v); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/MillerLoopUtility.cs b/src/Neo.Cryptography.BLS12_381/MillerLoopUtility.cs new file mode 100644 index 0000000..41eaf93 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/MillerLoopUtility.cs @@ -0,0 +1,109 @@ +using static Neo.Cryptography.BLS12_381.Constants; + +namespace Neo.Cryptography.BLS12_381; + +static class MillerLoopUtility +{ + public static T MillerLoop(D driver) where D : IMillerLoopDriver + { + var f = D.One; + + var found_one = false; + foreach (var i in Enumerable.Range(0, 64).Reverse().Select(b => ((BLS_X >> 1 >> b) & 1) == 1)) + { + if (!found_one) + { + found_one = i; + continue; + } + + f = driver.DoublingStep(f); + + if (i) + f = driver.AdditionStep(f); + + f = D.SquareOutput(f); + } + + f = driver.DoublingStep(f); + + if (BLS_X_IS_NEGATIVE) + f = D.Conjugate(f); + + return f; + } + + public static Fp12 Ell(in Fp12 f, in (Fp2 X, Fp2 Y, Fp2 Z) coeffs, in G1Affine p) + { + var c0 = new Fp2(coeffs.X.C0 * p.Y, coeffs.X.C1 * p.Y); + var c1 = new Fp2(coeffs.Y.C0 * p.X, coeffs.Y.C1 * p.X); + return f.MulBy_014(in coeffs.Z, in c1, in c0); + } + + public static (Fp2, Fp2, Fp2) DoublingStep(ref G2Projective r) + { + // Adaptation of Algorithm 26, https://eprint.iacr.org/2010/354.pdf + var tmp0 = r.X.Square(); + var tmp1 = r.Y.Square(); + var tmp2 = tmp1.Square(); + var tmp3 = (tmp1 + r.X).Square() - tmp0 - tmp2; + tmp3 += tmp3; + var tmp4 = tmp0 + tmp0 + tmp0; + var tmp6 = r.X + tmp4; + var tmp5 = tmp4.Square(); + var zsquared = r.Z.Square(); + var x = tmp5 - tmp3 - tmp3; + var z = (r.Z + r.Y).Square() - tmp1 - zsquared; + var y = (tmp3 - x) * tmp4; + tmp2 += tmp2; + tmp2 += tmp2; + tmp2 += tmp2; + y -= tmp2; + r = new(in x, in y, in z); + tmp3 = tmp4 * zsquared; + tmp3 += tmp3; + tmp3 = -tmp3; + tmp6 = tmp6.Square() - tmp0 - tmp5; + tmp1 += tmp1; + tmp1 += tmp1; + tmp6 -= tmp1; + tmp0 = r.Z * zsquared; + tmp0 += tmp0; + + return (tmp0, tmp3, tmp6); + } + + public static (Fp2, Fp2, Fp2) AdditionStep(ref G2Projective r, in G2Affine q) + { + // Adaptation of Algorithm 27, https://eprint.iacr.org/2010/354.pdf + var zsquared = r.Z.Square(); + var ysquared = q.Y.Square(); + var t0 = zsquared * q.X; + var t1 = ((q.Y + r.Z).Square() - ysquared - zsquared) * zsquared; + var t2 = t0 - r.X; + var t3 = t2.Square(); + var t4 = t3 + t3; + t4 += t4; + var t5 = t4 * t2; + var t6 = t1 - r.Y - r.Y; + var t9 = t6 * q.X; + var t7 = t4 * r.X; + var x = t6.Square() - t5 - t7 - t7; + var z = (r.Z + t2).Square() - zsquared - t3; + var t10 = q.Y + z; + var t8 = (t7 - x) * t6; + t0 = r.Y * t5; + t0 += t0; + var y = t8 - t0; + r = new(in x, in y, in z); + t10 = t10.Square() - ysquared; + var ztsquared = r.Z.Square(); + t10 -= ztsquared; + t9 = t9 + t9 - t10; + t10 = r.Z + r.Z; + t6 = -t6; + t1 = t6 + t6; + + return (t10, t1, t9); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/Neo.Cryptography.BLS12_381.csproj b/src/Neo.Cryptography.BLS12_381/Neo.Cryptography.BLS12_381.csproj new file mode 100644 index 0000000..d2c5d55 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Neo.Cryptography.BLS12_381.csproj @@ -0,0 +1,12 @@ + + + + 0.7.0 + net6.0 + enable + enable + preview + true + + + diff --git a/src/Neo.Cryptography.BLS12_381/Properties/AssemblyInfo.cs b/src/Neo.Cryptography.BLS12_381/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4d6a753 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Neo.Cryptography.BLS12_381.Tests")] diff --git a/src/Neo.Cryptography.BLS12_381/Scalar.cs b/src/Neo.Cryptography.BLS12_381/Scalar.cs new file mode 100644 index 0000000..5a3ea10 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Scalar.cs @@ -0,0 +1,488 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.MathUtility; +using static Neo.Cryptography.BLS12_381.ScalarConstants; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Size)] +public readonly struct Scalar : IEquatable, INumber +{ + public const int Size = 32; + public const int SizeL = Size / sizeof(ulong); + public static readonly Scalar Default = new(); + + static int INumber.Size => Size; + public static ref readonly Scalar Zero => ref Default; + public static ref readonly Scalar One => ref R; + + public bool IsZero => this == Zero; + + internal Scalar(ulong[] values) + { + // This internal method is only used by the constants classes. + // The data must be in the correct format. + // So, there is no need to do any checks. + this = MemoryMarshal.AsRef(MemoryMarshal.Cast(values)); + } + + public Scalar(ulong value) + { + Span data = stackalloc ulong[SizeL]; + data[0] = value; + this = FromRaw(data); + } + + public Scalar(RandomNumberGenerator rng) + { + Span buffer = stackalloc byte[Size * 2]; + rng.GetBytes(buffer); + this = FromBytesWide(buffer); + } + + public static Scalar FromBytes(ReadOnlySpan data) + { + if (data.Length != Size) + throw new FormatException($"The argument `{nameof(data)}` should contain {Size} bytes."); + + ref readonly Scalar ref_ = ref MemoryMarshal.AsRef(data); + + try + { + return ref_ * R2; + } + finally + { + ReadOnlySpan u64 = MemoryMarshal.Cast(data); + ulong borrow = 0; + (_, borrow) = Sbb(u64[0], MODULUS_LIMBS_64[0], borrow); + (_, borrow) = Sbb(u64[1], MODULUS_LIMBS_64[1], borrow); + (_, borrow) = Sbb(u64[2], MODULUS_LIMBS_64[2], borrow); + (_, borrow) = Sbb(u64[3], MODULUS_LIMBS_64[3], borrow); + if (borrow == 0) + { + // If the element is smaller than MODULUS then the subtraction will underflow. + // Otherwise, throws. + // Why not throw before return? + // Because we want to run the method in a constant time. + throw new FormatException(); + } + } + } + + public static Scalar FromBytesWide(ReadOnlySpan data) + { + if (data.Length != Size * 2) + throw new FormatException($"The argument `{nameof(data)}` should contain {Size * 2} bytes."); + + ReadOnlySpan d = MemoryMarshal.Cast(data); + return d[0] * R2 + d[1] * R3; + } + + public static Scalar FromRaw(ReadOnlySpan data) + { + ReadOnlySpan span = MemoryMarshal.Cast(data); + return span[0] * R2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ReadOnlySpan GetSpan() + { + return MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in this), 1)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Span GetSpanU64() + { + return MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in this), 1)); + } + + public override string ToString() + { + byte[] bytes = ToArray(); + StringBuilder sb = new(); + sb.Append("0x"); + for (int i = bytes.Length - 1; i >= 0; i--) + sb.AppendFormat("{0:x2}", bytes[i]); + return sb.ToString(); + } + + public static bool operator ==(in Scalar a, in Scalar b) + { + return ConstantTimeEq(in a, in b); + } + + public static bool operator !=(in Scalar a, in Scalar b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not Scalar other) return false; + return this == other; + } + + public bool Equals(Scalar other) + { + return this == other; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public Scalar Double() + { + return this + this; + } + + public byte[] ToArray() + { + ReadOnlySpan self = GetSpanU64(); + + // Turn into canonical form by computing + // (a.R) / R = a + Scalar result = MontgomeryReduce(self[0], self[1], self[2], self[3], 0, 0, 0, 0); + return result.GetSpan().ToArray(); + } + + public Scalar Square() + { + ReadOnlySpan self = GetSpanU64(); + ulong r0, r1, r2, r3, r4, r5, r6, r7; + ulong carry; + + (r1, carry) = Mac(0, self[0], self[1], 0); + (r2, carry) = Mac(0, self[0], self[2], carry); + (r3, r4) = Mac(0, self[0], self[3], carry); + + (r3, carry) = Mac(r3, self[1], self[2], 0); + (r4, r5) = Mac(r4, self[1], self[3], carry); + + (r5, r6) = Mac(r5, self[2], self[3], 0); + + r7 = r6 >> 63; + r6 = (r6 << 1) | (r5 >> 63); + r5 = (r5 << 1) | (r4 >> 63); + r4 = (r4 << 1) | (r3 >> 63); + r3 = (r3 << 1) | (r2 >> 63); + r2 = (r2 << 1) | (r1 >> 63); + r1 <<= 1; + + (r0, carry) = Mac(0, self[0], self[0], 0); + (r1, carry) = Adc(r1, carry, 0); + (r2, carry) = Mac(r2, self[1], self[1], carry); + (r3, carry) = Adc(r3, carry, 0); + (r4, carry) = Mac(r4, self[2], self[2], carry); + (r5, carry) = Adc(r5, carry, 0); + (r6, carry) = Mac(r6, self[3], self[3], carry); + (r7, _) = Adc(r7, carry, 0); + + return MontgomeryReduce(r0, r1, r2, r3, r4, r5, r6, r7); + } + + public Scalar Sqrt() + { + // Tonelli-Shank's algorithm for q mod 16 = 1 + // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) + + // w = self^((t - 1) // 2) + // = self^6104339283789297388802252303364915521546564123189034618274734669823 + var w = this.PowVartime(new ulong[] + { + 0x7fff_2dff_7fff_ffff, + 0x04d0_ec02_a9de_d201, + 0x94ce_bea4_199c_ec04, + 0x0000_0000_39f6_d3a9 + }); + + var v = S; + var x = this * w; + var b = x * w; + + // Initialize z as the 2^S root of unity. + var z = ROOT_OF_UNITY; + + for (uint max_v = S; max_v >= 1; max_v--) + { + uint k = 1; + var tmp = b.Square(); + var j_less_than_v = true; + + for (uint j = 2; j < max_v; j++) + { + var tmp_is_one = tmp == One; + var squared = ConditionalSelect(in tmp, in z, tmp_is_one).Square(); + tmp = ConditionalSelect(in squared, in tmp, tmp_is_one); + var new_z = ConditionalSelect(in z, in squared, tmp_is_one); + j_less_than_v &= j != v; + k = ConditionalSelect(j, k, tmp_is_one); + z = ConditionalSelect(in z, in new_z, j_less_than_v); + } + + var result = x * z; + x = ConditionalSelect(in result, in x, b == One); + z = z.Square(); + b *= z; + v = k; + } + + if (x * x != this) throw new ArithmeticException(); + return x; + } + + public Scalar Pow(ulong[] by) + { + if (by.Length != SizeL) + throw new ArgumentException($"The length of the parameter `{nameof(by)}` must be {SizeL}."); + + var res = One; + for (int j = by.Length - 1; j >= 0; j--) + { + for (int i = 63; i >= 0; i--) + { + res = res.Square(); + var tmp = res; + tmp *= this; + res.ConditionalAssign(in tmp, ((by[j] >> i) & 1) == 1); + } + } + return res; + } + + public Scalar Invert() + { + static void SquareAssignMulti(ref Scalar n, int num_times) + { + for (int i = 0; i < num_times; i++) + { + n = n.Square(); + } + } + + var t0 = Square(); + var t1 = t0 * this; + var t16 = t0.Square(); + var t6 = t16.Square(); + var t5 = t6 * t0; + t0 = t6 * t16; + var t12 = t5 * t16; + var t2 = t6.Square(); + var t7 = t5 * t6; + var t15 = t0 * t5; + var t17 = t12.Square(); + t1 *= t17; + var t3 = t7 * t2; + var t8 = t1 * t17; + var t4 = t8 * t2; + var t9 = t8 * t7; + t7 = t4 * t5; + var t11 = t4 * t17; + t5 = t9 * t17; + var t14 = t7 * t15; + var t13 = t11 * t12; + t12 = t11 * t17; + t15 *= t12; + t16 *= t15; + t3 *= t16; + t17 *= t3; + t0 *= t17; + t6 *= t0; + t2 *= t6; + SquareAssignMulti(ref t0, 8); + t0 *= t17; + SquareAssignMulti(ref t0, 9); + t0 *= t16; + SquareAssignMulti(ref t0, 9); + t0 *= t15; + SquareAssignMulti(ref t0, 9); + t0 *= t15; + SquareAssignMulti(ref t0, 7); + t0 *= t14; + SquareAssignMulti(ref t0, 7); + t0 *= t13; + SquareAssignMulti(ref t0, 10); + t0 *= t12; + SquareAssignMulti(ref t0, 9); + t0 *= t11; + SquareAssignMulti(ref t0, 8); + t0 *= t8; + SquareAssignMulti(ref t0, 8); + t0 *= this; + SquareAssignMulti(ref t0, 14); + t0 *= t9; + SquareAssignMulti(ref t0, 10); + t0 *= t8; + SquareAssignMulti(ref t0, 15); + t0 *= t7; + SquareAssignMulti(ref t0, 10); + t0 *= t6; + SquareAssignMulti(ref t0, 8); + t0 *= t5; + SquareAssignMulti(ref t0, 16); + t0 *= t3; + SquareAssignMulti(ref t0, 8); + t0 *= t2; + SquareAssignMulti(ref t0, 7); + t0 *= t4; + SquareAssignMulti(ref t0, 9); + t0 *= t2; + SquareAssignMulti(ref t0, 8); + t0 *= t3; + SquareAssignMulti(ref t0, 8); + t0 *= t2; + SquareAssignMulti(ref t0, 8); + t0 *= t2; + SquareAssignMulti(ref t0, 8); + t0 *= t2; + SquareAssignMulti(ref t0, 8); + t0 *= t3; + SquareAssignMulti(ref t0, 8); + t0 *= t2; + SquareAssignMulti(ref t0, 8); + t0 *= t2; + SquareAssignMulti(ref t0, 5); + t0 *= t1; + SquareAssignMulti(ref t0, 5); + t0 *= t1; + + if (this == Zero) throw new DivideByZeroException(); + return t0; + } + + private static Scalar MontgomeryReduce(ulong r0, ulong r1, ulong r2, ulong r3, ulong r4, ulong r5, ulong r6, ulong r7) + { + // The Montgomery reduction here is based on Algorithm 14.32 in + // Handbook of Applied Cryptography + // . + + ulong carry, carry2; + + var k = unchecked(r0 * INV); + (_, carry) = Mac(r0, k, MODULUS_LIMBS_64[0], 0); + (r1, carry) = Mac(r1, k, MODULUS_LIMBS_64[1], carry); + (r2, carry) = Mac(r2, k, MODULUS_LIMBS_64[2], carry); + (r3, carry) = Mac(r3, k, MODULUS_LIMBS_64[3], carry); + (r4, carry2) = Adc(r4, 0, carry); + + k = unchecked(r1 * INV); + (_, carry) = Mac(r1, k, MODULUS_LIMBS_64[0], 0); + (r2, carry) = Mac(r2, k, MODULUS_LIMBS_64[1], carry); + (r3, carry) = Mac(r3, k, MODULUS_LIMBS_64[2], carry); + (r4, carry) = Mac(r4, k, MODULUS_LIMBS_64[3], carry); + (r5, carry2) = Adc(r5, carry2, carry); + + k = unchecked(r2 * INV); + (_, carry) = Mac(r2, k, MODULUS_LIMBS_64[0], 0); + (r3, carry) = Mac(r3, k, MODULUS_LIMBS_64[1], carry); + (r4, carry) = Mac(r4, k, MODULUS_LIMBS_64[2], carry); + (r5, carry) = Mac(r5, k, MODULUS_LIMBS_64[3], carry); + (r6, carry2) = Adc(r6, carry2, carry); + + k = unchecked(r3 * INV); + (_, carry) = Mac(r3, k, MODULUS_LIMBS_64[0], 0); + (r4, carry) = Mac(r4, k, MODULUS_LIMBS_64[1], carry); + (r5, carry) = Mac(r5, k, MODULUS_LIMBS_64[2], carry); + (r6, carry) = Mac(r6, k, MODULUS_LIMBS_64[3], carry); + (r7, _) = Adc(r7, carry2, carry); + + // Result may be within MODULUS of the correct value + ReadOnlySpan tmp = stackalloc[] { r4, r5, r6, r7 }; + return MemoryMarshal.Cast(tmp)[0] - MODULUS; + } + + public static Scalar operator *(in Scalar a, in Scalar b) + { + ReadOnlySpan self = a.GetSpanU64(), rhs = b.GetSpanU64(); + ulong r0, r1, r2, r3, r4, r5, r6, r7; + ulong carry; + + (r0, carry) = Mac(0, self[0], rhs[0], 0); + (r1, carry) = Mac(0, self[0], rhs[1], carry); + (r2, carry) = Mac(0, self[0], rhs[2], carry); + (r3, r4) = Mac(0, self[0], rhs[3], carry); + + (r1, carry) = Mac(r1, self[1], rhs[0], 0); + (r2, carry) = Mac(r2, self[1], rhs[1], carry); + (r3, carry) = Mac(r3, self[1], rhs[2], carry); + (r4, r5) = Mac(r4, self[1], rhs[3], carry); + + (r2, carry) = Mac(r2, self[2], rhs[0], 0); + (r3, carry) = Mac(r3, self[2], rhs[1], carry); + (r4, carry) = Mac(r4, self[2], rhs[2], carry); + (r5, r6) = Mac(r5, self[2], rhs[3], carry); + + (r3, carry) = Mac(r3, self[3], rhs[0], 0); + (r4, carry) = Mac(r4, self[3], rhs[1], carry); + (r5, carry) = Mac(r5, self[3], rhs[2], carry); + (r6, r7) = Mac(r6, self[3], rhs[3], carry); + + return MontgomeryReduce(r0, r1, r2, r3, r4, r5, r6, r7); + } + + public static Scalar operator -(in Scalar a, in Scalar b) + { + ReadOnlySpan self = a.GetSpanU64(), rhs = b.GetSpanU64(); + ulong d0, d1, d2, d3; + ulong carry, borrow; + + (d0, borrow) = Sbb(self[0], rhs[0], 0); + (d1, borrow) = Sbb(self[1], rhs[1], borrow); + (d2, borrow) = Sbb(self[2], rhs[2], borrow); + (d3, borrow) = Sbb(self[3], rhs[3], borrow); + + borrow = borrow == 0 ? ulong.MinValue : ulong.MaxValue; + (d0, carry) = Adc(d0, MODULUS_LIMBS_64[0] & borrow, 0); + (d1, carry) = Adc(d1, MODULUS_LIMBS_64[1] & borrow, carry); + (d2, carry) = Adc(d2, MODULUS_LIMBS_64[2] & borrow, carry); + (d3, _) = Adc(d3, MODULUS_LIMBS_64[3] & borrow, carry); + + ReadOnlySpan tmp = stackalloc[] { d0, d1, d2, d3 }; + return MemoryMarshal.Cast(tmp)[0]; + } + + public static Scalar operator +(in Scalar a, in Scalar b) + { + ReadOnlySpan self = a.GetSpanU64(), rhs = b.GetSpanU64(); + ulong d0, d1, d2, d3; + ulong carry; + + (d0, carry) = Adc(self[0], rhs[0], 0); + (d1, carry) = Adc(self[1], rhs[1], carry); + (d2, carry) = Adc(self[2], rhs[2], carry); + (d3, _) = Adc(self[3], rhs[3], carry); + + // Attempt to subtract the modulus, to ensure the value + // is smaller than the modulus. + ReadOnlySpan tmp = stackalloc[] { d0, d1, d2, d3 }; + return MemoryMarshal.Cast(tmp)[0] - MODULUS; + } + + public static Scalar operator -(in Scalar a) + { + ReadOnlySpan self = a.GetSpanU64(); + ulong d0, d1, d2, d3; + ulong borrow; + + // Subtract `self` from `MODULUS` to negate. Ignore the final + // borrow because it cannot underflow; self is guaranteed to + // be in the field. + (d0, borrow) = Sbb(MODULUS_LIMBS_64[0], self[0], 0); + (d1, borrow) = Sbb(MODULUS_LIMBS_64[1], self[1], borrow); + (d2, borrow) = Sbb(MODULUS_LIMBS_64[2], self[2], borrow); + (d3, _) = Sbb(MODULUS_LIMBS_64[3], self[3], borrow); + + // `tmp` could be `MODULUS` if `self` was zero. Create a mask that is + // zero if `self` was zero, and `u64::max_value()` if self was nonzero. + ulong mask = a.IsZero ? ulong.MinValue : ulong.MaxValue; + + ReadOnlySpan tmp = stackalloc[] { d0 & mask, d1 & mask, d2 & mask, d3 & mask }; + return MemoryMarshal.Cast(tmp)[0]; + } +} diff --git a/src/Neo.Cryptography.BLS12_381/ScalarConstants.cs b/src/Neo.Cryptography.BLS12_381/ScalarConstants.cs new file mode 100644 index 0000000..3e4c0ef --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/ScalarConstants.cs @@ -0,0 +1,85 @@ +namespace Neo.Cryptography.BLS12_381; + +static class ScalarConstants +{ + // The modulus as u32 limbs. + public static readonly uint[] MODULUS_LIMBS_32 = + { + 0x0000_0001, + 0xffff_ffff, + 0xfffe_5bfe, + 0x53bd_a402, + 0x09a1_d805, + 0x3339_d808, + 0x299d_7d48, + 0x73ed_a753 + }; + + // The modulus as u64 limbs. + public static readonly ulong[] MODULUS_LIMBS_64 = + { + 0xffff_ffff_0000_0001, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48 + }; + + // q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 + public static readonly Scalar MODULUS = new(MODULUS_LIMBS_64); + + // The number of bits needed to represent the modulus. + public const uint MODULUS_BITS = 255; + + // GENERATOR = 7 (multiplicative generator of r-1 order, that is also quadratic nonresidue) + public static readonly Scalar GENERATOR = new(new ulong[] + { + 0x0000_000e_ffff_fff1, + 0x17e3_63d3_0018_9c0f, + 0xff9c_5787_6f84_57b0, + 0x3513_3220_8fc5_a8c4 + }); + + // INV = -(q^{-1} mod 2^64) mod 2^64 + public const ulong INV = 0xffff_fffe_ffff_ffff; + + // R = 2^256 mod q + public static readonly Scalar R = new(new ulong[] + { + 0x0000_0001_ffff_fffe, + 0x5884_b7fa_0003_4802, + 0x998c_4fef_ecbc_4ff5, + 0x1824_b159_acc5_056f + }); + + // R^2 = 2^512 mod q + public static readonly Scalar R2 = new(new ulong[] + { + 0xc999_e990_f3f2_9c6d, + 0x2b6c_edcb_8792_5c23, + 0x05d3_1496_7254_398f, + 0x0748_d9d9_9f59_ff11 + }); + + // R^3 = 2^768 mod q + public static readonly Scalar R3 = new(new ulong[] + { + 0xc62c_1807_439b_73af, + 0x1b3e_0d18_8cf0_6990, + 0x73d1_3c71_c7b5_f418, + 0x6e2a_5bb9_c8db_33e9 + }); + + // 2^S * t = MODULUS - 1 with t odd + public const uint S = 32; + + // GENERATOR^t where t * 2^s + 1 = q with t odd. + // In other words, this is a 2^s root of unity. + // `GENERATOR = 7 mod q` is a generator of the q - 1 order multiplicative subgroup. + public static readonly Scalar ROOT_OF_UNITY = new(new ulong[] + { + 0xb9b5_8d8c_5f0e_466a, + 0x5b1b_4c80_1819_d7ec, + 0x0af5_3ae3_52a3_1e64, + 0x5bf3_adda_19e9_b27b + }); +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/Neo.Cryptography.BLS12_381.Tests.csproj b/tests/Neo.Cryptography.BLS12_381.Tests/Neo.Cryptography.BLS12_381.Tests.csproj new file mode 100644 index 0000000..040059b --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/Neo.Cryptography.BLS12_381.Tests.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + enable + true + false + + + + + + + + + + + + + + diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp.cs new file mode 100644 index 0000000..a7cef1e --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp.cs @@ -0,0 +1,342 @@ +using System.Collections; +using System.Runtime.InteropServices; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; + +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_Fp +{ + [TestMethod] + public void TestSize() + { + Assert.AreEqual(Fp.Size, Marshal.SizeOf()); + } + + [TestMethod] + public void TestEquality() + { + static bool IsEqual(in Fp a, in Fp b) + { + bool eq = StructuralComparisons.StructuralEqualityComparer.Equals(a, b); + bool ct_eq = a == b; + Assert.AreEqual(eq, ct_eq); + return eq; + } + + Assert.IsTrue(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }))); + + Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 7, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }))); + Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 7, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }))); + Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 2, 7, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }))); + Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 7, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }))); + Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 7, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }))); + Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 7 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }))); + } + + [TestMethod] + public void TestConditionalSelection() + { + Fp a = Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }); + Fp b = Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 }); + + Assert.AreEqual(a, ConditionalSelect(in a, in b, false)); + Assert.AreEqual(b, ConditionalSelect(in a, in b, true)); + } + + [TestMethod] + public void TestSquaring() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0xd215_d276_8e83_191b, + 0x5085_d80f_8fb2_8261, + 0xce9a_032d_df39_3a56, + 0x3e9c_4fff_2ca0_c4bb, + 0x6436_b6f7_f4d9_5dfb, + 0x1060_6628_ad4a_4d90 + }); + Fp b = Fp.FromRawUnchecked(new ulong[] + { + 0x33d9_c42a_3cb3_e235, + 0xdad1_1a09_4c4c_d455, + 0xa2f1_44bd_729a_aeba, + 0xd415_0932_be9f_feac, + 0xe27b_c7c4_7d44_ee50, + 0x14b6_a78d_3ec7_a560 + }); + + Assert.AreEqual(b, a.Square()); + } + + [TestMethod] + public void TestMultiplication() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0x0397_a383_2017_0cd4, + 0x734c_1b2c_9e76_1d30, + 0x5ed2_55ad_9a48_beb5, + 0x095a_3c6b_22a7_fcfc, + 0x2294_ce75_d4e2_6a27, + 0x1333_8bd8_7001_1ebb + }); + Fp b = Fp.FromRawUnchecked(new ulong[] + { + 0xb9c3_c7c5_b119_6af7, + 0x2580_e208_6ce3_35c1, + 0xf49a_ed3d_8a57_ef42, + 0x41f2_81e4_9846_e878, + 0xe076_2346_c384_52ce, + 0x0652_e893_26e5_7dc0 + }); + Fp c = Fp.FromRawUnchecked(new ulong[] + { + 0xf96e_f3d7_11ab_5355, + 0xe8d4_59ea_00f1_48dd, + 0x53f7_354a_5f00_fa78, + 0x9e34_a4f3_125c_5f83, + 0x3fbe_0c47_ca74_c19e, + 0x01b0_6a8b_bd4a_dfe4 + }); + + Assert.AreEqual(c, a * b); + } + + [TestMethod] + public void TestAddition() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0x5360_bb59_7867_8032, + 0x7dd2_75ae_799e_128e, + 0x5c5b_5071_ce4f_4dcf, + 0xcdb2_1f93_078d_bb3e, + 0xc323_65c5_e73f_474a, + 0x115a_2a54_89ba_be5b + }); + Fp b = Fp.FromRawUnchecked(new ulong[] + { + 0x9fd2_8773_3d23_dda0, + 0xb16b_f2af_738b_3554, + 0x3e57_a75b_d3cc_6d1d, + 0x900b_c0bd_627f_d6d6, + 0xd319_a080_efb2_45fe, + 0x15fd_caa4_e4bb_2091 + }); + Fp c = Fp.FromRawUnchecked(new ulong[] + { + 0x3934_42cc_b58b_b327, + 0x1092_685f_3bd5_47e3, + 0x3382_252c_ab6a_c4c9, + 0xf946_94cb_7688_7f55, + 0x4b21_5e90_93a5_e071, + 0x0d56_e30f_34f5_f853 + }); + + Assert.AreEqual(c, a + b); + } + + [TestMethod] + public void TestSubtraction() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0x5360_bb59_7867_8032, + 0x7dd2_75ae_799e_128e, + 0x5c5b_5071_ce4f_4dcf, + 0xcdb2_1f93_078d_bb3e, + 0xc323_65c5_e73f_474a, + 0x115a_2a54_89ba_be5b + }); + Fp b = Fp.FromRawUnchecked(new ulong[] + { + 0x9fd2_8773_3d23_dda0, + 0xb16b_f2af_738b_3554, + 0x3e57_a75b_d3cc_6d1d, + 0x900b_c0bd_627f_d6d6, + 0xd319_a080_efb2_45fe, + 0x15fd_caa4_e4bb_2091 + }); + Fp c = Fp.FromRawUnchecked(new ulong[] + { + 0x6d8d_33e6_3b43_4d3d, + 0xeb12_82fd_b766_dd39, + 0x8534_7bb6_f133_d6d5, + 0xa21d_aa5a_9892_f727, + 0x3b25_6cfb_3ad8_ae23, + 0x155d_7199_de7f_8464 + }); + + Assert.AreEqual(c, a - b); + } + + [TestMethod] + public void TestNegation() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0x5360_bb59_7867_8032, + 0x7dd2_75ae_799e_128e, + 0x5c5b_5071_ce4f_4dcf, + 0xcdb2_1f93_078d_bb3e, + 0xc323_65c5_e73f_474a, + 0x115a_2a54_89ba_be5b + }); + Fp b = Fp.FromRawUnchecked(new ulong[] + { + 0x669e_44a6_8798_2a79, + 0xa0d9_8a50_37b5_ed71, + 0x0ad5_822f_2861_a854, + 0x96c5_2bf1_ebf7_5781, + 0x87f8_41f0_5c0c_658c, + 0x08a6_e795_afc5_283e + }); + + Assert.AreEqual(b, -a); + } + + [TestMethod] + public void TestToString() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0x5360_bb59_7867_8032, + 0x7dd2_75ae_799e_128e, + 0x5c5b_5071_ce4f_4dcf, + 0xcdb2_1f93_078d_bb3e, + 0xc323_65c5_e73f_474a, + 0x115a_2a54_89ba_be5b + }); + + Assert.AreEqual("0x104bf052ad3bc99bcb176c24a06a6c3aad4eaf2308fc4d282e106c84a757d061052630515305e59bdddf8111bfdeb704", a.ToString()); + } + + [TestMethod] + public void TestConstructor() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0xdc90_6d9b_e3f9_5dc8, + 0x8755_caf7_4596_91a1, + 0xcff1_a7f4_e958_3ab3, + 0x9b43_821f_849e_2284, + 0xf575_54f3_a297_4f3f, + 0x085d_bea8_4ed4_7f79 + }); + + for (int i = 0; i < 100; i++) + { + a = a.Square(); + byte[] tmp = a.ToArray(); + Fp b = Fp.FromBytes(tmp); + + Assert.AreEqual(b, a); + } + + Assert.AreEqual(-Fp.One, Fp.FromBytes(new byte[] + { + 26, 1, 17, 234, 57, 127, 230, 154, 75, 27, 167, 182, 67, 75, 172, 215, 100, 119, 75, + 132, 243, 133, 18, 191, 103, 48, 210, 160, 246, 176, 246, 36, 30, 171, 255, 254, 177, + 83, 255, 255, 185, 254, 255, 255, 255, 255, 170, 170 + })); + + Assert.ThrowsException(() => Fp.FromBytes(new byte[] + { + 27, 1, 17, 234, 57, 127, 230, 154, 75, 27, 167, 182, 67, 75, 172, 215, 100, 119, 75, + 132, 243, 133, 18, 191, 103, 48, 210, 160, 246, 176, 246, 36, 30, 171, 255, 254, 177, + 83, 255, 255, 185, 254, 255, 255, 255, 255, 170, 170 + })); + + Assert.ThrowsException(() => Fp.FromBytes(Enumerable.Repeat(0xff, 48).ToArray())); + } + + [TestMethod] + public void TestSqrt() + { + // a = 4 + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0xaa27_0000_000c_fff3, + 0x53cc_0032_fc34_000a, + 0x478f_e97a_6b0a_807f, + 0xb1d3_7ebe_e6ba_24d7, + 0x8ec9_733b_bf78_ab2f, + 0x09d6_4551_3d83_de7e + }); + + // b = 2 + Fp b = Fp.FromRawUnchecked(new ulong[] + { + 0x3213_0000_0006_554f, + 0xb93c_0018_d6c4_0005, + 0x5760_5e0d_b0dd_bb51, + 0x8b25_6521_ed1f_9bcb, + 0x6cf2_8d79_0162_2c03, + 0x11eb_ab9d_bb81_e28c + }); + + // sqrt(4) = -2 + Assert.AreEqual(b, -a.Sqrt()); + } + + [TestMethod] + public void TestInversion() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0x43b4_3a50_78ac_2076, + 0x1ce0_7630_46f8_962b, + 0x724a_5276_486d_735c, + 0x6f05_c2a6_282d_48fd, + 0x2095_bd5b_b4ca_9331, + 0x03b3_5b38_94b0_f7da + }); + Fp b = Fp.FromRawUnchecked(new ulong[] + { + 0x69ec_d704_0952_148f, + 0x985c_cc20_2219_0f55, + 0xe19b_ba36_a9ad_2f41, + 0x19bb_16c9_5219_dbd8, + 0x14dc_acfd_fb47_8693, + 0x115f_f58a_fff9_a8e1 + }); + + Assert.AreEqual(b, a.Invert()); + Assert.ThrowsException(() => Fp.Zero.Invert()); + } + + [TestMethod] + public void TestLexicographicLargest() + { + Assert.IsFalse(Fp.Zero.LexicographicallyLargest()); + Assert.IsFalse(Fp.One.LexicographicallyLargest()); + Assert.IsFalse(Fp.FromRawUnchecked(new ulong[] + { + 0xa1fa_ffff_fffe_5557, + 0x995b_fff9_76a3_fffe, + 0x03f4_1d24_d174_ceb4, + 0xf654_7998_c199_5dbd, + 0x778a_468f_507a_6034, + 0x0205_5993_1f7f_8103 + }).LexicographicallyLargest()); + Assert.IsTrue(Fp.FromRawUnchecked(new ulong[] + { + 0x1804_0000_0001_5554, + 0x8550_0005_3ab0_0001, + 0x633c_b57c_253c_276f, + 0x6e22_d1ec_31eb_b502, + 0xd391_6126_f2d1_4ca2, + 0x17fb_b857_1a00_6596 + }).LexicographicallyLargest()); + Assert.IsTrue(Fp.FromRawUnchecked(new ulong[] + { + 0x43f5_ffff_fffc_aaae, + 0x32b7_fff2_ed47_fffd, + 0x07e8_3a49_a2e9_9d69, + 0xeca8_f331_8332_bb7a, + 0xef14_8d1e_a0f4_c069, + 0x040a_b326_3eff_0206 + }).LexicographicallyLargest()); + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp12.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp12.cs new file mode 100644 index 0000000..5dfc57e --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp12.cs @@ -0,0 +1,336 @@ +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_Fp12 +{ + [TestMethod] + public void TestArithmetic() + { + var a = new Fp12(new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d + }), Fp.FromRawUnchecked(new ulong[] + { + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040 + }))), new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d + }), Fp.FromRawUnchecked(new ulong[] + { + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040 + })))); + + var b = new Fp12(new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d272_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d + }), Fp.FromRawUnchecked(new ulong[] + { + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e348 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040 + }))), new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd2_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d + }), Fp.FromRawUnchecked(new ulong[] + { + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a117_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040 + })))); + + var c = new Fp12(new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x47f9_cb98_71b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d + }), Fp.FromRawUnchecked(new ulong[] + { + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0x7791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_133c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_40e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_1744_c040 + }))), new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d + }), Fp.FromRawUnchecked(new ulong[] + { + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d3_c010_e60f, + 0x0acd_b8e1_58bf_e3c8 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_1040 + })))); + + // because a and b and c are similar to each other and + // I was lazy, this is just some arbitrary way to make + // them a little more different + a = a.Square().Invert().Square() + c; + b = b.Square().Invert().Square() + a; + c = c.Square().Invert().Square() + b; + + Assert.AreEqual(a * a, a.Square()); + Assert.AreEqual(b * b, b.Square()); + Assert.AreEqual(c * c, c.Square()); + + Assert.AreEqual((a + b) * c.Square(), (c * c * a) + (c * c * b)); + + Assert.AreEqual(a.Invert() * b.Invert(), (a * b).Invert()); + Assert.AreEqual(Fp12.One, a.Invert() * a); + + Assert.AreNotEqual(a, a.FrobeniusMap()); + Assert.AreEqual( + a, + a.FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + ); + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp2.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp2.cs new file mode 100644 index 0000000..a22e8df --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp2.cs @@ -0,0 +1,482 @@ +using System.Collections; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; + +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_Fp2 +{ + [TestMethod] + public void TestConditionalSelection() + { + var a = new Fp2( + Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), + Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 }) + ); + var b = new Fp2( + Fp.FromRawUnchecked(new ulong[] { 13, 14, 15, 16, 17, 18 }), + Fp.FromRawUnchecked(new ulong[] { 19, 20, 21, 22, 23, 24 }) + ); + + Assert.AreEqual(a, ConditionalSelect(in a, in b, false)); + Assert.AreEqual(b, ConditionalSelect(in a, in b, true)); + } + + [TestMethod] + public void TestEquality() + { + static bool IsEqual(in Fp2 a, in Fp2 b) + { + var eq = StructuralComparisons.StructuralEqualityComparer.Equals(a, b); + var ct_eq = a == b; + Assert.AreEqual(eq, ct_eq); + return eq; + } + + Assert.IsTrue(IsEqual( + new Fp2(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 })), + new Fp2(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 })) + )); + + Assert.IsFalse(IsEqual( + new Fp2(Fp.FromRawUnchecked(new ulong[] { 2, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 })), + new Fp2(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 })) + )); + + Assert.IsFalse(IsEqual( + new Fp2(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 2, 8, 9, 10, 11, 12 })), + new Fp2(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 })) + )); + } + + [TestMethod] + public void TestSquaring() + { + var a = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + })); + var b = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + }), Fp.FromRawUnchecked(new ulong[] + { + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + })); + + Assert.AreEqual(a.Square(), b); + } + + [TestMethod] + public void TestMultiplication() + { + var a = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + })); + var b = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + }), Fp.FromRawUnchecked(new ulong[] + { + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + })); + var c = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xf597_483e_27b4_e0f7, + 0x610f_badf_811d_ae5f, + 0x8432_af91_7714_327a, + 0x6a9a_9603_cf88_f09e, + 0xf05a_7bf8_bad0_eb01, + 0x0954_9131_c003_ffae, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x963b_02d0_f93d_37cd, + 0xc95c_e1cd_b30a_73d4, + 0x3087_25fa_3126_f9b8, + 0x56da_3c16_7fab_0d50, + 0x6b50_86b5_f4b6_d6af, + 0x09c3_9f06_2f18_e9f2, + })); + + Assert.AreEqual(c, a * b); + } + + [TestMethod] + public void TestAddition() + { + var a = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + })); + var b = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + }), Fp.FromRawUnchecked(new ulong[] + { + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + })); + var c = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x6b82_a9a7_08c1_32d2, + 0x476b_1da3_39ba_5ba4, + 0x848c_0e62_4b91_cd87, + 0x11f9_5955_295a_99ec, + 0xf337_6fce_2255_9f06, + 0x0c3f_e3fa_ce8c_8f43, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x6f99_2c12_73ab_5bc5, + 0x3355_1366_17a1_df33, + 0x8b0e_f74c_0aed_aff9, + 0x062f_9246_8ad2_ca12, + 0xe146_9770_738f_d584, + 0x12c3_c3dd_84bc_a26d, + })); + + Assert.AreEqual(a + b, c); + } + + [TestMethod] + public void TestSubtraction() + { + var a = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + })); + var b = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + }), Fp.FromRawUnchecked(new ulong[] + { + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + })); + var c = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xe1c0_86bb_bf1b_5981, + 0x4faf_c3a9_aa70_5d7e, + 0x2734_b5c1_0bb7_e726, + 0xb2bd_7776_af03_7a3e, + 0x1b89_5fb3_98a8_4164, + 0x1730_4aef_6f11_3cec, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x74c3_1c79_9519_1204, + 0x3271_aa54_79fd_ad2b, + 0xc9b4_7157_4915_a30f, + 0x65e4_0313_ec44_b8be, + 0x7487_b238_5b70_67cb, + 0x0952_3b26_d0ad_19a4, + })); + + Assert.AreEqual(c, a - b); + } + + [TestMethod] + public void TestNegation() + { + var a = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + })); + var b = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xf05c_e7ce_9c11_39d7, + 0x6274_8f57_97e8_a36d, + 0xc4e8_d9df_c664_96df, + 0xb457_88e1_8118_9209, + 0x6949_13d0_8772_930d, + 0x1549_836a_3770_f3cf, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x24d0_5bb9_fb9d_491c, + 0xfb1e_a120_c12e_39d0, + 0x7067_879f_c807_c7b1, + 0x60a9_269a_31bb_dab6, + 0x45c2_56bc_fd71_649b, + 0x18f6_9b5d_2b8a_fbde, + })); + + Assert.AreEqual(b, -a); + } + + [TestMethod] + public void TestSqrt() + { + // a = 1488924004771393321054797166853618474668089414631333405711627789629391903630694737978065425271543178763948256226639*u + 784063022264861764559335808165825052288770346101304131934508881646553551234697082295473567906267937225174620141295 + var a = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x2bee_d146_27d7_f9e9, + 0xb661_4e06_660e_5dce, + 0x06c4_cc7c_2f91_d42c, + 0x996d_7847_4b7a_63cc, + 0xebae_bc4c_820d_574e, + 0x1886_5e12_d93f_d845, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x7d82_8664_baf4_f566, + 0xd17e_6639_96ec_7339, + 0x679e_ad55_cb40_78d0, + 0xfe3b_2260_e001_ec28, + 0x3059_93d0_43d9_1b68, + 0x0626_f03c_0489_b72d, + })); + + Assert.AreEqual(a, a.Sqrt().Square()); + + // b = 5, which is a generator of the p - 1 order + // multiplicative subgroup + var b = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x6631_0000_0010_5545, + 0x2114_0040_0eec_000d, + 0x3fa7_af30_c820_e316, + 0xc52a_8b8d_6387_695d, + 0x9fb4_e61d_1e83_eac5, + 0x005c_b922_afe8_4dc7, + }), Fp.Zero); + + Assert.AreEqual(b, b.Sqrt().Square()); + + // c = 25, which is a generator of the (p - 1) / 2 order + // multiplicative subgroup + var c = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x44f6_0000_0051_ffae, + 0x86b8_0141_9948_0043, + 0xd715_9952_f1f3_794a, + 0x755d_6e3d_fe1f_fc12, + 0xd36c_d6db_5547_e905, + 0x02f8_c8ec_bf18_67bb, + }), Fp.Zero); + + Assert.AreEqual(c, c.Sqrt().Square()); + + // 2155129644831861015726826462986972654175647013268275306775721078997042729172900466542651176384766902407257452753362*u + 2796889544896299244102912275102369318775038861758288697415827248356648685135290329705805931514906495247464901062529 + // is nonsquare. + Assert.ThrowsException(() => + new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xc5fa_1bc8_fd00_d7f6, + 0x3830_ca45_4606_003b, + 0x2b28_7f11_04b1_02da, + 0xa7fb_30f2_8230_f23e, + 0x339c_db9e_e953_dbf0, + 0x0d78_ec51_d989_fc57, + }), Fp.FromRawUnchecked(new ulong[]{ + 0x27ec_4898_cf87_f613, + 0x9de1_394e_1abb_05a5, + 0x0947_f85d_c170_fc14, + 0x586f_bc69_6b61_14b7, + 0x2b34_75a4_077d_7169, + 0x13e1_c895_cc4b_6c22, + })).Sqrt()); + } + + [TestMethod] + public void TestInversion() + { + var a = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + }), Fp.FromRawUnchecked(new ulong[] + { + 0xd328_e37c_c2f5_8d41, + 0x948d_f085_8a60_5869, + 0x6032_f9d5_6f93_a573, + 0x2be4_83ef_3fff_dc87, + 0x30ef_61f8_8f48_3c2a, + 0x1333_f55a_3572_5be0, + })); + + var b = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x0581_a133_3d4f_48a6, + 0x5824_2f6e_f074_8500, + 0x0292_c955_349e_6da5, + 0xba37_721d_dd95_fcd0, + 0x70d1_6790_3aa5_dfc5, + 0x1189_5e11_8b58_a9d5, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x0eda_09d2_d7a8_5d17, + 0x8808_e137_a7d1_a2cf, + 0x43ae_2625_c1ff_21db, + 0xf85a_c9fd_f7a7_4c64, + 0x8fcc_dda5_b8da_9738, + 0x08e8_4f0c_b32c_d17d, + })); + Assert.AreEqual(b, a.Invert()); + + Assert.ThrowsException(() => Fp2.Zero.Invert()); + } + + [TestMethod] + public void TestLexicographicLargest() + { + Assert.IsFalse(Fp2.Zero.LexicographicallyLargest()); + Assert.IsFalse(Fp2.One.LexicographicallyLargest()); + Assert.IsTrue(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + }), Fp.FromRawUnchecked(new ulong[] + { + 0xd328_e37c_c2f5_8d41, + 0x948d_f085_8a60_5869, + 0x6032_f9d5_6f93_a573, + 0x2be4_83ef_3fff_dc87, + 0x30ef_61f8_8f48_3c2a, + 0x1333_f55a_3572_5be0, + })).LexicographicallyLargest()); + Assert.IsFalse(new Fp2(-Fp.FromRawUnchecked(new ulong[] + { + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + }), -Fp.FromRawUnchecked(new ulong[] + { + 0xd328_e37c_c2f5_8d41, + 0x948d_f085_8a60_5869, + 0x6032_f9d5_6f93_a573, + 0x2be4_83ef_3fff_dc87, + 0x30ef_61f8_8f48_3c2a, + 0x1333_f55a_3572_5be0, + })).LexicographicallyLargest()); + Assert.IsFalse(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + }), Fp.Zero).LexicographicallyLargest()); + Assert.IsTrue(new Fp2(-Fp.FromRawUnchecked(new ulong[] + { + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + }), Fp.Zero).LexicographicallyLargest()); + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp6.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp6.cs new file mode 100644 index 0000000..8afe9b6 --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp6.cs @@ -0,0 +1,168 @@ +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_Fp6 +{ + [TestMethod] + public void TestArithmetic() + { + var a = new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1 + })), c1: new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8 + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf + })), c2: new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6 + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040 + }))); + + var b = new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xf120_cb98_b16f_d84b, + 0x5fb5_10cf_f3de_1d61, + 0x0f21_a5d0_69d8_c251, + 0xaa1f_d62f_34f2_839a, + 0x5a13_3515_7f89_913f, + 0x14a3_fe32_9643_c247 + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0x3516_cb98_b16c_82f9, + 0x926d_10c2_e126_1d5f, + 0x1709_e01a_0cc2_5fba, + 0x96c8_c960_b825_3f14, + 0x4927_c234_207e_51a9, + 0x18ae_b158_d542_c44e + })), c1: new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xbf0d_cb98_b169_82fc, + 0xa679_10b7_1d1a_1d5c, + 0xb7c1_47c2_b8fb_06ff, + 0x1efa_710d_47d2_e7ce, + 0xed20_a79c_7e27_653c, + 0x02b8_5294_dac1_dfba + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0x9d52_cb98_b180_82e5, + 0x621d_1111_5176_1d6f, + 0xe798_8260_3b48_af43, + 0x0ad3_1637_a4f4_da37, + 0xaeac_737c_5ac1_cf2e, + 0x006e_7e73_5b48_b824 + })), c2: new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xe148_cb98_b17d_2d93, + 0x94d5_1104_3ebe_1d6c, + 0xef80_bca9_de32_4cac, + 0xf77c_0969_2827_95b1, + 0x9dc1_009a_fbb6_8f97, + 0x0479_3199_9a47_ba2b + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0x253e_cb98_b179_d841, + 0xc78d_10f7_2c06_1d6a, + 0xf768_f6f3_811b_ea15, + 0xe424_fc9a_ab5a_512b, + 0x8cd5_8db9_9cab_5001, + 0x0883_e4bf_d946_bc32 + }))); + + var c = new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x6934_cb98_b176_82ef, + 0xfa45_10ea_194e_1d67, + 0xff51_313d_2405_877e, + 0xd0cd_efcc_2e8d_0ca5, + 0x7bea_1ad8_3da0_106b, + 0x0c8e_97e6_1845_be39 + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0x4779_cb98_b18d_82d8, + 0xb5e9_1144_4daa_1d7a, + 0x2f28_6bda_a653_2fc2, + 0xbca6_94f6_8bae_ff0f, + 0x3d75_e6b8_1a3a_7a5d, + 0x0a44_c3c4_98cc_96a3 + })), c1: new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x8b6f_cb98_b18a_2d86, + 0xe8a1_1137_3af2_1d77, + 0x3710_a624_493c_cd2b, + 0xa94f_8828_0ee1_ba89, + 0x2c8a_73d6_bb2f_3ac7, + 0x0e4f_76ea_d7cb_98aa + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0xcf65_cb98_b186_d834, + 0x1b59_112a_283a_1d74, + 0x3ef8_e06d_ec26_6a95, + 0x95f8_7b59_9214_7603, + 0x1b9f_00f5_5c23_fb31, + 0x125a_2a11_16ca_9ab1 + })), c2: new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x135b_cb98_b183_82e2, + 0x4e11_111d_1582_1d72, + 0x46e1_1ab7_8f10_07fe, + 0x82a1_6e8b_1547_317d, + 0x0ab3_8e13_fd18_bb9b, + 0x1664_dd37_55c9_9cb8 + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0xce65_cb98_b131_8334, + 0xc759_0fdb_7c3a_1d2e, + 0x6fcb_8164_9d1c_8eb3, + 0x0d44_004d_1727_356a, + 0x3746_b738_a7d0_d296, + 0x136c_144a_96b1_34fc + }))); + + Assert.AreEqual(a * a, a.Square()); + Assert.AreEqual(b * b, b.Square()); + Assert.AreEqual(c * c, c.Square()); + + Assert.AreEqual((a + b) * c.Square(), (c * c * a) + (c * c * b)); + + Assert.AreEqual(a.Invert() * b.Invert(), (a * b).Invert()); + Assert.AreEqual(Fp6.One, a.Invert() * a); + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_G1.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_G1.cs new file mode 100644 index 0000000..dee4272 --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_G1.cs @@ -0,0 +1,592 @@ +using static Neo.Cryptography.BLS12_381.Constants; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.G1Constants; + +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_G1 +{ + [TestMethod] + public void TestBeta() + { + Assert.AreEqual(Fp.FromBytes(new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x19, 0x67, 0x2f, 0xdf, 0x76, + 0xce, 0x51, 0xba, 0x69, 0xc6, 0x07, 0x6a, 0x0f, 0x77, 0xea, 0xdd, 0xb3, 0xa9, 0x3b, + 0xe6, 0xf8, 0x96, 0x88, 0xde, 0x17, 0xd8, 0x13, 0x62, 0x0a, 0x00, 0x02, 0x2e, 0x01, + 0xff, 0xff, 0xff, 0xfe, 0xff, 0xfe + }), BETA); + Assert.AreNotEqual(Fp.One, BETA); + Assert.AreNotEqual(Fp.One, BETA * BETA); + Assert.AreEqual(Fp.One, BETA * BETA * BETA); + } + + [TestMethod] + public void TestIsOnCurve() + { + Assert.IsTrue(G1Affine.Identity.IsOnCurve); + Assert.IsTrue(G1Affine.Generator.IsOnCurve); + Assert.IsTrue(G1Projective.Identity.IsOnCurve); + Assert.IsTrue(G1Projective.Generator.IsOnCurve); + + Fp z = Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }); + + var gen = G1Affine.Generator; + G1Projective test = new(gen.X * z, gen.Y * z, in z); + + Assert.IsTrue(test.IsOnCurve); + + test = new(in z, in test.Y, in test.Z); + Assert.IsFalse(test.IsOnCurve); + } + + [TestMethod] + public void TestAffinePointEquality() + { + var a = G1Affine.Generator; + var b = G1Affine.Identity; + + Assert.AreEqual(a, a); + Assert.AreEqual(b, b); + Assert.AreNotEqual(a, b); + } + + [TestMethod] + public void TestProjectivePointEquality() + { + var a = G1Projective.Generator; + var b = G1Projective.Identity; + + Assert.AreEqual(a, a); + Assert.AreEqual(b, b); + Assert.AreNotEqual(a, b); + + Fp z = Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }); + + G1Projective c = new(a.X * z, a.Y * z, in z); + Assert.IsTrue(c.IsOnCurve); + + Assert.AreEqual(a, c); + Assert.AreNotEqual(b, c); + + c = new(in c.X, -c.Y, in c.Z); + Assert.IsTrue(c.IsOnCurve); + + Assert.AreNotEqual(a, c); + Assert.AreNotEqual(b, c); + + c = new(in z, -c.Y, in c.Z); + Assert.IsFalse(c.IsOnCurve); + Assert.AreNotEqual(a, b); + Assert.AreNotEqual(a, c); + Assert.AreNotEqual(b, c); + } + + [TestMethod] + public void TestConditionallySelectAffine() + { + var a = G1Affine.Generator; + var b = G1Affine.Identity; + + Assert.AreEqual(a, ConditionalSelect(in a, in b, false)); + Assert.AreEqual(b, ConditionalSelect(in a, in b, true)); + } + + [TestMethod] + public void TestConditionallySelectProjective() + { + var a = G1Projective.Generator; + var b = G1Projective.Identity; + + Assert.AreEqual(a, ConditionalSelect(in a, in b, false)); + Assert.AreEqual(b, ConditionalSelect(in a, in b, true)); + } + + [TestMethod] + public void TestProjectiveToAffine() + { + var a = G1Projective.Generator; + var b = G1Projective.Identity; + + Assert.IsTrue(new G1Affine(a).IsOnCurve); + Assert.IsFalse(new G1Affine(a).IsIdentity); + Assert.IsTrue(new G1Affine(b).IsOnCurve); + Assert.IsTrue(new G1Affine(b).IsIdentity); + + Fp z = Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }); + + G1Projective c = new(a.X * z, a.Y * z, in z); + + Assert.AreEqual(G1Affine.Generator, new G1Affine(c)); + } + + [TestMethod] + public void TestAffineToProjective() + { + var a = G1Affine.Generator; + var b = G1Affine.Identity; + + Assert.IsTrue(new G1Projective(a).IsOnCurve); + Assert.IsFalse(new G1Projective(a).IsIdentity); + Assert.IsTrue(new G1Projective(b).IsOnCurve); + Assert.IsTrue(new G1Projective(b).IsIdentity); + } + + [TestMethod] + public void TestDoubling() + { + { + var tmp = G1Projective.Identity.Double(); + Assert.IsTrue(tmp.IsIdentity); + Assert.IsTrue(tmp.IsOnCurve); + } + { + var tmp = G1Projective.Generator.Double(); + Assert.IsFalse(tmp.IsIdentity); + Assert.IsTrue(tmp.IsOnCurve); + + Assert.AreEqual(new G1Affine(Fp.FromRawUnchecked(new ulong[] + { + 0x53e9_78ce_58a9_ba3c, + 0x3ea0_583c_4f3d_65f9, + 0x4d20_bb47_f001_2960, + 0xa54c_664a_e5b2_b5d9, + 0x26b5_52a3_9d7e_b21f, + 0x0008_895d_26e6_8785 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x7011_0b32_9829_3940, + 0xda33_c539_3f1f_6afc, + 0xb86e_dfd1_6a5a_a785, + 0xaec6_d1c9_e7b1_c895, + 0x25cf_c2b5_22d1_1720, + 0x0636_1c83_f8d0_9b15 + })), new G1Affine(tmp)); + } + } + + [TestMethod] + public void TestProjectiveAddition() + { + { + var a = G1Projective.Identity; + var b = G1Projective.Identity; + var c = a + b; + Assert.IsTrue(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + { + var a = G1Projective.Identity; + var b = G1Projective.Generator; + + Fp z = Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }); + + b = new(b.X * z, b.Y * z, in z); + var c = a + b; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G1Projective.Generator, c); + } + { + var a = G1Projective.Identity; + var b = G1Projective.Generator; + + Fp z = Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }); + + b = new(b.X * z, b.Y * z, in z); + var c = b + a; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G1Projective.Generator, c); + } + { + var a = G1Projective.Generator.Double().Double(); // 4P + var b = G1Projective.Generator.Double(); // 2P + var c = a + b; + + var d = G1Projective.Generator; + for (int i = 0; i < 5; i++) + { + d += G1Projective.Generator; + } + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.IsFalse(d.IsIdentity); + Assert.IsTrue(d.IsOnCurve); + Assert.AreEqual(c, d); + } + { + Fp beta = Fp.FromRawUnchecked(new ulong[] + { + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741 + }); + beta = beta.Square(); + var a = G1Projective.Generator.Double().Double(); + var b = new G1Projective(a.X * beta, -a.Y, in a.Z); + Assert.IsTrue(a.IsOnCurve); + Assert.IsTrue(b.IsOnCurve); + + var c = a + b; + Assert.AreEqual(new G1Affine(new G1Projective(Fp.FromRawUnchecked(new ulong[] + { + 0x29e1_e987_ef68_f2d0, + 0xc5f3_ec53_1db0_3233, + 0xacd6_c4b6_ca19_730f, + 0x18ad_9e82_7bc2_bab7, + 0x46e3_b2c5_785c_c7a9, + 0x07e5_71d4_2d22_ddd6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x94d1_17a7_e5a5_39e7, + 0x8e17_ef67_3d4b_5d22, + 0x9d74_6aaf_508a_33ea, + 0x8c6d_883d_2516_c9a2, + 0x0bc3_b8d5_fb04_47f7, + 0x07bf_a4c7_210f_4f44, + }), in Fp.One)), new G1Affine(c)); + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + } + + [TestMethod] + public void TestMixedAddition() + { + { + var a = G1Affine.Identity; + var b = G1Projective.Identity; + var c = a + b; + Assert.IsTrue(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + { + var a = G1Affine.Identity; + var b = G1Projective.Generator; + + Fp z = Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }); + + b = new(b.X * z, b.Y * z, in z); + var c = a + b; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G1Projective.Generator, c); + } + { + var a = G1Affine.Identity; + var b = G1Projective.Generator; + + Fp z = Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }); + + b = new(b.X * z, b.Y * z, in z); + var c = b + a; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G1Projective.Generator, c); + } + { + var a = G1Projective.Generator.Double().Double(); // 4P + var b = G1Projective.Generator.Double(); // 2P + var c = a + b; + + var d = G1Projective.Generator; + for (int i = 0; i < 5; i++) + { + d += G1Affine.Generator; + } + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.IsFalse(d.IsIdentity); + Assert.IsTrue(d.IsOnCurve); + Assert.AreEqual(c, d); + } + { + Fp beta = Fp.FromRawUnchecked(new ulong[] + { + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741 + }); + beta = beta.Square(); + var a = G1Projective.Generator.Double().Double(); + var b = new G1Projective(a.X * beta, -a.Y, in a.Z); + var a2 = new G1Affine(a); + Assert.IsTrue(a2.IsOnCurve); + Assert.IsTrue(b.IsOnCurve); + + var c = a2 + b; + Assert.AreEqual(new G1Affine(new G1Projective(Fp.FromRawUnchecked(new ulong[] + { + 0x29e1_e987_ef68_f2d0, + 0xc5f3_ec53_1db0_3233, + 0xacd6_c4b6_ca19_730f, + 0x18ad_9e82_7bc2_bab7, + 0x46e3_b2c5_785c_c7a9, + 0x07e5_71d4_2d22_ddd6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x94d1_17a7_e5a5_39e7, + 0x8e17_ef67_3d4b_5d22, + 0x9d74_6aaf_508a_33ea, + 0x8c6d_883d_2516_c9a2, + 0x0bc3_b8d5_fb04_47f7, + 0x07bf_a4c7_210f_4f44 + }), Fp.One)), new G1Affine(c)); + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + } + + [TestMethod] + public void TestProjectiveNegationAndSubtraction() + { + var a = G1Projective.Generator.Double(); + Assert.AreEqual(a + (-a), G1Projective.Identity); + Assert.AreEqual(a + (-a), a - a); + } + + [TestMethod] + public void TestAffineNegationAndSubtraction() + { + var a = G1Affine.Generator; + Assert.AreEqual(G1Projective.Identity, new G1Projective(a) + (-a)); + Assert.AreEqual(new G1Projective(a) + (-a), new G1Projective(a) - a); + } + + [TestMethod] + public void TestProjectiveScalarMultiplication() + { + var g = G1Projective.Generator; + var a = Scalar.FromRaw(new ulong[] + { + 0x2b56_8297_a56d_a71c, + 0xd8c3_9ecb_0ef3_75d1, + 0x435c_38da_67bf_bf96, + 0x8088_a050_26b6_59b2 + }); + var b = Scalar.FromRaw(new ulong[] + { + 0x785f_dd9b_26ef_8b85, + 0xc997_f258_3769_5c18, + 0x4c8d_bc39_e7b7_56c1, + 0x70d9_b6cc_6d87_df20 + }); + var c = a * b; + + Assert.AreEqual(g * a * b, g * c); + } + + [TestMethod] + public void TestAffineScalarMultiplication() + { + var g = G1Affine.Generator; + var a = Scalar.FromRaw(new ulong[] + { + 0x2b56_8297_a56d_a71c, + 0xd8c3_9ecb_0ef3_75d1, + 0x435c_38da_67bf_bf96, + 0x8088_a050_26b6_59b2 + }); + var b = Scalar.FromRaw(new ulong[] + { + 0x785f_dd9b_26ef_8b85, + 0xc997_f258_3769_5c18, + 0x4c8d_bc39_e7b7_56c1, + 0x70d9_b6cc_6d87_df20 + }); + var c = a * b; + + Assert.AreEqual(new G1Affine(g * a) * b, g * c); + } + + [TestMethod] + public void TestIsTorsionFree() + { + var a = new G1Affine(Fp.FromRawUnchecked(new ulong[] + { + 0x0aba_f895_b97e_43c8, + 0xba4c_6432_eb9b_61b0, + 0x1250_6f52_adfe_307f, + 0x7502_8c34_3933_6b72, + 0x8474_4f05_b8e9_bd71, + 0x113d_554f_b095_54f7 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x73e9_0e88_f5cf_01c0, + 0x3700_7b65_dd31_97e2, + 0x5cf9_a199_2f0d_7c78, + 0x4f83_c10b_9eb3_330d, + 0xf6a6_3f6f_07f6_0961, + 0x0c53_b5b9_7e63_4df3 + })); + Assert.IsFalse(a.IsTorsionFree); + + Assert.IsTrue(G1Affine.Identity.IsTorsionFree); + Assert.IsTrue(G1Affine.Generator.IsTorsionFree); + } + + [TestMethod] + public void TestMulByX() + { + // multiplying by `x` a point in G1 is the same as multiplying by + // the equivalent scalar. + var generator = G1Projective.Generator; + var x = BLS_X_IS_NEGATIVE ? -new Scalar(BLS_X) : new Scalar(BLS_X); + Assert.AreEqual(generator.MulByX(), generator * x); + + var point = G1Projective.Generator * new Scalar(42); + Assert.AreEqual(point.MulByX(), point * x); + } + + [TestMethod] + public void TestClearCofactor() + { + // the generator (and the identity) are always on the curve, + // even after clearing the cofactor + var generator = G1Projective.Generator; + Assert.IsTrue(generator.ClearCofactor().IsOnCurve); + var id = G1Projective.Identity; + Assert.IsTrue(id.ClearCofactor().IsOnCurve); + + var z = Fp.FromRawUnchecked(new ulong[] + { + 0x3d2d1c670671394e, + 0x0ee3a800a2f7c1ca, + 0x270f4f21da2e5050, + 0xe02840a53f1be768, + 0x55debeb597512690, + 0x08bd25353dc8f791 + }); + + var point = new G1Projective(Fp.FromRawUnchecked(new ulong[] + { + 0x48af5ff540c817f0, + 0xd73893acaf379d5a, + 0xe6c43584e18e023c, + 0x1eda39c30f188b3e, + 0xf618c6d3ccc0f8d8, + 0x0073542cd671e16c + }) * z, Fp.FromRawUnchecked(new ulong[] + { + 0x57bf8be79461d0ba, + 0xfc61459cee3547c3, + 0x0d23567df1ef147b, + 0x0ee187bcce1d9b64, + 0xb0c8cfbe9dc8fdc1, + 0x1328661767ef368b + }), z.Square() * z); + + Assert.IsTrue(point.IsOnCurve); + Assert.IsFalse(new G1Affine(point).IsTorsionFree); + var cleared_point = point.ClearCofactor(); + Assert.IsTrue(cleared_point.IsOnCurve); + Assert.IsTrue(new G1Affine(cleared_point).IsTorsionFree); + + // in BLS12-381 the cofactor in G1 can be + // cleared multiplying by (1-x) + var h_eff = new Scalar(1) + new Scalar(BLS_X); + Assert.AreEqual(point.ClearCofactor(), point * h_eff); + } + + [TestMethod] + public void TestBatchNormalize() + { + var a = G1Projective.Generator.Double(); + var b = a.Double(); + var c = b.Double(); + + foreach (bool a_identity in new[] { false, true }) + { + foreach (bool b_identity in new[] { false, true }) + { + foreach (bool c_identity in new[] { false, true }) + { + var v = new[] { a, b, c }; + if (a_identity) + { + v[0] = G1Projective.Identity; + } + if (b_identity) + { + v[1] = G1Projective.Identity; + } + if (c_identity) + { + v[2] = G1Projective.Identity; + } + + var t = new G1Affine[3]; + var expected = new[] { new G1Affine(v[0]), new G1Affine(v[1]), new G1Affine(v[2]) }; + + G1Projective.BatchNormalize(v, t); + + CollectionAssert.AreEqual(expected, t); + } + } + } + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_G2.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_G2.cs new file mode 100644 index 0000000..153fea5 --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_G2.cs @@ -0,0 +1,812 @@ +using static Neo.Cryptography.BLS12_381.Constants; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; + +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_G2 +{ + [TestMethod] + public void TestIsOnCurve() + { + Assert.IsTrue(G2Affine.Identity.IsOnCurve); + Assert.IsTrue(G2Affine.Generator.IsOnCurve); + Assert.IsTrue(G2Projective.Identity.IsOnCurve); + Assert.IsTrue(G2Projective.Generator.IsOnCurve); + + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844 + })); + + var gen = G2Affine.Generator; + var test = new G2Projective(gen.X * z, gen.Y * z, z); + + Assert.IsTrue(test.IsOnCurve); + + test = new(in z, in test.Y, in test.Z); + Assert.IsFalse(test.IsOnCurve); + } + + [TestMethod] + public void TestAffinePointEquality() + { + var a = G2Affine.Generator; + var b = G2Affine.Identity; + + Assert.AreEqual(a, a); + Assert.AreEqual(b, b); + Assert.AreNotEqual(a, b); + } + + [TestMethod] + public void TestProjectivePointEquality() + { + var a = G2Projective.Generator; + var b = G2Projective.Identity; + + Assert.AreEqual(a, a); + Assert.AreEqual(b, b); + Assert.AreNotEqual(a, b); + + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844 + })); + + var c = new G2Projective(a.X * z, a.Y * z, in z); + Assert.IsTrue(c.IsOnCurve); + + Assert.AreEqual(a, c); + Assert.AreNotEqual(b, c); + + c = new(in c.X, -c.Y, in c.Z); + Assert.IsTrue(c.IsOnCurve); + + Assert.AreNotEqual(a, c); + Assert.AreNotEqual(b, c); + + c = new(in z, -c.Y, in c.Z); + Assert.IsFalse(c.IsOnCurve); + Assert.AreNotEqual(a, b); + Assert.AreNotEqual(a, c); + Assert.AreNotEqual(b, c); + } + + [TestMethod] + public void TestConditionallySelectAffine() + { + var a = G2Affine.Generator; + var b = G2Affine.Identity; + + Assert.AreEqual(a, ConditionalSelect(in a, in b, false)); + Assert.AreEqual(b, ConditionalSelect(in a, in b, true)); + } + + [TestMethod] + public void TestConditionallySelectProjective() + { + var a = G2Projective.Generator; + var b = G2Projective.Identity; + + Assert.AreEqual(a, ConditionalSelect(in a, in b, false)); + Assert.AreEqual(b, ConditionalSelect(in a, in b, true)); + } + + [TestMethod] + public void TestProjectiveToAffine() + { + var a = G2Projective.Generator; + var b = G2Projective.Identity; + + Assert.IsTrue(new G2Affine(a).IsOnCurve); + Assert.IsFalse(new G2Affine(a).IsIdentity); + Assert.IsTrue(new G2Affine(b).IsOnCurve); + Assert.IsTrue(new G2Affine(b).IsIdentity); + + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844 + })); + + var c = new G2Projective(a.X * z, a.Y * z, in z); + + Assert.AreEqual(G2Affine.Generator, new G2Affine(c)); + } + + [TestMethod] + public void TestAffineToProjective() + { + var a = G2Affine.Generator; + var b = G2Affine.Identity; + + Assert.IsTrue(new G2Projective(a).IsOnCurve); + Assert.IsFalse(new G2Projective(a).IsIdentity); + Assert.IsTrue(new G2Projective(b).IsOnCurve); + Assert.IsTrue(new G2Projective(b).IsIdentity); + } + + [TestMethod] + public void TestDoubling() + { + { + var tmp = G2Projective.Identity.Double(); + Assert.IsTrue(tmp.IsIdentity); + Assert.IsTrue(tmp.IsOnCurve); + } + { + var tmp = G2Projective.Generator.Double(); + Assert.IsFalse(tmp.IsIdentity); + Assert.IsTrue(tmp.IsOnCurve); + + Assert.AreEqual(new G2Affine(tmp), new G2Affine(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xe9d9_e2da_9620_f98b, + 0x54f1_1993_46b9_7f36, + 0x3db3_b820_376b_ed27, + 0xcfdb_31c9_b0b6_4f4c, + 0x41d7_c127_8635_4493, + 0x0571_0794_c255_c064 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xd6c1_d3ca_6ea0_d06e, + 0xda0c_bd90_5595_489f, + 0x4f53_52d4_3479_221d, + 0x8ade_5d73_6f8c_97e0, + 0x48cc_8433_925e_f70e, + 0x08d7_ea71_ea91_ef81 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x15ba_26eb_4b0d_186f, + 0x0d08_6d64_b7e9_e01e, + 0xc8b8_48dd_652f_4c78, + 0xeecf_46a6_123b_ae4f, + 0x255e_8dd8_b6dc_812a, + 0x1641_42af_21dc_f93f + }), Fp.FromRawUnchecked(new ulong[] + { + 0xf9b4_a1a8_9598_4db4, + 0xd417_b114_cccf_f748, + 0x6856_301f_c89f_086e, + 0x41c7_7787_8931_e3da, + 0x3556_b155_066a_2105, + 0x00ac_f7d3_25cb_89cf + })))); + } + } + + [TestMethod] + public void TestProjectiveAddition() + { + { + var a = G2Projective.Identity; + var b = G2Projective.Identity; + var c = a + b; + Assert.IsTrue(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + { + var a = G2Projective.Identity; + var b = G2Projective.Generator; + { + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844 + })); + + b = new G2Projective(b.X * z, b.Y * z, in z); + } + var c = a + b; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G2Projective.Generator, c); + } + { + var a = G2Projective.Identity; + var b = G2Projective.Generator; + { + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844 + })); + + b = new G2Projective(b.X * z, b.Y * z, in z); + } + var c = b + a; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G2Projective.Generator, c); + } + { + var a = G2Projective.Generator.Double().Double(); // 4P + var b = G2Projective.Generator.Double(); // 2P + var c = a + b; + + var d = G2Projective.Generator; + for (int i = 0; i < 5; i++) + { + d += G2Projective.Generator; + } + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.IsFalse(d.IsIdentity); + Assert.IsTrue(d.IsOnCurve); + Assert.AreEqual(c, d); + } + + // Degenerate case + { + var beta = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741 + }), Fp.Zero); + beta = beta.Square(); + var a = G2Projective.Generator.Double().Double(); + var b = new G2Projective(a.X * beta, -a.Y, in a.Z); + Assert.IsTrue(a.IsOnCurve); + Assert.IsTrue(b.IsOnCurve); + + var c = a + b; + Assert.AreEqual( + new G2Affine(c), + new G2Affine(new G2Projective(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x705a_bc79_9ca7_73d3, + 0xfe13_2292_c1d4_bf08, + 0xf37e_ce3e_07b2_b466, + 0x887e_1c43_f447_e301, + 0x1e09_70d0_33bc_77e8, + 0x1985_c81e_20a6_93f2 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1d79_b25d_b36a_b924, + 0x2394_8e4d_5296_39d3, + 0x471b_a7fb_0d00_6297, + 0x2c36_d4b4_465d_c4c0, + 0x82bb_c3cf_ec67_f538, + 0x051d_2728_b67b_f952 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x41b1_bbf6_576c_0abf, + 0xb6cc_9371_3f7a_0f9a, + 0x6b65_b43e_48f3_f01f, + 0xfb7a_4cfc_af81_be4f, + 0x3e32_dadc_6ec2_2cb6, + 0x0bb0_fc49_d798_07e3 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x7d13_9778_8f5f_2ddf, + 0xab29_0714_4ff0_d8e8, + 0x5b75_73e0_cdb9_1f92, + 0x4cb8_932d_d31d_af28, + 0x62bb_fac6_db05_2a54, + 0x11f9_5c16_d14c_3bbe + })), Fp2.One))); + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + } + + [TestMethod] + public void TestMixedAddition() + { + { + var a = G2Affine.Identity; + var b = G2Projective.Identity; + var c = a + b; + Assert.IsTrue(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + { + var a = G2Affine.Identity; + var b = G2Projective.Generator; + { + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844 + })); + + b = new G2Projective(b.X * z, b.Y * z, in z); + } + var c = a + b; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G2Projective.Generator, c); + } + { + var a = G2Affine.Identity; + var b = G2Projective.Generator; + { + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844 + })); + + b = new G2Projective(b.X * z, b.Y * z, in z); + } + var c = b + a; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G2Projective.Generator, c); + } + { + var a = G2Projective.Generator.Double().Double(); // 4P + var b = G2Projective.Generator.Double(); // 2P + var c = a + b; + + var d = G2Projective.Generator; + for (int i = 0; i < 5; i++) + { + d += G2Affine.Generator; + } + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.IsFalse(d.IsIdentity); + Assert.IsTrue(d.IsOnCurve); + Assert.AreEqual(c, d); + } + + // Degenerate case + { + var beta = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741 + }), Fp.Zero); + beta = beta.Square(); + var _a = G2Projective.Generator.Double().Double(); + var b = new G2Projective(_a.X * beta, -_a.Y, in _a.Z); + var a = new G2Affine(_a); + Assert.IsTrue((a.IsOnCurve)); + Assert.IsTrue((b.IsOnCurve)); + + var c = a + b; + Assert.AreEqual(new G2Affine(new G2Projective(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x705a_bc79_9ca7_73d3, + 0xfe13_2292_c1d4_bf08, + 0xf37e_ce3e_07b2_b466, + 0x887e_1c43_f447_e301, + 0x1e09_70d0_33bc_77e8, + 0x1985_c81e_20a6_93f2 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1d79_b25d_b36a_b924, + 0x2394_8e4d_5296_39d3, + 0x471b_a7fb_0d00_6297, + 0x2c36_d4b4_465d_c4c0, + 0x82bb_c3cf_ec67_f538, + 0x051d_2728_b67b_f952 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x41b1_bbf6_576c_0abf, + 0xb6cc_9371_3f7a_0f9a, + 0x6b65_b43e_48f3_f01f, + 0xfb7a_4cfc_af81_be4f, + 0x3e32_dadc_6ec2_2cb6, + 0x0bb0_fc49_d798_07e3 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x7d13_9778_8f5f_2ddf, + 0xab29_0714_4ff0_d8e8, + 0x5b75_73e0_cdb9_1f92, + 0x4cb8_932d_d31d_af28, + 0x62bb_fac6_db05_2a54, + 0x11f9_5c16_d14c_3bbe + })), Fp2.One)), new G2Affine(c)); + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + } + + [TestMethod] + public void TestProjectiveNegationAndSubtraction() + { + var a = G2Projective.Generator.Double(); + Assert.AreEqual(G2Projective.Identity, a + (-a)); + Assert.AreEqual(a - a, a + (-a)); + } + + [TestMethod] + public void TestAffineNegationAndSubtraction() + { + var a = G2Affine.Generator; + Assert.AreEqual(G2Projective.Identity, new G2Projective(a) + (-a)); + Assert.AreEqual(new G2Projective(a) - a, new G2Projective(a) + (-a)); + } + + [TestMethod] + public void TestProjectiveScalarMultiplication() + { + var g = G2Projective.Generator; + var a = Scalar.FromRaw(new ulong[] + { + 0x2b56_8297_a56d_a71c, + 0xd8c3_9ecb_0ef3_75d1, + 0x435c_38da_67bf_bf96, + 0x8088_a050_26b6_59b2 + }); + var b = Scalar.FromRaw(new ulong[] + { + 0x785f_dd9b_26ef_8b85, + 0xc997_f258_3769_5c18, + 0x4c8d_bc39_e7b7_56c1, + 0x70d9_b6cc_6d87_df20 + }); + var c = a * b; + + Assert.AreEqual(g * c, g * a * b); + } + + [TestMethod] + public void TestAffineScalarMultiplication() + { + var g = G2Affine.Generator; + var a = Scalar.FromRaw(new ulong[] + { + 0x2b56_8297_a56d_a71c, + 0xd8c3_9ecb_0ef3_75d1, + 0x435c_38da_67bf_bf96, + 0x8088_a050_26b6_59b2 + }); + var b = Scalar.FromRaw(new ulong[] + { + 0x785f_dd9b_26ef_8b85, + 0xc997_f258_3769_5c18, + 0x4c8d_bc39_e7b7_56c1, + 0x70d9_b6cc_6d87_df20 + }); + var c = a * b; + + Assert.AreEqual(g * c, new G2Affine(g * a) * b); + } + + [TestMethod] + public void TestIsTorsionFree() + { + var a = new G2Affine(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x89f5_50c8_13db_6431, + 0xa50b_e8c4_56cd_8a1a, + 0xa45b_3741_14ca_e851, + 0xbb61_90f5_bf7f_ff63, + 0x970c_a02c_3ba8_0bc7, + 0x02b8_5d24_e840_fbac + }), + Fp.FromRawUnchecked(new ulong[] + { + 0x6888_bc53_d707_16dc, + 0x3dea_6b41_1768_2d70, + 0xd8f5_f930_500c_a354, + 0x6b5e_cb65_56f5_c155, + 0xc96b_ef04_3477_8ab0, + 0x0508_1505_5150_06ad + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x3cf1_ea0d_434b_0f40, + 0x1a0d_c610_e603_e333, + 0x7f89_9561_60c7_2fa0, + 0x25ee_03de_cf64_31c5, + 0xeee8_e206_ec0f_e137, + 0x0975_92b2_26df_ef28 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x71e8_bb5f_2924_7367, + 0xa5fe_049e_2118_31ce, + 0x0ce6_b354_502a_3896, + 0x93b0_1200_0997_314e, + 0x6759_f3b6_aa5b_42ac, + 0x1569_44c4_dfe9_2bbb + }))); + Assert.IsFalse(a.IsTorsionFree); + + Assert.IsTrue(G2Affine.Identity.IsTorsionFree); + Assert.IsTrue(G2Affine.Generator.IsTorsionFree); + } + + [TestMethod] + public void TestMulByX() + { + // multiplying by `x` a point in G2 is the same as multiplying by + // the equivalent scalar. + var generator = G2Projective.Generator; + var x = BLS_X_IS_NEGATIVE ? -new Scalar(BLS_X) : new Scalar(BLS_X); + Assert.AreEqual(generator * x, generator.MulByX()); + + var point = G2Projective.Generator * new Scalar(42); + Assert.AreEqual(point * x, point.MulByX()); + } + + [TestMethod] + public void TestPsi() + { + var generator = G2Projective.Generator; + + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x0ef2ddffab187c0a, + 0x2424522b7d5ecbfc, + 0xc6f341a3398054f4, + 0x5523ddf409502df0, + 0xd55c0b5a88e0dd97, + 0x066428d704923e52 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x538bbe0c95b4878d, + 0xad04a50379522881, + 0x6d5c05bf5c12fb64, + 0x4ce4a069a2d34787, + 0x59ea6c8d0dffaeaf, + 0x0d42a083a75bd6f3 + })); + + // `point` is a random point in the curve + var point = new G2Projective(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xee4c8cb7c047eaf2, + 0x44ca22eee036b604, + 0x33b3affb2aefe101, + 0x15d3e45bbafaeb02, + 0x7bfc2154cd7419a4, + 0x0a2d0c2b756e5edc + }), Fp.FromRawUnchecked(new ulong[] + { + 0xfc224361029a8777, + 0x4cbf2baab8740924, + 0xc5008c6ec6592c89, + 0xecc2c57b472a9c2d, + 0x8613eafd9d81ffb1, + 0x10fe54daa2d3d495 + })) * z, new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x7de7edc43953b75c, + 0x58be1d2de35e87dc, + 0x5731d30b0e337b40, + 0xbe93b60cfeaae4c9, + 0x8b22c203764bedca, + 0x01616c8d1033b771 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xea126fe476b5733b, + 0x85cee68b5dae1652, + 0x98247779f7272b04, + 0xa649c8b468c6e808, + 0xb5b9a62dff0c4e45, + 0x1555b67fc7bbe73d + })), z.Square() * z); + Assert.IsTrue(point.IsOnCurve); + + // psi2(P) = psi(psi(P)) + Assert.AreEqual(generator.Psi2(), generator.Psi().Psi()); + Assert.AreEqual(point.Psi2(), point.Psi().Psi()); + // psi(P) is a morphism + Assert.AreEqual(generator.Double().Psi(), generator.Psi().Double()); + Assert.AreEqual(point.Psi() + generator.Psi(), (point + generator).Psi()); + // psi(P) behaves in the same way on the same projective point + var normalized_points = new G2Affine[1]; + G2Projective.BatchNormalize(new[] { point }, normalized_points); + var normalized_point = new G2Projective(normalized_points[0]); + Assert.AreEqual(point.Psi(), normalized_point.Psi()); + Assert.AreEqual(point.Psi2(), normalized_point.Psi2()); + } + + [TestMethod] + public void TestClearCofactor() + { + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x0ef2ddffab187c0a, + 0x2424522b7d5ecbfc, + 0xc6f341a3398054f4, + 0x5523ddf409502df0, + 0xd55c0b5a88e0dd97, + 0x066428d704923e52 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x538bbe0c95b4878d, + 0xad04a50379522881, + 0x6d5c05bf5c12fb64, + 0x4ce4a069a2d34787, + 0x59ea6c8d0dffaeaf, + 0x0d42a083a75bd6f3 + })); + + // `point` is a random point in the curve + var point = new G2Projective(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xee4c8cb7c047eaf2, + 0x44ca22eee036b604, + 0x33b3affb2aefe101, + 0x15d3e45bbafaeb02, + 0x7bfc2154cd7419a4, + 0x0a2d0c2b756e5edc + }), Fp.FromRawUnchecked(new ulong[] + { + 0xfc224361029a8777, + 0x4cbf2baab8740924, + 0xc5008c6ec6592c89, + 0xecc2c57b472a9c2d, + 0x8613eafd9d81ffb1, + 0x10fe54daa2d3d495 + })) * z, new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x7de7edc43953b75c, + 0x58be1d2de35e87dc, + 0x5731d30b0e337b40, + 0xbe93b60cfeaae4c9, + 0x8b22c203764bedca, + 0x01616c8d1033b771 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xea126fe476b5733b, + 0x85cee68b5dae1652, + 0x98247779f7272b04, + 0xa649c8b468c6e808, + 0xb5b9a62dff0c4e45, + 0x1555b67fc7bbe73d + })), z.Square() * z); + + Assert.IsTrue(point.IsOnCurve); + Assert.IsFalse(new G2Affine(point).IsTorsionFree); + var cleared_point = point.ClearCofactor(); + + Assert.IsTrue(cleared_point.IsOnCurve); + Assert.IsTrue(new G2Affine(cleared_point).IsTorsionFree); + + // the generator (and the identity) are always on the curve, + // even after clearing the cofactor + var generator = G2Projective.Generator; + Assert.IsTrue(generator.ClearCofactor().IsOnCurve); + var id = G2Projective.Identity; + Assert.IsTrue(id.ClearCofactor().IsOnCurve); + + // test the effect on q-torsion points multiplying by h_eff modulo |Scalar| + // h_eff % q = 0x2b116900400069009a40200040001ffff + byte[] h_eff_modq = + { + 0xff, 0xff, 0x01, 0x00, 0x04, 0x00, 0x02, 0xa4, 0x09, 0x90, 0x06, 0x00, 0x04, 0x90, 0x16, + 0xb1, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 + }; + Assert.AreEqual(generator * h_eff_modq, generator.ClearCofactor()); + Assert.AreEqual(cleared_point * h_eff_modq, cleared_point.ClearCofactor()); + } + + [TestMethod] + public void TestBatchNormalize() + { + var a = G2Projective.Generator.Double(); + var b = a.Double(); + var c = b.Double(); + + foreach (bool a_identity in new[] { false, true }) + { + foreach (bool b_identity in new[] { false, true }) + { + foreach (bool c_identity in new[] { false, true }) + { + var v = new[] { a, b, c }; + if (a_identity) + { + v[0] = G2Projective.Identity; + } + if (b_identity) + { + v[1] = G2Projective.Identity; + } + if (c_identity) + { + v[2] = G2Projective.Identity; + } + + var t = new G2Affine[3]; + var expected = new[] { new G2Affine(v[0]), new G2Affine(v[1]), new G2Affine(v[2]) }; + + G2Projective.BatchNormalize(v, t); + + CollectionAssert.AreEqual(t, expected); + } + } + } + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Pairings.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Pairings.cs new file mode 100644 index 0000000..6bac1e1 --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Pairings.cs @@ -0,0 +1,58 @@ +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_Pairings +{ + [TestMethod] + public void TestGtGenerator() + { + Assert.AreEqual( + Gt.Generator, + Bls12.Pairing(in G1Affine.Generator, in G2Affine.Generator) + ); + } + + [TestMethod] + public void TestBilinearity() + { + var a = Scalar.FromRaw(new ulong[] { 1, 2, 3, 4 }).Invert().Square(); + var b = Scalar.FromRaw(new ulong[] { 5, 6, 7, 8 }).Invert().Square(); + var c = a * b; + + var g = new G1Affine(G1Affine.Generator * a); + var h = new G2Affine(G2Affine.Generator * b); + var p = Bls12.Pairing(in g, in h); + + Assert.AreNotEqual(Gt.Identity, p); + + var expected = new G1Affine(G1Affine.Generator * c); + + Assert.AreEqual(p, Bls12.Pairing(in expected, in G2Affine.Generator)); + Assert.AreEqual( + p, + Bls12.Pairing(in G1Affine.Generator, in G2Affine.Generator) * c + ); + } + + [TestMethod] + public void TestUnitary() + { + var g = G1Affine.Generator; + var h = G2Affine.Generator; + var p = -Bls12.Pairing(in g, in h); + var q = Bls12.Pairing(in g, -h); + var r = Bls12.Pairing(-g, in h); + + Assert.AreEqual(p, q); + Assert.AreEqual(q, r); + } + + [TestMethod] + public void TestMillerLoopResultDefault() + { + Assert.AreEqual( + Gt.Identity, + new MillerLoopResult(Fp12.One).FinalExponentiation() + ); + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Scalar.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Scalar.cs new file mode 100644 index 0000000..2b46fe1 --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Scalar.cs @@ -0,0 +1,400 @@ +using static Neo.Cryptography.BLS12_381.ScalarConstants; + +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_Scalar +{ + private static readonly Scalar LARGEST = new(new ulong[] + { + 0xffff_ffff_0000_0000, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48 + }); + + [TestMethod] + public void TestInv() + { + // Compute -(q^{-1} mod 2^64) mod 2^64 by exponentiating + // by totient(2**64) - 1 + + var inv = 1ul; + for (int i = 0; i < 63; i++) + { + inv = unchecked(inv * inv); + inv = unchecked(inv * MODULUS_LIMBS_64[0]); + } + inv = unchecked(~inv + 1); + + Assert.AreEqual(INV, inv); + } + + [TestMethod] + public void TestToString() + { + Assert.AreEqual("0x0000000000000000000000000000000000000000000000000000000000000000", Scalar.Zero.ToString()); + Assert.AreEqual("0x0000000000000000000000000000000000000000000000000000000000000001", Scalar.One.ToString()); + Assert.AreEqual("0x1824b159acc5056f998c4fefecbc4ff55884b7fa0003480200000001fffffffe", R2.ToString()); + } + + [TestMethod] + public void TestEquality() + { + Assert.AreEqual(Scalar.Zero, Scalar.Zero); + Assert.AreEqual(Scalar.One, Scalar.One); + Assert.AreEqual(R2, R2); + + Assert.AreNotEqual(Scalar.Zero, Scalar.One); + Assert.AreNotEqual(Scalar.One, R2); + } + + [TestMethod] + public void TestToBytes() + { + CollectionAssert.AreEqual(new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + }, Scalar.Zero.ToArray()); + + CollectionAssert.AreEqual(new byte[] + { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + }, Scalar.One.ToArray()); + + CollectionAssert.AreEqual(new byte[] + { + 254, 255, 255, 255, 1, 0, 0, 0, 2, 72, 3, 0, 250, 183, 132, 88, 245, 79, 188, 236, 239, + 79, 140, 153, 111, 5, 197, 172, 89, 177, 36, 24 + }, R2.ToArray()); + + CollectionAssert.AreEqual(new byte[] + { + 0, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115 + }, (-Scalar.One).ToArray()); + } + + [TestMethod] + public void TestFromBytes() + { + Assert.AreEqual(Scalar.Zero, Scalar.FromBytes(new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + })); + + Assert.AreEqual(Scalar.One, Scalar.FromBytes(new byte[] + { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + })); + + Assert.AreEqual(R2, Scalar.FromBytes(new byte[] + { + 254, 255, 255, 255, 1, 0, 0, 0, 2, 72, 3, 0, 250, 183, 132, 88, 245, 79, 188, 236, 239, + 79, 140, 153, 111, 5, 197, 172, 89, 177, 36, 24 + })); + + // -1 should work + Scalar.FromBytes(new byte[] + { + 0, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115 + }); + + // modulus is invalid + Assert.ThrowsException(() => Scalar.FromBytes(new byte[] + { + 1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115 + })); + + // Anything larger than the modulus is invalid + Assert.ThrowsException(() => Scalar.FromBytes(new byte[] + { + 2, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115 + })); + Assert.ThrowsException(() => Scalar.FromBytes(new byte[] + { + 1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 58, 51, 72, 125, 157, 41, 83, 167, 237, 115 + })); + Assert.ThrowsException(() => Scalar.FromBytes(new byte[] + { + 1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 116 + })); + } + + [TestMethod] + public void TestFromBytesWideR2() + { + Assert.AreEqual(R2, Scalar.FromBytesWide(new byte[] + { + 254, 255, 255, 255, 1, 0, 0, 0, 2, 72, 3, 0, 250, 183, 132, 88, 245, 79, 188, 236, 239, + 79, 140, 153, 111, 5, 197, 172, 89, 177, 36, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + })); + } + + [TestMethod] + public void TestFromBytesWideNegativeOne() + { + Assert.AreEqual(-Scalar.One, Scalar.FromBytesWide(new byte[] + { + 0, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + })); + } + + [TestMethod] + public void TestFromBytesWideMaximum() + { + Assert.AreEqual(new Scalar(new ulong[] + { + 0xc62c_1805_439b_73b1, + 0xc2b9_551e_8ced_218e, + 0xda44_ec81_daf9_a422, + 0x5605_aa60_1c16_2e79 + }), Scalar.FromBytesWide(Enumerable.Repeat(0xff, 64).ToArray())); + } + + [TestMethod] + public void TestZero() + { + Assert.AreEqual(Scalar.Zero, -Scalar.Zero); + Assert.AreEqual(Scalar.Zero, Scalar.Zero + Scalar.Zero); + Assert.AreEqual(Scalar.Zero, Scalar.Zero - Scalar.Zero); + Assert.AreEqual(Scalar.Zero, Scalar.Zero * Scalar.Zero); + } + + [TestMethod] + public void TestAddition() + { + var tmp = LARGEST; + tmp += LARGEST; + + Assert.AreEqual(new Scalar(new ulong[] + { + 0xffff_fffe_ffff_ffff, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48 + }), tmp); + + tmp = LARGEST; + tmp += new Scalar(new ulong[] { 1, 0, 0, 0 }); + + Assert.AreEqual(Scalar.Zero, tmp); + } + + [TestMethod] + public void TestNegation() + { + var tmp = -LARGEST; + + Assert.AreEqual(new Scalar(new ulong[] { 1, 0, 0, 0 }), tmp); + + tmp = -Scalar.Zero; + Assert.AreEqual(Scalar.Zero, tmp); + tmp = -new Scalar(new ulong[] { 1, 0, 0, 0 }); + Assert.AreEqual(LARGEST, tmp); + } + + [TestMethod] + public void TestSubtraction() + { + var tmp = LARGEST; + tmp -= LARGEST; + + Assert.AreEqual(Scalar.Zero, tmp); + + tmp = Scalar.Zero; + tmp -= LARGEST; + + var tmp2 = MODULUS; + tmp2 -= LARGEST; + + Assert.AreEqual(tmp, tmp2); + } + + [TestMethod] + public void TestMultiplication() + { + var cur = LARGEST; + + for (int i = 0; i < 100; i++) + { + var tmp = cur; + tmp *= cur; + + var tmp2 = Scalar.Zero; + foreach (bool b in cur + .ToArray() + .SelectMany(p => Enumerable.Range(0, 8).Select(q => ((p >> q) & 1) == 1)) + .Reverse()) + { + var tmp3 = tmp2; + tmp2 += tmp3; + + if (b) + { + tmp2 += cur; + } + } + + Assert.AreEqual(tmp, tmp2); + + cur += LARGEST; + } + } + + [TestMethod] + public void TestSquaring() + { + var cur = LARGEST; + + for (int i = 0; i < 100; i++) + { + var tmp = cur; + tmp = tmp.Square(); + + var tmp2 = Scalar.Zero; + foreach (bool b in cur + .ToArray() + .SelectMany(p => Enumerable.Range(0, 8).Select(q => ((p >> q) & 1) == 1)) + .Reverse()) + { + var tmp3 = tmp2; + tmp2 += tmp3; + + if (b) + { + tmp2 += cur; + } + } + + Assert.AreEqual(tmp, tmp2); + + cur += LARGEST; + } + } + + [TestMethod] + public void TestInversion() + { + Assert.ThrowsException(() => Scalar.Zero.Invert()); + Assert.AreEqual(Scalar.One, Scalar.One.Invert()); + Assert.AreEqual(-Scalar.One, (-Scalar.One).Invert()); + + var tmp = R2; + + for (int i = 0; i < 100; i++) + { + var tmp2 = tmp.Invert(); + tmp2 *= tmp; + + Assert.AreEqual(Scalar.One, tmp2); + + tmp += R2; + } + } + + [TestMethod] + public void TestInvertIsPow() + { + ulong[] q_minus_2 = + { + 0xffff_fffe_ffff_ffff, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48 + }; + + var r1 = R; + var r2 = R; + var r3 = R; + + for (int i = 0; i < 100; i++) + { + r1 = r1.Invert(); + r2 = r2.PowVartime(q_minus_2); + r3 = r3.Pow(q_minus_2); + + Assert.AreEqual(r1, r2); + Assert.AreEqual(r2, r3); + // Add R so we check something different next time around + r1 += R; + r2 = r1; + r3 = r1; + } + } + + [TestMethod] + public void TestSqrt() + { + Assert.AreEqual(Scalar.Zero.Sqrt(), Scalar.Zero); + + var square = new Scalar(new ulong[] + { + 0x46cd_85a5_f273_077e, + 0x1d30_c47d_d68f_c735, + 0x77f6_56f6_0bec_a0eb, + 0x494a_a01b_df32_468d + }); + + var none_count = 0; + + for (int i = 0; i < 100; i++) + { + Scalar square_root; + try + { + square_root = square.Sqrt(); + Assert.AreEqual(square, square_root * square_root); + } + catch (ArithmeticException) + { + none_count++; + } + square -= Scalar.One; + } + + Assert.AreEqual(49, none_count); + } + + [TestMethod] + public void TestFromRaw() + { + Assert.AreEqual(Scalar.FromRaw(new ulong[] + { + 0x0001_ffff_fffd, + 0x5884_b7fa_0003_4802, + 0x998c_4fef_ecbc_4ff5, + 0x1824_b159_acc5_056f + }), Scalar.FromRaw(Enumerable.Repeat(0xffff_ffff_ffff_ffff, 4).ToArray())); + + Assert.AreEqual(Scalar.Zero, Scalar.FromRaw(MODULUS_LIMBS_64)); + + Assert.AreEqual(R, Scalar.FromRaw(new ulong[] { 1, 0, 0, 0 })); + } + + [TestMethod] + public void TestDouble() + { + var a = Scalar.FromRaw(new ulong[] + { + 0x1fff_3231_233f_fffd, + 0x4884_b7fa_0003_4802, + 0x998c_4fef_ecbc_4ff3, + 0x1824_b159_acc5_0562 + }); + + Assert.AreEqual(a + a, a.Double()); + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/Usings.cs b/tests/Neo.Cryptography.BLS12_381.Tests/Usings.cs new file mode 100644 index 0000000..ab67c7e --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file