diff --git a/.gitignore b/.gitignore
index 3398d4b4..acc5c501 100644
--- a/.gitignore
+++ b/.gitignore
@@ -360,4 +360,6 @@ launchSettings.json
/dist
# Secret test files
-test_SessionIdentity.json
\ No newline at end of file
+test_SessionIdentity.json
+
+.mono/
\ No newline at end of file
diff --git a/ICP.sln b/ICP.sln
index 4a448821..6b100093 100644
--- a/ICP.sln
+++ b/ICP.sln
@@ -47,7 +47,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdjCase.ICP.WebSockets", "s
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.WebSockets", "samples\Sample.WebSockets\Sample.WebSockets.csproj", "{B4F9E828-BC3D-4EFF-9E21-53DD82928AB0}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSockets.Tests", "test\WebSockets.Tests\WebSockets.Tests.csproj", "{3015AFBC-B866-459F-B25C-4BEA00C2A91E}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSockets.Tests", "test\WebSockets.Tests\WebSockets.Tests.csproj", "{3015AFBC-B866-459F-B25C-4BEA00C2A91E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BLS.Tests", "test\BLS.Tests\BLS.Tests.csproj", "{213F30BA-D147-4291-93A3-13A8A006126D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -111,6 +113,10 @@ Global
{3015AFBC-B866-459F-B25C-4BEA00C2A91E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3015AFBC-B866-459F-B25C-4BEA00C2A91E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3015AFBC-B866-459F-B25C-4BEA00C2A91E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {213F30BA-D147-4291-93A3-13A8A006126D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {213F30BA-D147-4291-93A3-13A8A006126D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {213F30BA-D147-4291-93A3-13A8A006126D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {213F30BA-D147-4291-93A3-13A8A006126D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -126,6 +132,7 @@ Global
{94023B5A-0650-4E67-A685-D1C979DA717F} = {9A64FA9E-AC6C-4C1F-B648-E9E64F172EA8}
{B4F9E828-BC3D-4EFF-9E21-53DD82928AB0} = {7FADA9D9-5FDA-4CFB-8A53-A578A61FBBA9}
{3015AFBC-B866-459F-B25C-4BEA00C2A91E} = {F71B8320-C279-4A79-A8D4-4039DB39D522}
+ {213F30BA-D147-4291-93A3-13A8A006126D} = {F71B8320-C279-4A79-A8D4-4039DB39D522}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3103E11A-E792-49EE-98C5-B2F3709DB088}
diff --git a/README.md b/README.md
index 059164df..4569d827 100644
--- a/README.md
+++ b/README.md
@@ -141,4 +141,4 @@ public enum MyVariantTag
- [IC Http Interface Spec](https://smartcontracts.org/docs/current/references/ic-interface-spec)
- [Candid Spec](https://github.com/dfinity/candid/blob/master/spec/Candid.md)
- [Candid Decoder](https://fxa77-fiaaa-aaaae-aaana-cai.raw.ic0.app/explain)
-- [Candid UI Tester](https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.ic0.app)
+- [Candid UI Tester](https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.ic0.app)
\ No newline at end of file
diff --git a/UnityAssets/WebGlBlsCryptography.cs b/UnityAssets/WebGlBlsCryptography.cs
index fd45f157..9a371626 100644
--- a/UnityAssets/WebGlBlsCryptography.cs
+++ b/UnityAssets/WebGlBlsCryptography.cs
@@ -9,19 +9,46 @@
using UnityEngine;
using UnityEngine.Networking;
-#if UNITY_WEBGL
+#if UNITY_WEBGL && !UNITY_EDITOR
public class WebGlBlsCryptography : IBlsCryptography
{
public bool VerifySignature(byte[] publicKey, byte[] messageHash, byte[] signature)
{
- return BrowserBlsLib.VerifySignature(publicKey, messageHash, signature);
+ string publicKeyHex = ToHexString(publicKey);
+ string messageHashHex = ToHexString(messageHash);
+ string signatureHex = ToHexString(signature);
+ return BrowserBlsLib.VerifySignature(publicKeyHex, messageHashHex, signatureHex);
+ }
+
+ public static string ToHexString(byte[] bytes)
+ {
+ char[] stringValue = new char[bytes.Length * 2];
+ int i = 0;
+ foreach (byte b in bytes)
+ {
+ int charIndex = i++ * 2;
+ int quotient = Math.DivRem(b, 16, out int remainder);
+ stringValue[charIndex] = GetChar(quotient);
+ stringValue[charIndex + 1] = GetChar(remainder);
+ }
+
+ return new string(stringValue); // returns: "48656C6C6F20776F726C64" for "Hello world"
+
+ }
+ private static char GetChar(int value)
+ {
+ if (value < 10)
+ {
+ return (char)(value + 48); // 0->9
+ }
+ return (char)(value + 65 - 10); // A->F ASCII
}
}
internal static class BrowserBlsLib
{
[DllImport("__Internal")]
- public static extern bool VerifySignature(byte[] publicKey, byte[] messageHash, byte[] signature);
+ public static extern bool VerifySignature(string publicKeyHex, string messageHashHex, string signatureHex);
}
#endif
diff --git a/samples/Sample.CLI/Program.cs b/samples/Sample.CLI/Program.cs
index 0438db48..1f4989ba 100644
--- a/samples/Sample.CLI/Program.cs
+++ b/samples/Sample.CLI/Program.cs
@@ -15,6 +15,10 @@
using EdjCase.ICP.Agent.Standards.AssetCanister;
using System.Text;
using EdjCase.ICP.Agent.Standards.AssetCanister.Models;
+using EdjCase.ICP.BLS;
+using EdjCase.ICP.Candid.Utilities;
+using EdjCase.ICP.BLS.Models;
+using System.Diagnostics;
public class Program
{
@@ -182,7 +186,7 @@ string filePath
try
{
contentStream = File.OpenRead(filePath);
-
+
await client.UploadAssetChunkedAsync(
key: key,
contentType: contentType,
diff --git a/src/Agent/API.xml b/src/Agent/API.xml
index 8f3cf9d3..d10f6da1 100644
--- a/src/Agent/API.xml
+++ b/src/Agent/API.xml
@@ -4,45 +4,6 @@
EdjCase.ICP.Agent
-
-
- An `IAgent` implementation using HTTP to make requests to the IC
-
-
-
-
- The identity that will be used on each request unless overriden
- This identity can be anonymous
-
-
-
- Optional. Identity to use for each request. If unspecified, will use anonymous identity
- Optional. Bls crypto implementation to validate signatures. If unspecified, will use default implementation
- Optional. Sets the http client to use, otherwise will use the default http client
-
-
- Optional. Identity to use for each request. If unspecified, will use anonymous identity
- Optional. Bls crypto implementation to validate signatures. If unspecified, will use default implementation
- Url to the boundry node to connect to. Defaults to `https://ic0.app/`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
The default http client to use with the built in `HttpClient`
@@ -111,6 +72,45 @@
+
+
+ An `IAgent` implementation using HTTP to make requests to the IC
+
+
+
+
+ The identity that will be used on each request unless overriden
+ This identity can be anonymous
+
+
+
+ Optional. Identity to use for each request. If unspecified, will use anonymous identity
+ Optional. Bls crypto implementation to validate signatures. If unspecified, will use default implementation
+ Optional. Sets the http client to use, otherwise will use the default http client
+
+
+ Optional. Identity to use for each request. If unspecified, will use anonymous identity
+ Optional. Bls crypto implementation to validate signatures. If unspecified, will use default implementation
+ Url to the boundry node to connect to. Defaults to `https://ic0.app/`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
An agent is used to communicate with the Internet Computer with certain protocols that
diff --git a/src/Agent/Agents/HttpAgent.cs b/src/Agent/Agents/HttpAgent.cs
index d406f582..a49d066c 100644
--- a/src/Agent/Agents/HttpAgent.cs
+++ b/src/Agent/Agents/HttpAgent.cs
@@ -48,7 +48,7 @@ public HttpAgent(
{
this.Identity = identity;
this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
- this.bls = bls ?? new WasmBlsCryptography();
+ this.bls = bls ?? new DefaultBlsCryptograhy();
}
/// Optional. Identity to use for each request. If unspecified, will use anonymous identity
@@ -65,7 +65,7 @@ public HttpAgent(
{
BaseAddress = httpBoundryNodeUrl ?? new Uri("https://ic0.app/")
});
- this.bls = bls ?? new WasmBlsCryptography();
+ this.bls = bls ?? new DefaultBlsCryptograhy();
}
diff --git a/src/Agent/README.md b/src/Agent/README.md
index 25637680..1e16ed4f 100644
--- a/src/Agent/README.md
+++ b/src/Agent/README.md
@@ -40,7 +40,7 @@ IAgent agent = new HttpAgent();
// Create Candid arg to send in request
ulong proposalId = 1234;
CandidArg arg = CandidArg.FromCandid(
- CandidTypedValue.FromObject(proposalId) // Conversion can be C# or custom types
+ CandidTypedValue.FromObject(proposalId) // Conversion can be C# or custom types
);
// Make request to IC
@@ -89,19 +89,19 @@ string name = await client.Name();
// Get the balance of a specific account
Account account = new Account
{
- Id = Principal.FromText("")
+ Id = Principal.FromText("")
};
UnboundedUInt balance = await client.BalanceOf(account);
// Transfer tokens from one account to another
TransferArgs transferArgs = new TransferArgs
{
- To = new Account
- {
- Id = Principal.FromText("")
- },
- Amount = 1,
- Memo = ""
+ To = new Account
+ {
+ Id = Principal.FromText("")
+ },
+ Amount = 1,
+ Memo = ""
};
TransferResult transferResult = await client.Transfer(transferArgs);
```
@@ -172,18 +172,7 @@ var delegatedIdentity = new DelegationIdentity(innerIdentity, chain);
Due to how WebGL works by converting C# to JS/WASM using IL2CPP there are a few additional steps to avoid
incompatibilities.
- UnityHttpClient - The .NET `HttpClient` does not work in many cases, so `UnityHttpClient` is added via Unity C# script.
- ```cs
- var client = new UnityHttpClient();
- var agent = new HttpAgent(client);
- ```
-- WebGlBlsCryptography - The BLS signature verification relies on a 3rd party library and due to that library not being directly compatible with the WebGL builds, `WebGlBlsCryptography` needs to be used instead of the default `WasmBlsCryptography` class.
-
- ```cs
- var bls = new WebGlBlsCrytography();
- var agent = new HttpAgent(client, bls: bls);
- ```
- In addition, the `noble-curves.js` file located [HERE](`https://github.com/paulmillr/noble-curves/releases/tag/1.2.0`) needs to be included in the Unity project and referenced by the HTML page
- ```html
-
- ```
- That will load in the JS bls library that will be referenced by the `Bls.jslib` that is included in the ICP.NET unity package
+ ```cs
+ var client = new UnityHttpClient();
+ var agent = new HttpAgent(client);
+ ```
\ No newline at end of file
diff --git a/src/BLS/API.xml b/src/BLS/API.xml
index 00d20025..dabc0304 100644
--- a/src/BLS/API.xml
+++ b/src/BLS/API.xml
@@ -13,6 +13,14 @@
+
+
+ Represents the default implementation of the IBlsCryptography interface for BLS cryptography.
+
+
+
+
+
An interface for all BLS crytography operations
@@ -27,13 +35,5 @@
The signature of the message
True if the signature is valid, otherwise false
-
-
- Class with functions around BLS signatures (ICP flavor only)
-
-
-
-
-
diff --git a/src/BLS/BlsUtil.cs b/src/BLS/BlsUtil.cs
new file mode 100644
index 00000000..2b6e6a23
--- /dev/null
+++ b/src/BLS/BlsUtil.cs
@@ -0,0 +1,132 @@
+using Dirichlet.Numerics;
+using EdjCase.ICP.BLS.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+using Wasmtime;
+
+namespace EdjCase.ICP.BLS
+{
+ internal class BlsUtil
+ {
+ internal static (ulong, ulong) MultiplyAddCarry(ulong a, ulong b, ulong c, ulong carry)
+ {
+ UInt128.Multiply(out UInt128 product, b, c);
+ UInt128 sum = UInt128.Add(UInt128.Add(product, a), carry);
+
+ // Extract the lower and upper 64 bits of the result
+ ulong lower = (ulong)(sum & 0xFFFFFFFFFFFFFFFF);
+ ulong upper = (ulong)((sum >> 64) & 0xFFFFFFFFFFFFFFFF);
+
+ return (lower, upper);
+ }
+
+
+ internal static (ulong sum, ulong carry) AddWithCarry(ulong a, ulong b)
+ {
+ ulong sum = a + b;
+ // If sum is less than either a or b, then it means there was a wraparound, hence a carry.
+ ulong carry = (sum < a || sum < b) ? 1UL : 0UL;
+
+ return (sum, carry);
+ }
+
+
+
+
+ internal static (ulong Value, ulong BorrowOut) SubtractWithBorrow(ulong a, ulong b, ulong borrow)
+ {
+ unchecked
+ {
+ // Interpret any non-zero borrow value as true (1) for subtraction
+ ulong effectiveBorrow = borrow > 0 ? 1UL : 0UL;
+ ulong result = a - b - effectiveBorrow;
+
+ // Determine if a borrow occurred. A borrow is needed if a is less than b,
+ // or if a equals b and an effective borrow is subtracted.
+ ulong borrowOut = (a < b || (a == b && effectiveBorrow > 0)) ? 1UL : 0UL;
+
+ return (result, borrowOut);
+ }
+ }
+
+
+ internal static (ulong, ulong) AddWithCarry(ulong a, ulong b, ulong carry)
+ {
+ // Start with the addition of 'a' and 'b'
+ ulong lower = a + b;
+
+ // If 'lower' is less than either 'a' or 'b', it means an overflow occurred
+ // So, we need to account for this in the upper part
+ ulong overflow = (lower < a || lower < b) ? 1UL : 0;
+
+ // Add the carry to the lower part. If this addition overflows, it will also contribute to the upper part
+ lower += carry;
+ if (lower < carry) // Check for overflow after adding the carry
+ {
+ overflow++;
+ }
+
+ return (lower, overflow);
+ }
+
+ internal static T MillerLoop(
+ T f,
+ Func doublingStep,
+ Func additionStep,
+ Func square,
+ Func conjugate
+ )
+ {
+ bool foundOne = false;
+
+ var values = Enumerable.Range(0, 64)
+ .Reverse()
+ .Select(b => ((Constants.BLS_X >> 1 >> b) & 1) == 1);
+
+ foreach (bool i in values)
+ {
+ if (!foundOne)
+ {
+ foundOne = i;
+ continue;
+ }
+
+ f = doublingStep(f);
+
+ if (i)
+ {
+ f = additionStep(f);
+ }
+ f = square(f);
+ }
+
+ f = doublingStep(f);
+
+ f = conjugate(f);
+ return f;
+
+ }
+
+ public static Fp FromOkmFp(byte[] okm)
+ {
+ Fp F2256 = new(
+ 0x075b_3cd7_c5ce_820f,
+ 0x3ec6_ba62_1c3e_db0b,
+ 0x168a_13d8_2bff_6bce,
+ 0x8766_3c4b_f8c4_49d2,
+ 0x15f3_4c83_ddc8_d830,
+ 0x0f96_28b4_9caa_2e85
+ );
+ var bs = new byte[48];
+ Array.Copy(okm, 0, bs, 16, 32);
+ Fp db = Fp.FromBytes(bs);
+ Array.Copy(okm, 32, bs, 16, 32);
+ Fp da = Fp.FromBytes(bs);
+ return db * F2256 + da;
+ }
+
+ }
+}
diff --git a/src/BLS/Constants.cs b/src/BLS/Constants.cs
index ebcbab8a..c9207ba5 100644
--- a/src/BLS/Constants.cs
+++ b/src/BLS/Constants.cs
@@ -7,16 +7,7 @@ namespace EdjCase.ICP.BLS
{
internal class Constants
{
- public const int BLS12_381 = 5;
+ public const ulong BLS_X = 0xd201_0000_0001_0000;
- public const int FR_UNIT_SIZE = 4;
- public const int FP_UNIT_SIZE = 6;
- public const int FP_SIZE = FP_UNIT_SIZE * 8;
-
- public const int COMPILED_TIME_VAR = FR_UNIT_SIZE * 10 + FP_UNIT_SIZE;
-
-
- public const int PUBLICKEY_SIZE = 288;
- public const int SIGNATURE_SIZE = 144;
}
}
diff --git a/src/BLS/DefaultBlsCryptograhy.cs b/src/BLS/DefaultBlsCryptograhy.cs
new file mode 100644
index 00000000..c182e8aa
--- /dev/null
+++ b/src/BLS/DefaultBlsCryptograhy.cs
@@ -0,0 +1,219 @@
+using EdjCase.ICP.BLS.Models;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace EdjCase.ICP.BLS
+{
+
+ ///
+ /// Represents the default implementation of the IBlsCryptography interface for BLS cryptography.
+ ///
+ public class DefaultBlsCryptograhy : IBlsCryptography
+ {
+ internal static readonly byte[] DomainSeperator;
+
+ static DefaultBlsCryptograhy()
+ {
+ DomainSeperator = Encoding.UTF8.GetBytes("BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_");
+ }
+
+ ///
+ public bool VerifySignature(byte[] publicKey, byte[] messageHash, byte[] signature)
+ {
+ G1Affine sig = G1Affine.FromCompressed(signature);
+ G2Prepared g2Gen = G2Affine.Generator().Neg().ToProjective().ToPrepared();
+
+ G1Affine msg = G1Projective.HashToCurve(messageHash, DomainSeperator).ToAffine();
+ G2Prepared pk = G2Affine.FromCompressed(publicKey).ToProjective().ToPrepared();
+ (G1Affine, G2Prepared)[] pairs = new[]
+ {
+ (sig, g2Gen),
+ (msg, pk)
+ };
+ return this.VerifyInternal(pairs);
+ }
+
+ private bool VerifyInternal(
+ (G1Affine G1, G2Prepared G2)[] pairs
+ )
+ {
+ if (!pairs.Any())
+ {
+ return false;
+ }
+
+ // Zero keys should always fail
+ if (pairs.Any(p => p.G1.IsIdentity()))
+ {
+ return false;
+ }
+
+ int index = 0;
+ Fp12 Step(Fp12 f)
+ {
+ foreach ((G1Affine g1, G2Prepared g2) in pairs)
+ {
+ bool eitherIdentity = g1.IsIdentity() || g2.IsInfinity;
+ if (!eitherIdentity)
+ {
+ (Fp2 c0, Fp2 c1, Fp2 c2) = g2.Coefficients[index];
+
+ c0 = new Fp2(c0.C0 * g1.Y, c0.C1 * g1.Y);
+ c1 = new Fp2(c1.C0 * g1.X, c1.C1 * g1.X);
+
+ f = f.MultiplyBy014(c2, c1, c0);
+ }
+ }
+ index += 1;
+ return f;
+ }
+
+ Fp12 millerLoopValue = BlsUtil.MillerLoop(
+ Fp12.One(),
+ Step,
+ Step,
+ (f) => f.Square(),
+ (f) => f.Conjugate()
+ );
+
+
+ return FinalExponentiation(millerLoopValue).Equals(Fp12.One());
+ }
+
+
+ private static Fp12 FinalExponentiation(Fp12 f)
+ {
+ Fp12 t0 = f
+ .FrobeniusMap()
+ .FrobeniusMap()
+ .FrobeniusMap()
+ .FrobeniusMap()
+ .FrobeniusMap()
+ .FrobeniusMap();
+ Fp12 t1 = f.Invert() ?? throw new Exception("Failed to invert");
+ Fp12 t2 = t0.Multiply(t1);
+ t1 = t2;
+ t2 = t2.FrobeniusMap().FrobeniusMap();
+ t2 = t2.Multiply(t1);
+ t1 = CyclotonmicSquare(t2).Conjugate();
+ Fp12 t3 = CyclotomicExp(t2);
+ Fp12 t4 = CyclotonmicSquare(t3);
+ Fp12 t5 = t1.Multiply(t3);
+ t1 = CyclotomicExp(t5);
+ t0 = CyclotomicExp(t1);
+ Fp12 t6 = CyclotomicExp(t0);
+ t6 = t6.Multiply(t4);
+ t4 = CyclotomicExp(t6);
+ t5 = t5.Conjugate();
+ t4 = t4.Multiply(t5).Multiply(t2);
+ t5 = t2.Conjugate();
+ t1 = t1.Multiply(t2);
+ t1 = t1.FrobeniusMap().FrobeniusMap().FrobeniusMap();
+ t6 = t6.Multiply(t5);
+ t6 = t6.FrobeniusMap();
+ t3 = t3.Multiply(t0);
+ t3 = t3.FrobeniusMap().FrobeniusMap();
+ t3 = t3.Multiply(t1);
+ t3 = t3.Multiply(t6);
+ f = t3.Multiply(t4);
+ return f;
+ }
+
+ private static (Fp2, Fp2) Fp4Square(Fp2 a, Fp2 b)
+ {
+ Fp2 t0 = a.Square();
+ Fp2 t1 = b.Square();
+ Fp2 t2 = t1.MultiplyByNonresidue();
+ Fp2 c0 = t2.Add(t0);
+ t2 = a.Add(b);
+ t2 = t2.Square();
+ t2 = t2.Subtract(t0);
+ Fp2 c1 = t2.Subtract(t1);
+
+ return (c0, c1);
+ }
+
+ private static Fp12 CyclotonmicSquare(Fp12 f)
+ {
+ Fp2 z0 = f.C0.C0;
+ Fp2 z4 = f.C0.C1;
+ Fp2 z3 = f.C0.C2;
+ Fp2 z2 = f.C1.C0;
+ Fp2 z1 = f.C1.C1;
+ Fp2 z5 = f.C1.C2;
+
+ (Fp2 t0, Fp2 t1) = Fp4Square(z0, z1);
+
+ // For A
+ z0 = t0.Subtract(z0);
+ z0 = z0.Add(z0).Add(t0);
+
+ z1 = t1.Add(z1);
+ z1 = z1.Add(z1).Add(t1);
+
+ (t0, t1) = Fp4Square(z2, z3);
+ (Fp2 t2, Fp2 t3) = Fp4Square(z4, z5);
+
+ // For C
+ z4 = t0.Subtract(z4);
+ z4 = z4.Add(z4).Add(t0);
+
+ z5 = t1.Add(z5);
+ z5 = z5.Add(z5).Add(t1);
+
+ // For B
+ t0 = t3.MultiplyByNonresidue();
+ z2 = t0.Add(z2);
+ z2 = z2.Add(z2).Add(t0);
+
+ z3 = t2.Subtract(z3);
+ z3 = z3.Add(z3).Add(t2);
+ return new Fp12(new Fp6(z0, z4, z3), new Fp6(z2, z1, z5));
+
+ }
+
+ private static Fp12 CyclotomicExp(Fp12 f)
+ {
+ ulong x = Constants.BLS_X;
+ Fp12 tmp = Fp12.One();
+ bool foundOne = false;
+ for (int i = 63; i >= 0; i--)
+ {
+ bool b = ((x >> i) & 1) == 1;
+ if (foundOne)
+ {
+ tmp = CyclotonmicSquare(tmp);
+ }
+ else
+ {
+ foundOne = b;
+ }
+
+ if (b)
+ {
+ tmp = tmp.Multiply(f);
+ }
+ }
+
+ return tmp.Conjugate();
+ }
+
+ class ByteArrayComparer : IEqualityComparer
+ {
+ public bool Equals(byte[] x, byte[] y)
+ {
+ return x.SequenceEqual(y);
+ }
+
+ public int GetHashCode(byte[] obj)
+ {
+ return obj.GetHashCode();
+ }
+ }
+ }
+
+}
diff --git a/src/BLS/EdjCase.ICP.BLS.csproj b/src/BLS/EdjCase.ICP.BLS.csproj
index 55886609..689b2648 100644
--- a/src/BLS/EdjCase.ICP.BLS.csproj
+++ b/src/BLS/EdjCase.ICP.BLS.csproj
@@ -26,10 +26,6 @@
-
-
-
-
diff --git a/src/BLS/Models/Expander.cs b/src/BLS/Models/Expander.cs
new file mode 100644
index 00000000..573fa88c
--- /dev/null
+++ b/src/BLS/Models/Expander.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Security.Cryptography;
+
+namespace EdjCase.ICP.BLS.Models
+{
+ internal class Expander
+ {
+ public byte[] Dst { get; }
+ public byte[] B0 { get; }
+ public byte[] Bi { get; private set; }
+ public byte I { get; private set; }
+ public int BOffs { get; private set; }
+ public int Remain { get; private set; }
+
+ private static readonly SHA256 sHA256 = SHA256.Create();
+
+ public Expander(byte[] dst, byte[] b0, byte[] bi, byte i, int bOffs, int remain)
+ {
+ this.Dst = dst;
+ this.B0 = b0;
+ this.Bi = bi;
+ this.I = i;
+ this.BOffs = bOffs;
+ this.Remain = remain;
+ }
+
+ public static Expander Create(byte[] message, byte[] dst, int byteLength, int hashSize)
+ {
+ int ell = (byteLength + hashSize - 1) / hashSize;
+ if (ell > 255)
+ {
+ throw new ArgumentException("Invalid ExpandMsgXmd usage: ell > 255");
+ }
+ if (dst.Length > 255)
+ {
+ throw new NotImplementedException();
+ }
+
+ byte[] b0 = sHA256.ComputeHash(
+ new byte[64]
+ .Concat(message)
+ .Concat(ToU16Bytes(byteLength))
+ .Concat(new byte[] { 0 })
+ .Concat(dst)
+ .Concat(ToU8Bytes(dst.Length))
+ .ToArray()
+ );
+ byte[] bi = sHA256.ComputeHash(
+ b0
+ .Concat(new byte[] { 1 })
+ .Concat(dst)
+ .Concat(ToU8Bytes(dst.Length))
+ .ToArray()
+ );
+
+ return new Expander(dst, b0, bi, 2, 0, byteLength);
+ }
+
+ private static byte[] ToU16Bytes(int value)
+ {
+ if (value > ushort.MaxValue)
+ {
+ throw new ArgumentException("Max value is " + ushort.MaxValue);
+ }
+ byte[] bytes = BitConverter.GetBytes((ushort)value);
+
+ if (BitConverter.IsLittleEndian)
+ {
+ Array.Reverse(bytes); // Convert to big-endian if system is little-endian
+ }
+ return bytes;
+ }
+ private static byte[] ToU8Bytes(int value)
+ {
+ if (value > byte.MaxValue)
+ {
+ throw new ArgumentException("Max value is " + byte.MaxValue);
+ }
+ return new byte[] { (byte)value };
+ }
+
+ public byte[] ReadInto(int outputLength, int hashSize)
+ {
+ byte[] output = new byte[outputLength];
+ int readLen = Math.Min(this.Remain, outputLength);
+ int offs = 0;
+ while (offs < readLen)
+ {
+ int bOffs = this.BOffs;
+ int copyLen = hashSize - bOffs;
+ if (copyLen > 0)
+ {
+ copyLen = Math.Min(readLen - offs, copyLen);
+ Array.Copy(this.Bi, bOffs, output, offs, copyLen);
+ offs += copyLen;
+ this.BOffs = bOffs + copyLen;
+ }
+ else
+ {
+ byte[] bPrevXor = new byte[this.B0.Length];
+ Array.Copy(this.B0, bPrevXor, this.B0.Length);
+ for (int j = 0; j < hashSize; j++)
+ {
+ bPrevXor[j] ^= this.Bi[j];
+ }
+ this.Bi = sHA256.ComputeHash(
+ bPrevXor
+ .Concat(new[] { this.I })
+ .Concat(this.Dst)
+ .Concat(ToU8Bytes(this.Dst.Length))
+ .ToArray()
+ );
+ this.BOffs = 0;
+ this.I += 1;
+ }
+ }
+ this.Remain -= readLen;
+ return output;
+ }
+ }
+}
diff --git a/src/BLS/Models/Fp.cs b/src/BLS/Models/Fp.cs
new file mode 100644
index 00000000..d39cd34f
--- /dev/null
+++ b/src/BLS/Models/Fp.cs
@@ -0,0 +1,663 @@
+using System;
+using System.Buffers.Binary;
+using System.Diagnostics;
+using System.Linq;
+
+namespace EdjCase.ICP.BLS.Models
+{
+ internal struct Fp
+ {
+ public ulong V0;
+ public ulong V1;
+ public ulong V2;
+ public ulong V3;
+ public ulong V4;
+ public ulong V5;
+ public ulong[] ToArray() => new ulong[] {this.V0, this.V1, this.V2, this.V3, this.V4, this.V5};
+
+ // Constants
+ public static readonly ulong[] MODULUS = new ulong[] {
+ 0xb9fe_ffff_ffff_aaab,
+ 0x1eab_fffe_b153_ffff,
+ 0x6730_d2a0_f6b0_f624,
+ 0x6477_4b84_f385_12bf,
+ 0x4b1b_a7b6_434b_acd7,
+ 0x1a01_11ea_397f_e69a
+ };
+ public static readonly ulong INV = 0x89f3_fffc_fffc_fffd;
+
+ // Static readonly instances of Fp for R, R2, and R3
+ public static readonly Fp R;
+ public static readonly Fp R2;
+ public static readonly Fp R3;
+ public static readonly Fp B;
+
+ static Fp()
+ {
+ R = new Fp(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 = new Fp(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 = new Fp(0xed48_ac6b_d94c_a1e0, 0x315f_831e_03a7_adf8, 0x9a53_352a_615e_29dd, 0x34c0_4e5e_921e_1761, 0x2512_d435_6572_4728, 0x0aa6_3460_9175_5d4d);
+
+ B = new Fp(
+ 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 Fp(ulong v0, ulong v1, ulong v2, ulong v3, ulong v4, ulong v5)
+ {
+ this.V0 = v0;
+ this.V1 = v1;
+ this.V2 = v2;
+ this.V3 = v3;
+ this.V4 = v4;
+ this.V5 = v5;
+ }
+
+ public static Fp Zero()
+ {
+ return new Fp(0, 0, 0, 0, 0, 0);
+ }
+
+ public static Fp Default()
+ {
+ return Fp.Zero();
+ }
+
+ internal static Fp One()
+ {
+ return R.Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Fp fp)
+ {
+ return this.Equals(fp);
+ }
+ return false;
+ }
+
+ public bool Equals(Fp other)
+ {
+ return this.V0 == other.V0
+ && this.V1 == other.V1
+ && this.V2 == other.V2
+ && this.V3 == other.V3
+ && this.V4 == other.V4
+ && this.V5 == other.V5;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(this.V0, this.V1, this.V2, this.V3, this.V4, this.V5);
+ }
+
+ public bool IsZero()
+ {
+ return this.Equals(Fp.Zero());
+ }
+
+ public Fp Clone()
+ {
+ return new Fp(
+ this.V0,
+ this.V1,
+ this.V2,
+ this.V3,
+ this.V4,
+ this.V5
+ );
+ }
+
+ public byte[] ToBytes()
+ {
+ // Turn into canonical form by computing
+ // (a.R) / R = a
+ Fp tmp = Fp.MontgomeryReduce(
+ this.V0,
+ this.V1,
+ this.V2,
+ this.V3,
+ this.V4,
+ this.V5,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ );
+ byte[] bytes = new byte[48];
+
+ // Write bytes directly into the array, using a helper method to respect endianness.
+ WriteBytes(bytes, 0, tmp.V5);
+ WriteBytes(bytes, 8, tmp.V4);
+ WriteBytes(bytes, 16, tmp.V3);
+ WriteBytes(bytes, 24, tmp.V2);
+ WriteBytes(bytes, 32, tmp.V1);
+ WriteBytes(bytes, 40, tmp.V0);
+
+ return bytes;
+ }
+
+ private static void WriteBytes(byte[] target, int start, ulong value)
+ {
+ BinaryPrimitives.WriteUInt64BigEndian(target.AsSpan(start, 8), value);
+ }
+
+
+ public Fp Add(Fp rhs)
+ {
+ ulong carry = 0;
+
+ (ulong v0, carry) = BlsUtil.AddWithCarry(this.V0, rhs.V0, carry);
+ (ulong v1, carry) = BlsUtil.AddWithCarry(this.V1, rhs.V1, carry);
+ (ulong v2, carry) = BlsUtil.AddWithCarry(this.V2, rhs.V2, carry);
+ (ulong v3, carry) = BlsUtil.AddWithCarry(this.V3, rhs.V3, carry);
+ (ulong v4, carry) = BlsUtil.AddWithCarry(this.V4, rhs.V4, carry);
+ (ulong v5, carry) = BlsUtil.AddWithCarry(this.V5, rhs.V5, carry);
+
+ // Attempt to subtract the modulus, to ensure the value
+ // is smaller than the modulus.
+ return new Fp(
+ v0,
+ v1,
+ v2,
+ v3,
+ v4,
+ v5
+ )
+ .SubtractP();
+ }
+
+ public static Fp operator +(Fp a, Fp b)
+ {
+ return a.Add(b);
+ }
+
+ public Fp Subtract(Fp rhs)
+ {
+ return rhs.Neg().Add(this);
+ }
+
+ public static Fp operator -(Fp a, Fp b)
+ {
+ return a.Subtract(b);
+ }
+
+ public Fp Multiply(Fp rhs)
+ {
+ (ulong t0, ulong carry) = BlsUtil.MultiplyAddCarry(0, this.V0, rhs.V0, 0);
+ (ulong t1, carry) = BlsUtil.MultiplyAddCarry(0, this.V0, rhs.V1, carry);
+ (ulong t2, carry) = BlsUtil.MultiplyAddCarry(0, this.V0, rhs.V2, carry);
+ (ulong t3, carry) = BlsUtil.MultiplyAddCarry(0, this.V0, rhs.V3, carry);
+ (ulong t4, carry) = BlsUtil.MultiplyAddCarry(0, this.V0, rhs.V4, carry);
+ (ulong t5, ulong t6) = BlsUtil.MultiplyAddCarry(0, this.V0, rhs.V5, carry);
+
+ (t1, carry) = BlsUtil.MultiplyAddCarry(t1, this.V1, rhs.V0, 0);
+ (t2, carry) = BlsUtil.MultiplyAddCarry(t2, this.V1, rhs.V1, carry);
+ (t3, carry) = BlsUtil.MultiplyAddCarry(t3, this.V1, rhs.V2, carry);
+ (t4, carry) = BlsUtil.MultiplyAddCarry(t4, this.V1, rhs.V3, carry);
+ (t5, carry) = BlsUtil.MultiplyAddCarry(t5, this.V1, rhs.V4, carry);
+ (t6, ulong t7) = BlsUtil.MultiplyAddCarry(t6, this.V1, rhs.V5, carry);
+
+ (t2, carry) = BlsUtil.MultiplyAddCarry(t2, this.V2, rhs.V0, 0);
+ (t3, carry) = BlsUtil.MultiplyAddCarry(t3, this.V2, rhs.V1, carry);
+ (t4, carry) = BlsUtil.MultiplyAddCarry(t4, this.V2, rhs.V2, carry);
+ (t5, carry) = BlsUtil.MultiplyAddCarry(t5, this.V2, rhs.V3, carry);
+ (t6, carry) = BlsUtil.MultiplyAddCarry(t6, this.V2, rhs.V4, carry);
+ (t7, ulong t8) = BlsUtil.MultiplyAddCarry(t7, this.V2, rhs.V5, carry);
+
+ (t3, carry) = BlsUtil.MultiplyAddCarry(t3, this.V3, rhs.V0, 0);
+ (t4, carry) = BlsUtil.MultiplyAddCarry(t4, this.V3, rhs.V1, carry);
+ (t5, carry) = BlsUtil.MultiplyAddCarry(t5, this.V3, rhs.V2, carry);
+ (t6, carry) = BlsUtil.MultiplyAddCarry(t6, this.V3, rhs.V3, carry);
+ (t7, carry) = BlsUtil.MultiplyAddCarry(t7, this.V3, rhs.V4, carry);
+ (t8, ulong t9) = BlsUtil.MultiplyAddCarry(t8, this.V3, rhs.V5, carry);
+
+ (t4, carry) = BlsUtil.MultiplyAddCarry(t4, this.V4, rhs.V0, 0);
+ (t5, carry) = BlsUtil.MultiplyAddCarry(t5, this.V4, rhs.V1, carry);
+ (t6, carry) = BlsUtil.MultiplyAddCarry(t6, this.V4, rhs.V2, carry);
+ (t7, carry) = BlsUtil.MultiplyAddCarry(t7, this.V4, rhs.V3, carry);
+ (t8, carry) = BlsUtil.MultiplyAddCarry(t8, this.V4, rhs.V4, carry);
+ (t9, ulong t10) = BlsUtil.MultiplyAddCarry(t9, this.V4, rhs.V5, carry);
+
+ (t5, carry) = BlsUtil.MultiplyAddCarry(t5, this.V5, rhs.V0, 0);
+ (t6, carry) = BlsUtil.MultiplyAddCarry(t6, this.V5, rhs.V1, carry);
+ (t7, carry) = BlsUtil.MultiplyAddCarry(t7, this.V5, rhs.V2, carry);
+ (t8, carry) = BlsUtil.MultiplyAddCarry(t8, this.V5, rhs.V3, carry);
+ (t9, carry) = BlsUtil.MultiplyAddCarry(t9, this.V5, rhs.V4, carry);
+ (t10, ulong t11) = BlsUtil.MultiplyAddCarry(t10, this.V5, rhs.V5, carry);
+
+ return MontgomeryReduce(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11);
+ }
+
+ public static Fp operator *(Fp a, Fp b)
+ {
+ return a.Multiply(b);
+ }
+
+
+ public Fp Square()
+ {
+ // Initial declarations
+ ulong t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11;
+ ulong carry;
+
+ // Operations
+ (t1, carry) = BlsUtil.MultiplyAddCarry(0, this.V0, this.V1, 0);
+ (t2, carry) = BlsUtil.MultiplyAddCarry(0, this.V0, this.V2, carry);
+ (t3, carry) = BlsUtil.MultiplyAddCarry(0, this.V0, this.V3, carry);
+ (t4, carry) = BlsUtil.MultiplyAddCarry(0, this.V0, this.V4, carry);
+ (t5, t6) = BlsUtil.MultiplyAddCarry(0, this.V0, this.V5, carry);
+
+ (t3, carry) = BlsUtil.MultiplyAddCarry(t3, this.V1, this.V2, 0);
+ (t4, carry) = BlsUtil.MultiplyAddCarry(t4, this.V1, this.V3, carry);
+ (t5, carry) = BlsUtil.MultiplyAddCarry(t5, this.V1, this.V4, carry);
+ (t6, t7) = BlsUtil.MultiplyAddCarry(t6, this.V1, this.V5, carry);
+
+ (t5, carry) = BlsUtil.MultiplyAddCarry(t5, this.V2, this.V3, 0);
+ (t6, carry) = BlsUtil.MultiplyAddCarry(t6, this.V2, this.V4, carry);
+ (t7, t8) = BlsUtil.MultiplyAddCarry(t7, this.V2, this.V5, carry);
+
+ (t7, carry) = BlsUtil.MultiplyAddCarry(t7, this.V3, this.V4, 0);
+ (t8, t9) = BlsUtil.MultiplyAddCarry(t8, this.V3, this.V5, carry);
+
+ (t9, t10) = BlsUtil.MultiplyAddCarry(t9, this.V4, this.V5, 0);
+
+ // Bit shifting operations
+ 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 = t1 << 1;
+
+ // Final operations with carry management
+ ulong t0;
+ (t0, carry) = BlsUtil.MultiplyAddCarry(0, this.V0, this.V0, 0);
+ (t1, carry) = BlsUtil.AddWithCarry(t1, 0, carry);
+ (t2, carry) = BlsUtil.MultiplyAddCarry(t2, this.V1, this.V1, carry);
+ (t3, carry) = BlsUtil.AddWithCarry(t3, 0, carry);
+ (t4, carry) = BlsUtil.MultiplyAddCarry(t4, this.V2, this.V2, carry);
+ (t5, carry) = BlsUtil.AddWithCarry(t5, 0, carry);
+ (t6, carry) = BlsUtil.MultiplyAddCarry(t6, this.V3, this.V3, carry);
+ (t7, carry) = BlsUtil.AddWithCarry(t7, 0, carry);
+ (t8, carry) = BlsUtil.MultiplyAddCarry(t8, this.V4, this.V4, carry);
+ (t9, carry) = BlsUtil.AddWithCarry(t9, 0, carry);
+ (t10, carry) = BlsUtil.MultiplyAddCarry(t10, this.V5, this.V5, carry);
+ (t11, _) = BlsUtil.AddWithCarry(t11, 0, carry);
+
+ // Return the final result
+ return MontgomeryReduce(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11);
+ }
+
+
+ public Fp SubtractP()
+ {
+ (ulong r0, ulong borrow) = BlsUtil.SubtractWithBorrow(this.V0, MODULUS[0], 0);
+ (ulong r1, borrow) = BlsUtil.SubtractWithBorrow(this.V1, MODULUS[1], borrow);
+ (ulong r2, borrow) = BlsUtil.SubtractWithBorrow(this.V2, MODULUS[2], borrow);
+ (ulong r3, borrow) = BlsUtil.SubtractWithBorrow(this.V3, MODULUS[3], borrow);
+ (ulong r4, borrow) = BlsUtil.SubtractWithBorrow(this.V4, MODULUS[4], borrow);
+ (ulong r5, borrow) = BlsUtil.SubtractWithBorrow(this.V5, MODULUS[5], borrow);
+
+ // If underflow occurred on the final limb, borrow = 0xfff...fff, otherwise
+ // borrow = 0x000...000. Thus, we use it as a mask!
+ ulong mask = borrow == 0 ? ulong.MaxValue : 0;
+
+ r0 = (this.V0 & ~mask) | (r0 & mask);
+ r1 = (this.V1 & ~mask) | (r1 & mask);
+ r2 = (this.V2 & ~mask) | (r2 & mask);
+ r3 = (this.V3 & ~mask) | (r3 & mask);
+ r4 = (this.V4 & ~mask) | (r4 & mask);
+ r5 = (this.V5 & ~mask) | (r5 & mask);
+
+ return new Fp(r0, r1, r2, r3, r4, r5);
+ }
+
+ public Fp Neg()
+ {
+ (ulong d0, ulong borrow) = BlsUtil.SubtractWithBorrow(MODULUS[0], this.V0, 0);
+ (ulong d1, borrow) = BlsUtil.SubtractWithBorrow(MODULUS[1], this.V1, borrow);
+ (ulong d2, borrow) = BlsUtil.SubtractWithBorrow(MODULUS[2], this.V2, borrow);
+ (ulong d3, borrow) = BlsUtil.SubtractWithBorrow(MODULUS[3], this.V3, borrow);
+ (ulong d4, borrow) = BlsUtil.SubtractWithBorrow(MODULUS[4], this.V4, borrow);
+ (ulong d5, _) = BlsUtil.SubtractWithBorrow(MODULUS[5], this.V5, borrow);
+
+ // Let's use a mask if `self` was zero, which would mean
+ // the result of the subtraction is p.
+ ulong mask = ((this.V0 | this.V1 | this.V2 | this.V3 | this.V4 | this.V5) == 0)
+ ? 0
+ : ulong.MaxValue;
+
+ return new Fp(
+ d0 & mask,
+ d1 & mask,
+ d2 & mask,
+ d3 & mask,
+ d4 & mask,
+ d5 & mask
+ );
+ }
+
+
+ public static Fp FromBytes(byte[] bytes)
+ {
+ if (bytes.Length != 48)
+ throw new ArgumentException("Byte array must be exactly 48 bytes long.");
+
+ ulong[] value = new ulong[6];
+ for (int i = 0; i < 6; i++)
+ {
+ // Extract 8 bytes for each ulong, considering the position in the array
+ byte[] ulongBytes = new byte[8];
+ Array.Copy(bytes, i * 8, ulongBytes, 0, 8);
+
+ // Check if the system is little-endian; reverse the array to ensure big-endian order
+ if (BitConverter.IsLittleEndian)
+ {
+ Array.Reverse(ulongBytes);
+ }
+
+ // Convert bytes to ulong considering they are now in big-endian format
+ value[5 - i] = BitConverter.ToUInt64(ulongBytes, 0);
+ }
+
+ ulong borrow = 0;
+ for (int i = 0; i < 6; i++)
+ {
+ (_, borrow) = BlsUtil.SubtractWithBorrow(value[i], MODULUS[i], borrow);
+ }
+ bool isValid = (borrow & 1) == 1;
+
+ if (!isValid)
+ throw new ArgumentException("The provided bytes represent a value that is not within the valid range of the field.");
+
+ Fp fp = new Fp(value[0], value[1], value[2], value[3], value[4], value[5]);
+ fp *= R2;
+ return fp;
+ }
+
+
+ public static Fp MontgomeryReduce(
+ ulong t0,
+ ulong t1,
+ ulong t2,
+ ulong t3,
+ ulong t4,
+ ulong t5,
+ ulong t6,
+ ulong t7,
+ ulong t8,
+ ulong t9,
+ ulong t10,
+ ulong t11
+ )
+ {
+ ulong k = t0 * INV;
+ (ulong _, ulong carry) = BlsUtil.MultiplyAddCarry(t0, k, MODULUS[0], 0);
+ (ulong r1, carry) = BlsUtil.MultiplyAddCarry(t1, k, MODULUS[1], carry);
+ (ulong r2, carry) = BlsUtil.MultiplyAddCarry(t2, k, MODULUS[2], carry);
+ (ulong r3, carry) = BlsUtil.MultiplyAddCarry(t3, k, MODULUS[3], carry);
+ (ulong r4, carry) = BlsUtil.MultiplyAddCarry(t4, k, MODULUS[4], carry);
+ (ulong r5, carry) = BlsUtil.MultiplyAddCarry(t5, k, MODULUS[5], carry);
+ (ulong r6, ulong r7) = BlsUtil.AddWithCarry(t6, 0, carry);
+
+ k = r1 * INV;
+ (_, carry) = BlsUtil.MultiplyAddCarry(r1, k, MODULUS[0], 0);
+ (r2, carry) = BlsUtil.MultiplyAddCarry(r2, k, MODULUS[1], carry);
+ (r3, carry) = BlsUtil.MultiplyAddCarry(r3, k, MODULUS[2], carry);
+ (r4, carry) = BlsUtil.MultiplyAddCarry(r4, k, MODULUS[3], carry);
+ (r5, carry) = BlsUtil.MultiplyAddCarry(r5, k, MODULUS[4], carry);
+ (r6, carry) = BlsUtil.MultiplyAddCarry(r6, k, MODULUS[5], carry);
+ (r7, ulong r8) = BlsUtil.AddWithCarry(t7, r7, carry);
+
+ k = r2 * INV;
+ (_, carry) = BlsUtil.MultiplyAddCarry(r2, k, MODULUS[0], 0);
+ (r3, carry) = BlsUtil.MultiplyAddCarry(r3, k, MODULUS[1], carry);
+ (r4, carry) = BlsUtil.MultiplyAddCarry(r4, k, MODULUS[2], carry);
+ (r5, carry) = BlsUtil.MultiplyAddCarry(r5, k, MODULUS[3], carry);
+ (r6, carry) = BlsUtil.MultiplyAddCarry(r6, k, MODULUS[4], carry);
+ (r7, carry) = BlsUtil.MultiplyAddCarry(r7, k, MODULUS[5], carry);
+ (r8, ulong r9) = BlsUtil.AddWithCarry(t8, r8, carry);
+
+ k = r3 * INV;
+ (_, carry) = BlsUtil.MultiplyAddCarry(r3, k, MODULUS[0], 0);
+ (r4, carry) = BlsUtil.MultiplyAddCarry(r4, k, MODULUS[1], carry);
+ (r5, carry) = BlsUtil.MultiplyAddCarry(r5, k, MODULUS[2], carry);
+ (r6, carry) = BlsUtil.MultiplyAddCarry(r6, k, MODULUS[3], carry);
+ (r7, carry) = BlsUtil.MultiplyAddCarry(r7, k, MODULUS[4], carry);
+ (r8, carry) = BlsUtil.MultiplyAddCarry(r8, k, MODULUS[5], carry);
+ (r9, ulong r10) = BlsUtil.AddWithCarry(t9, r9, carry);
+
+ k = r4 * INV;
+ (_, carry) = BlsUtil.MultiplyAddCarry(r4, k, MODULUS[0], 0);
+ (r5, carry) = BlsUtil.MultiplyAddCarry(r5, k, MODULUS[1], carry);
+ (r6, carry) = BlsUtil.MultiplyAddCarry(r6, k, MODULUS[2], carry);
+ (r7, carry) = BlsUtil.MultiplyAddCarry(r7, k, MODULUS[3], carry);
+ (r8, carry) = BlsUtil.MultiplyAddCarry(r8, k, MODULUS[4], carry);
+ (r9, carry) = BlsUtil.MultiplyAddCarry(r9, k, MODULUS[5], carry);
+ (r10, ulong r11) = BlsUtil.AddWithCarry(t10, r10, carry);
+
+ k = r5 * INV;
+ (_, carry) = BlsUtil.MultiplyAddCarry(r5, k, MODULUS[0], 0);
+ (r6, carry) = BlsUtil.MultiplyAddCarry(r6, k, MODULUS[1], carry);
+ (r7, carry) = BlsUtil.MultiplyAddCarry(r7, k, MODULUS[2], carry);
+ (r8, carry) = BlsUtil.MultiplyAddCarry(r8, k, MODULUS[3], carry);
+ (r9, carry) = BlsUtil.MultiplyAddCarry(r9, k, MODULUS[4], carry);
+ (r10, carry) = BlsUtil.MultiplyAddCarry(r10, k, MODULUS[5], carry);
+ (r11, _) = BlsUtil.AddWithCarry(t11, r11, carry);
+
+ return new Fp(r6, r7, r8, r9, r10, r11).SubtractP();
+ }
+
+ public Fp SquareRoot()
+ {
+ Fp by = new(
+ 0xee7f_bfff_ffff_eaab,
+ 0x07aa_ffff_ac54_ffff,
+ 0xd9cc_34a8_3dac_3d89,
+ 0xd91d_d2e1_3ce1_44af,
+ 0x92c6_e9ed_90d2_eb35,
+ 0x0680_447a_8e5f_f9a6
+ );
+ return this.Pow(by);
+ }
+
+ public Fp Pow(Fp by)
+ {
+ Fp res = Fp.One();
+ for (int i = 5; i >= 0; i--)
+ {
+ ulong v = by.GetValueByIndex(i);
+ for (int j = 63; j >= 0; j--)
+ {
+ res = res.Square();
+ if (((v >> j) & 1) == 1)
+ {
+ res *= this;
+ }
+ }
+ }
+ return res;
+ }
+
+ public Fp? Invert()
+ {
+ Fp t = this.Pow(new Fp(
+ 0xb9fe_ffff_ffff_aaa9,
+ 0x1eab_fffe_b153_ffff,
+ 0x6730_d2a0_f6b0_f624,
+ 0x6477_4b84_f385_12bf,
+ 0x4b1b_a7b6_434b_acd7,
+ 0x1a01_11ea_397f_e69a
+ ));
+ if (this.IsZero())
+ {
+ return null;
+ }
+ return t;
+ }
+
+ public bool LexicographicallyLargest()
+ {
+ Fp tmp = Fp.MontgomeryReduce(
+ this.V0,
+ this.V1,
+ this.V2,
+ this.V3,
+ this.V4,
+ this.V5,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ );
+ (_, ulong borrow) = BlsUtil.SubtractWithBorrow(tmp.V0, 0xdcff_7fff_ffff_d556, 0);
+ (_, borrow) = BlsUtil.SubtractWithBorrow(tmp.V1, 0x0f55_ffff_58a9_ffff, borrow);
+ (_, borrow) = BlsUtil.SubtractWithBorrow(tmp.V2, 0xb398_6950_7b58_7b12, borrow);
+ (_, borrow) = BlsUtil.SubtractWithBorrow(tmp.V3, 0xb23b_a5c2_79c2_895f, borrow);
+ (_, borrow) = BlsUtil.SubtractWithBorrow(tmp.V4, 0x258d_d3db_21a5_d66b, borrow);
+ (_, borrow) = BlsUtil.SubtractWithBorrow(tmp.V5, 0x0d00_88f5_1cbf_f34d, borrow);
+ return (borrow & 1) != 1;
+ }
+
+ public static Fp SumOfProducts(Fp[] a, Fp[] b)
+ {
+ if (a.Length != b.Length)
+ {
+ throw new ArgumentException("Arrays must be of the same length.");
+ };
+ // Iterate Range from 0 -> 6
+
+ (ulong u0, ulong u1, ulong u2, ulong u3, ulong u4, ulong u5) = Enumerable.Range(0, 6)
+ .Aggregate((0ul, 0ul, 0ul, 0ul, 0ul, 0ul), (acc, j) =>
+ {
+ (ulong u0, ulong u1, ulong u2, ulong u3, ulong u4, ulong u5) = acc;
+ (ulong t0, ulong t1, ulong t2, ulong t3, ulong t4, ulong t5, ulong t6) = a
+ .Zip(b, (a, b) => (a, b))
+ .Aggregate(
+ (u0, u1, u2, u3, u4, u5, 0ul),
+ (acc2, pair) =>
+ {
+ ulong vj = pair.a.GetValueByIndex(j);
+ (ulong t0, ulong t1, ulong t2, ulong t3, ulong t4, ulong t5, ulong t6) = acc2;
+ (t0, ulong carry) = BlsUtil.MultiplyAddCarry(t0, vj, pair.b.V0, 0);
+ (t1, carry) = BlsUtil.MultiplyAddCarry(t1, vj, pair.b.V1, carry);
+ (t2, carry) = BlsUtil.MultiplyAddCarry(t2, vj, pair.b.V2, carry);
+ (t3, carry) = BlsUtil.MultiplyAddCarry(t3, vj, pair.b.V3, carry);
+ (t4, carry) = BlsUtil.MultiplyAddCarry(t4, vj, pair.b.V4, carry);
+ (t5, carry) = BlsUtil.MultiplyAddCarry(t5, vj, pair.b.V5, carry);
+ (t6, _) = BlsUtil.AddWithCarry(t6, 0, carry);
+ return (t0, t1, t2, t3, t4, t5, t6);
+ });
+
+ ulong k;
+ unchecked
+ {
+ // Wrapping multiply
+ k = t0 * INV;
+ }
+ (ulong _, ulong carry) = BlsUtil.MultiplyAddCarry(t0, k, MODULUS[0], 0);
+ (ulong r1, carry) = BlsUtil.MultiplyAddCarry(t1, k, MODULUS[1], carry);
+ (ulong r2, carry) = BlsUtil.MultiplyAddCarry(t2, k, MODULUS[2], carry);
+ (ulong r3, carry) = BlsUtil.MultiplyAddCarry(t3, k, MODULUS[3], carry);
+ (ulong r4, carry) = BlsUtil.MultiplyAddCarry(t4, k, MODULUS[4], carry);
+ (ulong r5, carry) = BlsUtil.MultiplyAddCarry(t5, k, MODULUS[5], carry);
+ (ulong r6, _) = BlsUtil.AddWithCarry(t6, 0, carry);
+ return (r1, r2, r3, r4, r5, r6);
+ });
+ return new Fp(
+ u0,
+ u1,
+ u2,
+ u3,
+ u4,
+ u5
+ )
+ .SubtractP();
+
+ }
+
+ public ulong GetValueByIndex(int i)
+ {
+ switch (i)
+ {
+ case 0:
+ return this.V0;
+ case 1:
+ return this.V1;
+ case 2:
+ return this.V2;
+ case 3:
+ return this.V3;
+ case 4:
+ return this.V4;
+ case 5:
+ return this.V5;
+ default:
+ throw new NotImplementedException();
+ };
+ }
+
+ public override string ToString()
+ {
+ // Hex of bytes with 0x prefix
+ byte[] bytes = this.ToBytes();
+ Span stringValue = stackalloc char[bytes.Length * 2 + 2];
+ stringValue[0] = '0';
+ stringValue[1] = 'x';
+ int i = 1;
+ foreach (byte b in bytes)
+ {
+ int charIndex = i++ * 2;
+ int quotient = Math.DivRem(b, 16, out int remainder);
+ stringValue[charIndex] = GetChar(quotient);
+ stringValue[charIndex + 1] = GetChar(remainder);
+ }
+
+ return new string(stringValue);
+ }
+
+ private static char GetChar(int value)
+ {
+ if (value < 10)
+ {
+ return (char)(value + 48); // 0->9
+ }
+ return (char)(value + 65 - 10); // A->F ASCII
+ }
+
+ internal bool Sgn0()
+ {
+ Fp tmp = Fp.MontgomeryReduce(
+ this.V0,
+ this.V1,
+ this.V2,
+ this.V3,
+ this.V4,
+ this.V5,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ );
+ return (tmp.V0 & 1) != 0;
+ }
+ }
+}
diff --git a/src/BLS/Models/Fp12.cs b/src/BLS/Models/Fp12.cs
new file mode 100644
index 00000000..9c4f9f44
--- /dev/null
+++ b/src/BLS/Models/Fp12.cs
@@ -0,0 +1,155 @@
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Diagnostics;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+
+namespace EdjCase.ICP.BLS.Models
+{
+ internal struct Fp12
+ {
+ public Fp6 C0 { get; }
+ public Fp6 C1 { get; }
+
+ public Fp12(Fp6 c0, Fp6 c1)
+ {
+ this.C0 = c0;
+ this.C1 = c1;
+ }
+
+ public Fp12 FrobeniusMap()
+ {
+ Fp6 c0 = this.C0.FrobeniusMap();
+ Fp6 c1 = this.C1.FrobeniusMap();
+
+ c1 *= new Fp6(
+ new Fp2(
+ new Fp(
+ 0x0708_9552_b319_d465,
+ 0xc669_5f92_b50a_8313,
+ 0x97e8_3ccc_d117_228f,
+ 0xa35b_aeca_b2dc_29ee,
+ 0x1ce3_93ea_5daa_ce4d,
+ 0x08f2_220f_b0fb_66eb
+ ),
+ new Fp(
+ 0xb2f6_6aad_4ce5_d646,
+ 0x5842_a06b_fc49_7cec,
+ 0xcf48_95d4_2599_d394,
+ 0xc11b_9cba_40a8_e8d0,
+ 0x2e38_13cb_e5a0_de89,
+ 0x110e_efda_8884_7faf
+ )
+ ),
+ Fp2.Zero(),
+ Fp2.Zero()
+ );
+ return new Fp12(c0, c1);
+ }
+
+ public Fp12? Invert()
+ {
+ Fp6? t = (this.C0.Square() - this.C1.Square().MultiplyByNonresidue()).Invert();
+ if (t == null)
+ {
+ return null;
+ }
+ return new Fp12(this.C0.Multiply(t.Value), this.C1.Multiply(t.Value.Neg()));
+ }
+
+ public Fp12 Multiply(Fp12 rhs)
+ {
+ Fp6 aa = this.C0.Multiply(rhs.C0);
+ Fp6 bb = this.C1.Multiply(rhs.C1);
+ Fp6 o = rhs.C0.Add(rhs.C1);
+ Fp6 c1 = this.C1.Add(this.C0);
+ c1 = c1.Multiply(o);
+ c1 = c1.Subtract(aa);
+ c1 = c1.Subtract(bb);
+ Fp6 c0 = bb.MultiplyByNonresidue();
+ c0 = c0.Add(aa);
+ return new Fp12(c0, c1);
+ }
+
+ public static Fp12 operator *(Fp12 a, Fp12 b)
+ {
+ return a.Multiply(b);
+ }
+
+ public Fp12 Conjugate()
+ {
+ return new Fp12(this.C0, this.C1.Neg());
+ }
+
+ public static Fp12 Zero()
+ {
+ return new Fp12(Fp6.Zero(), Fp6.Zero());
+ }
+
+ public static Fp12 One()
+ {
+ return new Fp12(Fp6.One(), Fp6.Zero());
+ }
+
+ internal Fp12 Square()
+ {
+ Fp6 ab = this.C0 * this.C1;
+ Fp6 c0c1 = this.C0 + this.C1;
+ Fp6 c0 = this.C1.MultiplyByNonresidue();
+ c0 += this.C0;
+ c0 *= c0c1;
+ c0 -= ab;
+ Fp6 c1 = ab + ab;
+ c0 -= ab.MultiplyByNonresidue();
+
+ return new Fp12(c0, c1);
+ }
+
+ internal Fp12 MultiplyBy014(Fp2 c0, Fp2 c1, Fp2 c4)
+ {
+ Fp6 aa = this.C0.MultiplyBy01(c0, c1);
+ Fp6 bb = this.C1.MultiplyBy1(c4);
+ Fp2 o = c1 + c4;
+ Fp6 newC1 = this.C1 + this.C0;
+ newC1 = newC1.MultiplyBy01(c0, o);
+ newC1 = newC1 - aa - bb;
+ Fp6 newC0 = bb;
+ newC0 = newC0.MultiplyByNonresidue();
+ newC0 = newC0 + aa;
+
+ return new Fp12(newC0, newC1);
+ }
+
+
+ public Fp12 Add(Fp12 rhs)
+ {
+ return new Fp12(this.C0 + rhs.C0, this.C1 + rhs.C1);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Fp12 fp12)
+ {
+ return this.Equals(fp12);
+ }
+ return false;
+ }
+
+ public bool Equals(Fp12 rhs)
+ {
+ return this.C0.Equals(rhs.C0) && this.C1.Equals(rhs.C1);
+ }
+
+ public override int GetHashCode()
+ {
+ return this.C0.GetHashCode() ^ this.C1.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return $"{this.C0} + {this.C1}";
+ }
+ }
+}
diff --git a/src/BLS/Models/Fp2.cs b/src/BLS/Models/Fp2.cs
new file mode 100644
index 00000000..a18aa0fe
--- /dev/null
+++ b/src/BLS/Models/Fp2.cs
@@ -0,0 +1,236 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+
+namespace EdjCase.ICP.BLS.Models
+{
+ internal struct Fp2
+ {
+ public static readonly Fp2 B;
+ public static readonly Fp2 B3;
+ static Fp2()
+ {
+ B = new Fp2(
+ new Fp(
+ 0xaa27_0000_000c_fff3,
+ 0x53cc_0032_fc34_000a,
+ 0x478f_e97a_6b0a_807f,
+ 0xb1d3_7ebe_e6ba_24d7,
+ 0x8ec9_733b_bf78_ab2f,
+ 0x09d6_4551_3d83_de7e
+ ),
+ new Fp(
+ 0xaa27_0000_000c_fff3,
+ 0x53cc_0032_fc34_000a,
+ 0x478f_e97a_6b0a_807f,
+ 0xb1d3_7ebe_e6ba_24d7,
+ 0x8ec9_733b_bf78_ab2f,
+ 0x09d6_4551_3d83_de7e
+ )
+ );
+ B3 = B + B + B;
+ }
+ public readonly Fp C0;
+ public readonly Fp C1;
+
+ public Fp2(Fp c0, Fp c1)
+ {
+ this.C0 = c0;
+ this.C1 = c1;
+ }
+
+ public static Fp2 Zero()
+ {
+ return new Fp2(Fp.Zero(), Fp.Zero());
+ }
+
+ public static Fp2 One()
+ {
+ return new Fp2(Fp.One(), Fp.Zero());
+ }
+
+ public bool IsZero()
+ {
+ return this.C0.IsZero() && this.C1.IsZero();
+ }
+
+ public Fp2 Square()
+ {
+ Fp a = this.C0.Add(this.C1);
+ Fp b = this.C0.Subtract(this.C1);
+ Fp c = this.C0.Add(this.C0);
+
+
+ Fp newC0 = a.Multiply(b);
+ Fp newC1 = c.Multiply(this.C1);
+ return new Fp2(newC0, newC1);
+ }
+
+ public bool LexicographicallyLargest()
+ {
+ return this.C1.LexicographicallyLargest()
+ || (this.C1.IsZero() && this.C0.LexicographicallyLargest());
+ }
+
+ public Fp2 Add(Fp2 rhs)
+ {
+ return new Fp2(this.C0.Add(rhs.C0), this.C1.Add(rhs.C1));
+ }
+
+ public static Fp2 operator +(Fp2 lhs, Fp2 rhs)
+ {
+ return lhs.Add(rhs);
+ }
+
+ public Fp2 Subtract(Fp2 rhs)
+ {
+ return new Fp2(this.C0.Subtract(rhs.C0), this.C1.Subtract(rhs.C1));
+ }
+
+ public static Fp2 operator -(Fp2 lhs, Fp2 rhs)
+ {
+ return lhs.Subtract(rhs);
+ }
+
+ public Fp2 Multiply(Fp2 rhs)
+ {
+ Fp newC0 = Fp.SumOfProducts(new[] { this.C0, this.C1.Neg() }, new[] { rhs.C0, rhs.C1 });
+ Fp newC1 = Fp.SumOfProducts(new[] { this.C0, this.C1 }, new[] { rhs.C1, rhs.C0 });
+ return new Fp2(newC0, newC1);
+ }
+
+ public static Fp2 operator *(Fp2 lhs, Fp2 rhs)
+ {
+ return lhs.Multiply(rhs);
+ }
+
+ public Fp2 Neg()
+ {
+ return new Fp2(this.C0.Neg(), this.C1.Neg());
+ }
+
+ public Fp2? SquareRoot()
+ {
+ if (this.IsZero())
+ {
+ return null;
+ }
+ Fp2 a1 = this.Pow(new Fp(
+ 0xee7f_bfff_ffff_eaaa,
+ 0x07aa_ffff_ac54_ffff,
+ 0xd9cc_34a8_3dac_3d89,
+ 0xd91d_d2e1_3ce1_44af,
+ 0x92c6_e9ed_90d2_eb35,
+ 0x0680_447a_8e5f_f9a6
+ ));
+ Fp2 alpha = a1.Square() * this;
+ Fp2 x0 = a1 * this;
+ if (alpha.Equals(Fp2.One().Neg()))
+ {
+ return new Fp2(x0.C1.Neg(), x0.C0);
+ }
+ Fp2 sqrt = (alpha + Fp2.One()).Pow(new Fp(
+ 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;
+ if (!sqrt.Square().Equals(this))
+ {
+ return null;
+ }
+ return sqrt;
+ }
+
+ public Fp2 Pow(Fp by)
+ {
+ Fp2 res = Fp2.One();
+ for (int i = 5; i >= 0; i--)
+ {
+ ulong v = by.GetValueByIndex(i);
+ for (int j = 63; j >= 0; j--)
+ {
+ res = res.Square();
+ if (((v >> j) & 1) == 1)
+ {
+ res *= this;
+ }
+ }
+ }
+ return res;
+ }
+
+ public Fp2? Invert()
+ {
+ Fp t0 = this.C0.Square();
+ Fp t1 = this.C1.Square();
+ Fp t2 = t0.Add(t1);
+ Fp? t3 = t2.Invert();
+ if (t3 == null)
+ {
+ return null;
+ }
+ Fp t4 = this.C0.Multiply(t3.Value);
+ Fp t5 = this.C1.Multiply(t3.Value.Neg());
+ return new Fp2(t4, t5);
+ }
+
+ public Fp2 FrobeniusMap()
+ {
+ return this.Conjugate();
+ }
+
+ public Fp2 Conjugate()
+ {
+ return new Fp2(this.C0, this.C1.Neg());
+ }
+
+ public Fp2 MultiplyByNonresidue()
+ {
+ return new Fp2(this.C0 - this.C1, this.C0 + this.C1);
+ }
+
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Fp2 fp2)
+ {
+ return this.Equals(fp2);
+ }
+ return false;
+ }
+
+ public bool Equals(Fp2 rhs)
+ {
+ return this.C0.Equals(rhs.C0) && this.C1.Equals(rhs.C1);
+ }
+
+ public override int GetHashCode()
+ {
+ return this.C0.GetHashCode() ^ this.C1.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return $"{this.C0} + ({this.C1})*u";
+ }
+
+ internal static Fp Default()
+ {
+ return Fp.Zero();
+ }
+
+ internal bool Sgn0()
+ {
+ bool sign0 = this.C0.Sgn0();
+ bool zero0 = this.C0.IsZero();
+ bool sign1 = this.C1.Sgn0();
+ return sign0 || (zero0 && sign1);
+ }
+ }
+}
diff --git a/src/BLS/Models/Fp6.cs b/src/BLS/Models/Fp6.cs
new file mode 100644
index 00000000..5f2cac47
--- /dev/null
+++ b/src/BLS/Models/Fp6.cs
@@ -0,0 +1,238 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+
+namespace EdjCase.ICP.BLS.Models
+{
+ internal struct Fp6
+ {
+ public Fp2 C0 { get; }
+ public Fp2 C1 { get; }
+ public Fp2 C2 { get; }
+
+ public Fp6(Fp2 c0, Fp2 c1, Fp2 c2)
+ {
+ this.C0 = c0;
+ this.C1 = c1;
+ this.C2 = c2;
+ }
+
+ public Fp6 Add(Fp6 rhs)
+ {
+ return new Fp6(this.C0.Add(rhs.C0), this.C1.Add(rhs.C1), this.C2.Add(rhs.C2));
+ }
+
+ public static Fp6 operator +(Fp6 lhs, Fp6 rhs)
+ {
+ return lhs.Add(rhs);
+ }
+
+ public Fp6 Subtract(Fp6 rhs)
+ {
+ Fp2 c0 = this.C0.Subtract(rhs.C0);
+ Fp2 c1 = this.C1.Subtract(rhs.C1);
+ Fp2 c2 = this.C2.Subtract(rhs.C2);
+ return new Fp6(c0, c1, c2);
+ }
+
+ public static Fp6 operator -(Fp6 lhs, Fp6 rhs)
+ {
+ return lhs.Subtract(rhs);
+ }
+
+ public Fp6 Multiply(Fp6 rhs)
+ {
+ Fp6 a = this;
+ Fp b10_p_b11 = rhs.C1.C0.Add(rhs.C1.C1);
+ Fp b10_m_b11 = rhs.C1.C0.Subtract(rhs.C1.C1);
+ Fp b20_p_b21 = rhs.C2.C0.Add(rhs.C2.C1);
+ Fp b20_m_b21 = rhs.C2.C0.Subtract(rhs.C2.C1);
+
+ Fp2 c0 = new Fp2(
+ Fp.SumOfProducts(
+ new Fp[] { a.C0.C0, a.C0.C1.Neg(), a.C1.C0, a.C1.C1.Neg(), a.C2.C0, a.C2.C1.Neg() },
+ new Fp[] { rhs.C0.C0, rhs.C0.C1, b20_m_b21, b20_p_b21, b10_m_b11, b10_p_b11 }
+ ),
+ Fp.SumOfProducts(
+ new Fp[] { a.C0.C0, a.C0.C1, a.C1.C0, a.C1.C1, a.C2.C0, a.C2.C1 },
+ new Fp[] { rhs.C0.C1, rhs.C0.C0, b20_p_b21, b20_m_b21, b10_p_b11, b10_m_b11 }
+ )
+ );
+
+ Fp2 c1 = new Fp2(
+ Fp.SumOfProducts(
+ new Fp[] { a.C0.C0, a.C0.C1.Neg(), a.C1.C0, a.C1.C1.Neg(), a.C2.C0, a.C2.C1.Neg() },
+ new Fp[] { rhs.C1.C0, rhs.C1.C1, rhs.C0.C0, rhs.C0.C1, b20_m_b21, b20_p_b21 }
+ ),
+ Fp.SumOfProducts(
+ new Fp[] { a.C0.C0, a.C0.C1, a.C1.C0, a.C1.C1, a.C2.C0, a.C2.C1 },
+ new Fp[] { rhs.C1.C1, rhs.C1.C0, rhs.C0.C1, rhs.C0.C0, b20_p_b21, b20_m_b21 }
+ )
+ );
+
+ Fp2 c2 = new Fp2(
+ Fp.SumOfProducts(
+ new Fp[] { a.C0.C0, a.C0.C1.Neg(), a.C1.C0, a.C1.C1.Neg(), a.C2.C0, a.C2.C1.Neg() },
+ new Fp[] { rhs.C2.C0, rhs.C2.C1, rhs.C1.C0, rhs.C1.C1, rhs.C0.C0, rhs.C0.C1 }
+ ),
+ Fp.SumOfProducts(
+ new Fp[] { a.C0.C0, a.C0.C1, a.C1.C0, a.C1.C1, a.C2.C0, a.C2.C1 },
+ new Fp[] { rhs.C2.C1, rhs.C2.C0, rhs.C1.C1, rhs.C1.C0, rhs.C0.C1, rhs.C0.C0 }
+ )
+ );
+
+ return new Fp6(c0, c1, c2);
+ }
+
+ public static Fp6 operator *(Fp6 lhs, Fp6 rhs)
+ {
+ return lhs.Multiply(rhs);
+ }
+
+ public Fp6 FrobeniusMap()
+ {
+ Fp2 c0 = this.C0.FrobeniusMap();
+ Fp2 c1 = this.C1.FrobeniusMap();
+ Fp2 c2 = this.C2.FrobeniusMap();
+
+ c1 *= new Fp2(
+ Fp.Zero(),
+ new Fp(
+ 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 *= new Fp2(
+ new Fp(
+ 0x890d_c9e4_8675_45c3,
+ 0x2af3_2253_3285_a5d5,
+ 0x5088_0866_309b_7e2c,
+ 0xa20d_1b8c_7e88_1024,
+ 0x14e4_f04f_e2db_9068,
+ 0x14e5_6d3f_1564_853a
+ ),
+ Fp.Zero()
+ );
+
+ return new Fp6(c0, c1, c2);
+ }
+
+ internal Fp6 Square()
+ {
+ Fp2 s0 = this.C0.Square();
+ Fp2 ab = this.C0 * this.C1;
+ Fp2 s1 = ab + ab;
+ Fp2 s2 = (this.C0 - this.C1 + this.C2).Square();
+ Fp2 bc = this.C1 * this.C2;
+ Fp2 s3 = bc + bc;
+ Fp2 s4 = this.C2.Square();
+
+ Fp2 c0 = s3.MultiplyByNonresidue() + s0;
+ Fp2 c1 = s4.MultiplyByNonresidue() + s1;
+ Fp2 c2 = s1 + s2 + s3 - s0 - s4;
+
+ return new Fp6(c0, c1, c2);
+ }
+
+ public Fp6 MultiplyByNonresidue()
+ {
+ Fp2 c0 = this.C2.MultiplyByNonresidue();
+ Fp2 c1 = this.C0;
+ Fp2 c2 = this.C1;
+ return new Fp6(c0, c1, c2);
+ }
+
+ public Fp6? Invert()
+ {
+ Fp2 c0 = (this.C1 * this.C2).MultiplyByNonresidue();
+ c0 = this.C0.Square() - c0;
+
+ Fp2 c1 = this.C2.Square().MultiplyByNonresidue();
+ c1 -= this.C0 * this.C1;
+
+ Fp2 c2 = this.C1.Square();
+ c2 -= this.C0 * this.C2;
+
+ Fp2 tmp = ((this.C1 * c2) + (this.C2 * c1)).MultiplyByNonresidue();
+ tmp += this.C0 * c0;
+
+ Fp2? t = tmp.Invert();
+ if (t == null)
+ {
+ return null;
+ }
+ return new Fp6(c0.Multiply(t.Value), c1.Multiply(t.Value), c2.Multiply(t.Value));
+ }
+
+ public Fp6 Neg()
+ {
+ return new Fp6(this.C0.Neg(), this.C1.Neg(), this.C2.Neg());
+ }
+
+ public static Fp6 Zero()
+ {
+ return new Fp6(Fp2.Zero(), Fp2.Zero(), Fp2.Zero());
+ }
+
+ public static Fp6 One()
+ {
+ return new Fp6(Fp2.One(), Fp2.Zero(), Fp2.Zero());
+ }
+
+ internal Fp6 MultiplyBy01(Fp2 c0, Fp2 c1)
+ {
+ Fp2 a_a = this.C0.Multiply(c0);
+ Fp2 b_b = this.C1.Multiply(c1);
+
+ Fp2 t1 = (this.C2.Multiply(c1).MultiplyByNonresidue()) + a_a;
+
+ Fp2 t2 = (c0 + c1).Multiply(this.C0 + this.C1) - a_a - b_b;
+
+ Fp2 t3 = this.C2.Multiply(c0) + b_b;
+
+ return new Fp6(t1, t2, t3);
+ }
+
+ internal Fp6 MultiplyBy1(Fp2 c1)
+ {
+ return new Fp6(
+ this.C2.Multiply(c1).MultiplyByNonresidue(),
+ this.C0.Multiply(c1),
+ this.C1.Multiply(c1)
+ );
+ }
+
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Fp6 fp6)
+ {
+ return this.Equals(fp6);
+ }
+ return false;
+ }
+
+ public bool Equals(Fp6 rhs)
+ {
+ return this.C0.Equals(rhs.C0) && this.C1.Equals(rhs.C1) && this.C2.Equals(rhs.C2);
+ }
+
+ public override int GetHashCode()
+ {
+ return this.C0.GetHashCode() ^ this.C1.GetHashCode() ^ this.C2.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return $"{this.C0} + ({this.C1})*v + ({this.C2})*v^2";
+ }
+ }
+}
diff --git a/src/BLS/Models/G1Affine.cs b/src/BLS/Models/G1Affine.cs
new file mode 100644
index 00000000..befa3e5e
--- /dev/null
+++ b/src/BLS/Models/G1Affine.cs
@@ -0,0 +1,612 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace EdjCase.ICP.BLS.Models
+{
+ internal struct G1Affine
+ {
+ public Fp X { get; }
+ public Fp Y { get; }
+ public bool IsInfinity { get; }
+
+ public static readonly Fp SSWU_XI;
+ public static readonly Fp SSWU_ELLP_A;
+ public static readonly Fp SSWU_ELLP_B;
+ public static readonly Fp SQRT_M_XI_CUBED;
+ public static readonly Fp[] ISO11_XNUM;
+ public static readonly Fp[] ISO11_XDEN;
+ public static readonly Fp[] ISO11_YNUM;
+ public static readonly Fp[] ISO11_YDEN;
+
+ static G1Affine()
+ {
+ SSWU_XI = new Fp(
+ 0x886c_0000_0023_ffdc,
+ 0x0f70_008d_3090_001d,
+ 0x7767_2417_ed58_28c3,
+ 0x9dac_23e9_43dc_1740,
+ 0x5055_3f1b_9c13_1521,
+ 0x078c_712f_be0a_b6e8
+ );
+ SSWU_ELLP_A = new Fp(
+ 0x2f65_aa0e_9af5_aa51,
+ 0x8646_4c2d_1e84_16c3,
+ 0xb85c_e591_b7bd_31e2,
+ 0x27e1_1c91_b5f2_4e7c,
+ 0x2837_6eda_6bfc_1835,
+ 0x1554_55c3_e507_1d85
+ );
+ SSWU_ELLP_B = new Fp(
+ 0xfb99_6971_fe22_a1e0,
+ 0x9aa9_3eb3_5b74_2d6f,
+ 0x8c47_6013_de99_c5c4,
+ 0x873e_27c3_a221_e571,
+ 0xca72_b5e4_5a52_d888,
+ 0x0682_4061_418a_386b
+ );
+ SQRT_M_XI_CUBED = new Fp(
+ 0x43b5_71ca_d321_5f1f,
+ 0xccb4_60ef_1c70_2dc2,
+ 0x742d_884f_4f97_100b,
+ 0xdb2c_3e32_38a3_382b,
+ 0xe40f_3fa1_3fce_8f88,
+ 0x0073_a2af_9892_a2ff
+ );
+ ISO11_XNUM = new[]{
+ new Fp(
+ 0x4d18_b6f3_af00_131c,
+ 0x19fa_2197_93fe_e28c,
+ 0x3f28_85f1_467f_19ae,
+ 0x23dc_ea34_f2ff_b304,
+ 0xd15b_58d2_ffc0_0054,
+ 0x0913_be20_0a20_bef4
+ ),
+ new Fp(
+ 0x8989_8538_5cdb_bd8b,
+ 0x3c79_e43c_c7d9_66aa,
+ 0x1597_e193_f4cd_233a,
+ 0x8637_ef1e_4d66_23ad,
+ 0x11b2_2dee_d20d_827b,
+ 0x0709_7bc5_9987_84ad
+ ),
+ new Fp(
+ 0xa542_583a_480b_664b,
+ 0xfc71_69c0_26e5_68c6,
+ 0x5ba2_ef31_4ed8_b5a6,
+ 0x5b54_91c0_5102_f0e7,
+ 0xdf6e_9970_7d2a_0079,
+ 0x0784_151e_d760_5524
+ ),
+ new Fp(
+ 0x494e_2128_70f7_2741,
+ 0xab9b_e52f_bda4_3021,
+ 0x26f5_5779_94e3_4c3d,
+ 0x049d_fee8_2aef_bd60,
+ 0x65da_dd78_2850_5289,
+ 0x0e93_d431_ea01_1aeb
+ ),
+ new Fp(
+ 0x90ee_774b_d6a7_4d45,
+ 0x7ada_1c8a_41bf_b185,
+ 0x0f1a_8953_b325_f464,
+ 0x104c_2421_1be4_805c,
+ 0x1691_39d3_19ea_7a8f,
+ 0x09f2_0ead_8e53_2bf6
+ ),
+ new Fp(
+ 0x6ddd_93e2_f436_26b7,
+ 0xa548_2c9a_a1cc_d7bd,
+ 0x1432_4563_1883_f4bd,
+ 0x2e0a_94cc_f77e_c0db,
+ 0xb028_2d48_0e56_489f,
+ 0x18f4_bfcb_b436_8929
+ ),
+ new Fp(
+ 0x23c5_f0c9_5340_2dfd,
+ 0x7a43_ff69_58ce_4fe9,
+ 0x2c39_0d3d_2da5_df63,
+ 0xd0df_5c98_e1f9_d70f,
+ 0xffd8_9869_a572_b297,
+ 0x1277_ffc7_2f25_e8fe
+ ),
+ new Fp(
+ 0x79f4_f049_0f06_a8a6,
+ 0x85f8_94a8_8030_fd81,
+ 0x12da_3054_b18b_6410,
+ 0xe2a5_7f65_0588_0d65,
+ 0xbba0_74f2_60e4_00f1,
+ 0x08b7_6279_f621_d028
+ ),
+ new Fp(
+ 0xe672_45ba_78d5_b00b,
+ 0x8456_ba9a_1f18_6475,
+ 0x7888_bff6_e6b3_3bb4,
+ 0xe215_85b9_a30f_86cb,
+ 0x05a6_9cdc_ef55_feee,
+ 0x09e6_99dd_9adf_a5ac
+ ),
+ new Fp(
+ 0x0de5_c357_bff5_7107,
+ 0x0a0d_b4ae_6b1a_10b2,
+ 0xe256_bb67_b3b3_cd8d,
+ 0x8ad4_5657_4e9d_b24f,
+ 0x0443_915f_50fd_4179,
+ 0x098c_4bf7_de8b_6375
+ ),
+ new Fp(
+ 0xe6b0_617e_7dd9_29c7,
+ 0xfe6e_37d4_4253_7375,
+ 0x1daf_deda_137a_489e,
+ 0xe4ef_d1ad_3f76_7ceb,
+ 0x4a51_d866_7f0f_e1cf,
+ 0x054f_df4b_bf1d_821c
+ ),
+ new Fp(
+ 0x72db_2a50_658d_767b,
+ 0x8abf_91fa_a257_b3d5,
+ 0xe969_d683_3764_ab47,
+ 0x4641_7014_2a10_09eb,
+ 0xb14f_01aa_db30_be2f,
+ 0x18ae_6a85_6f40_715d
+ )
+ };
+ ISO11_XDEN = new[]{
+ new Fp(
+ 0xb962_a077_fdb0_f945,
+ 0xa6a9_740f_efda_13a0,
+ 0xc14d_568c_3ed6_c544,
+ 0xb43f_c37b_908b_133e,
+ 0x9c0b_3ac9_2959_9016,
+ 0x0165_aa6c_93ad_115f
+ ),
+ new Fp(
+ 0x2327_9a3b_a506_c1d9,
+ 0x92cf_ca0a_9465_176a,
+ 0x3b29_4ab1_3755_f0ff,
+ 0x116d_da1c_5070_ae93,
+ 0xed45_3092_4cec_2045,
+ 0x0833_83d6_ed81_f1ce
+ ),
+ new Fp(
+ 0x9885_c2a6_449f_ecfc,
+ 0x4a2b_54cc_d377_33f0,
+ 0x17da_9ffd_8738_c142,
+ 0xa0fb_a727_32b3_fafd,
+ 0xff36_4f36_e54b_6812,
+ 0x0f29_c13c_6605_23e2
+ ),
+ new Fp(
+ 0xe349_cc11_8278_f041,
+ 0xd487_228f_2f32_04fb,
+ 0xc9d3_2584_9ade_5150,
+ 0x43a9_2bd6_9c15_c2df,
+ 0x1c2c_7844_bc41_7be4,
+ 0x1202_5184_f407_440c
+ ),
+ new Fp(
+ 0x587f_65ae_6acb_057b,
+ 0x1444_ef32_5140_201f,
+ 0xfbf9_95e7_1270_da49,
+ 0xccda_0660_7243_6a42,
+ 0x7408_904f_0f18_6bb2,
+ 0x13b9_3c63_edf6_c015
+ ),
+ new Fp(
+ 0xfb91_8622_cd14_1920,
+ 0x4a4c_6442_3eca_ddb4,
+ 0x0beb_2329_27f7_fb26,
+ 0x30f9_4df6_f83a_3dc2,
+ 0xaeed_d424_d780_f388,
+ 0x06cc_402d_d594_bbeb
+ ),
+ new Fp(
+ 0xd41f_7611_51b2_3f8f,
+ 0x32a9_2465_4357_19b3,
+ 0x64f4_36e8_88c6_2cb9,
+ 0xdf70_a9a1_f757_c6e4,
+ 0x6933_a38d_5b59_4c81,
+ 0x0c6f_7f72_37b4_6606
+ ),
+ new Fp(
+ 0x693c_0874_7876_c8f7,
+ 0x22c9_850b_f9cf_80f0,
+ 0x8e90_71da_b950_c124,
+ 0x89bc_62d6_1c7b_af23,
+ 0xbc6b_e2d8_dad5_7c23,
+ 0x1791_6987_aa14_a122
+ ),
+ new Fp(
+ 0x1be3_ff43_9c13_16fd,
+ 0x9965_243a_7571_dfa7,
+ 0xc7f7_f629_62f5_cd81,
+ 0x32c6_aa9a_f394_361c,
+ 0xbbc2_ee18_e1c2_27f4,
+ 0x0c10_2cba_c531_bb34
+ ),
+ new Fp(
+ 0x9976_14c9_7bac_bf07,
+ 0x61f8_6372_b991_92c0,
+ 0x5b8c_95fc_1435_3fc3,
+ 0xca2b_066c_2a87_492f,
+ 0x1617_8f5b_bf69_8711,
+ 0x12a6_dcd7_f0f4_e0e8
+ ),
+ new Fp(
+ 0x7609_0000_0002_fffd,
+ 0xebf4_000b_c40c_0002,
+ 0x5f48_9857_53c7_58ba,
+ 0x77ce_5853_7052_5745,
+ 0x5c07_1a97_a256_ec6d,
+ 0x15f6_5ec3_fa80_e493
+ )
+ };
+ ISO11_YNUM = new[] {
+ new Fp(
+ 0x2b56_7ff3_e283_7267,
+ 0x1d4d_9e57_b958_a767,
+ 0xce02_8fea_04bd_7373,
+ 0xcc31_a30a_0b6c_d3df,
+ 0x7d7b_18a6_8269_2693,
+ 0x0d30_0744_d42a_0310
+ ),
+ new Fp(
+ 0x99c2_555f_a542_493f,
+ 0xfe7f_53cc_4874_f878,
+ 0x5df0_608b_8f97_608a,
+ 0x14e0_3832_052b_49c8,
+ 0x7063_26a6_957d_d5a4,
+ 0x0a8d_add9_c241_4555
+ ),
+ new Fp(
+ 0x13d9_4292_2a5c_f63a,
+ 0x357e_33e3_6e26_1e7d,
+ 0xcf05_a27c_8456_088d,
+ 0x0000_bd1d_e7ba_50f0,
+ 0x83d0_c753_2f8c_1fde,
+ 0x13f7_0bf3_8bbf_2905
+ ),
+ new Fp(
+ 0x5c57_fd95_bfaf_bdbb,
+ 0x28a3_59a6_5e54_1707,
+ 0x3983_ceb4_f636_0b6d,
+ 0xafe1_9ff6_f97e_6d53,
+ 0xb346_8f45_5019_2bf7,
+ 0x0bb6_cde4_9d8b_a257
+ ),
+ new Fp(
+ 0x590b_62c7_ff8a_513f,
+ 0x314b_4ce3_72ca_cefd,
+ 0x6bef_32ce_94b8_a800,
+ 0x6ddf_84a0_9571_3d5f,
+ 0x64ea_ce4c_b098_2191,
+ 0x0386_213c_651b_888d
+ ),
+ new Fp(
+ 0xa531_0a31_111b_bcdd,
+ 0xa14a_c0f5_da14_8982,
+ 0xf9ad_9cc9_5423_d2e9,
+ 0xaa6e_c095_283e_e4a7,
+ 0xcf5b_1f02_2e1c_9107,
+ 0x01fd_df5a_ed88_1793
+ ),
+ new Fp(
+ 0x65a5_72b0_d7a7_d950,
+ 0xe25c_2d81_8347_3a19,
+ 0xc2fc_ebe7_cb87_7dbd,
+ 0x05b2_d36c_769a_89b0,
+ 0xba12_961b_e86e_9efb,
+ 0x07eb_1b29_c1df_de1f
+ ),
+ new Fp(
+ 0x93e0_9572_f7c4_cd24,
+ 0x364e_9290_7679_5091,
+ 0x8569_467e_68af_51b5,
+ 0xa47d_a894_39f5_340f,
+ 0xf4fa_9180_82e4_4d64,
+ 0x0ad5_2ba3_e669_5a79
+ ),
+ new Fp(
+ 0x9114_2984_4e0d_5f54,
+ 0xd03f_51a3_516b_b233,
+ 0x3d58_7e56_4053_6e66,
+ 0xfa86_d2a3_a9a7_3482,
+ 0xa90e_d5ad_f1ed_5537,
+ 0x149c_9c32_6a5e_7393
+ ),
+ new Fp(
+ 0x462b_beb0_3c12_921a,
+ 0xdc9a_f5fa_0a27_4a17,
+ 0x9a55_8ebd_e836_ebed,
+ 0x649e_f8f1_1a4f_ae46,
+ 0x8100_e165_2b3c_dc62,
+ 0x1862_bd62_c291_dacb
+ ),
+ new Fp(
+ 0x05c9_b8ca_89f1_2c26,
+ 0x0194_160f_a9b9_ac4f,
+ 0x6a64_3d5a_6879_fa2c,
+ 0x1466_5bdd_8846_e19d,
+ 0xbb1d_0d53_af3f_f6bf,
+ 0x12c7_e1c3_b289_62e5
+ ),
+ new Fp(
+ 0xb55e_bf90_0b8a_3e17,
+ 0xfedc_77ec_1a92_01c4,
+ 0x1f07_db10_ea1a_4df4,
+ 0x0dfb_d15d_c41a_594d,
+ 0x3895_47f2_334a_5391,
+ 0x0241_9f98_1658_71a4
+ ),
+ new Fp(
+ 0xb416_af00_0745_fc20,
+ 0x8e56_3e9d_1ea6_d0f5,
+ 0x7c76_3e17_763a_0652,
+ 0x0145_8ef0_159e_bbef,
+ 0x8346_fe42_1f96_bb13,
+ 0x0d2d_7b82_9ce3_24d2
+ ),
+ new Fp(
+ 0x9309_6bb5_38d6_4615,
+ 0x6f2a_2619_951d_823a,
+ 0x8f66_b3ea_5951_4fa4,
+ 0xf563_e637_04f7_092f,
+ 0x724b_136c_4cf2_d9fa,
+ 0x0469_59cf_cfd0_bf49
+ ),
+ new Fp(
+ 0xea74_8d4b_6e40_5346,
+ 0x91e9_079c_2c02_d58f,
+ 0x4106_4965_946d_9b59,
+ 0xa067_31f1_d2bb_e1ee,
+ 0x07f8_97e2_67a3_3f1b,
+ 0x1017_2909_1921_0e5f
+ ),
+ new Fp(
+ 0x872a_a6c1_7d98_5097,
+ 0xeecc_5316_1264_562a,
+ 0x07af_e37a_fff5_5002,
+ 0x5475_9078_e5be_6838,
+ 0xc4b9_2d15_db8a_cca8,
+ 0x106d_87d1_b51d_13b9
+ )
+ };
+ ISO11_YDEN = new[] {
+ new Fp(
+ 0xeb6c_359d_47e5_2b1c,
+ 0x18ef_5f8a_1063_4d60,
+ 0xddfa_71a0_889d_5b7e,
+ 0x723e_71dc_c5fc_1323,
+ 0x52f4_5700_b70d_5c69,
+ 0x0a8b_981e_e476_91f1
+ ),
+ new Fp(
+ 0x616a_3c4f_5535_b9fb,
+ 0x6f5f_0373_95db_d911,
+ 0xf25f_4cc5_e35c_65da,
+ 0x3e50_dffe_a3c6_2658,
+ 0x6a33_dca5_2356_0776,
+ 0x0fad_eff7_7b6b_fe3e
+ ),
+ new Fp(
+ 0x2be9_b66d_f470_059c,
+ 0x24a2_c159_a3d3_6742,
+ 0x115d_be7a_d10c_2a37,
+ 0xb663_4a65_2ee5_884d,
+ 0x04fe_8bb2_b8d8_1af4,
+ 0x01c2_a7a2_56fe_9c41
+ ),
+ new Fp(
+ 0xf27b_f8ef_3b75_a386,
+ 0x898b_3674_76c9_073f,
+ 0x2448_2e6b_8c2f_4e5f,
+ 0xc8e0_bbd6_fe11_0806,
+ 0x59b0_c17f_7631_448a,
+ 0x1103_7cd5_8b3d_bfbd
+ ),
+ new Fp(
+ 0x31c7_912e_a267_eec6,
+ 0x1dbf_6f1c_5fcd_b700,
+ 0xd30d_4fe3_ba86_fdb1,
+ 0x3cae_528f_bee9_a2a4,
+ 0xb1cc_e69b_6aa9_ad9a,
+ 0x0443_93bb_632d_94fb
+ ),
+ new Fp(
+ 0xc66e_f6ef_eeb5_c7e8,
+ 0x9824_c289_dd72_bb55,
+ 0x71b1_a4d2_f119_981d,
+ 0x104f_c1aa_fb09_19cc,
+ 0x0e49_df01_d942_a628,
+ 0x096c_3a09_7732_72d4
+ ),
+ new Fp(
+ 0x9abc_11eb_5fad_eff4,
+ 0x32dc_a50a_8857_28f0,
+ 0xfb1f_a372_1569_734c,
+ 0xc4b7_6271_ea65_06b3,
+ 0xd466_a755_99ce_728e,
+ 0x0c81_d464_5f4c_b6ed
+ ),
+ new Fp(
+ 0x4199_f10e_5b8b_e45b,
+ 0xda64_e495_b1e8_7930,
+ 0xcb35_3efe_9b33_e4ff,
+ 0x9e9e_fb24_aa64_24c6,
+ 0xf08d_3368_0a23_7465,
+ 0x0d33_7802_3e4c_7406
+ ),
+ new Fp(
+ 0x7eb4_ae92_ec74_d3a5,
+ 0xc341_b4aa_9fac_3497,
+ 0x5be6_0389_9e90_7687,
+ 0x03bf_d9cc_a75c_bdeb,
+ 0x564c_2935_a96b_fa93,
+ 0x0ef3_c333_71e2_fdb5
+ ),
+ new Fp(
+ 0x7ee9_1fd4_49f6_ac2e,
+ 0xe5d5_bd5c_b935_7a30,
+ 0x773a_8ca5_196b_1380,
+ 0xd0fd_a172_174e_d023,
+ 0x6cb9_5e0f_a776_aead,
+ 0x0d22_d5a4_0cec_7cff
+ ),
+ new Fp(
+ 0xf727_e092_85fd_8519,
+ 0xdc9d_55a8_3017_897b,
+ 0x7549_d8bd_0578_94ae,
+ 0x1784_1961_3d90_d8f8,
+ 0xfce9_5ebd_eb5b_490a,
+ 0x0467_ffae_f23f_c49e
+ ),
+ new Fp(
+ 0xc176_9e6a_7c38_5f1b,
+ 0x79bc_930d_eac0_1c03,
+ 0x5461_c75a_23ed_e3b5,
+ 0x6e20_829e_5c23_0c45,
+ 0x828e_0f1e_772a_53cd,
+ 0x116a_efa7_4912_7bff
+ ),
+ new Fp(
+ 0x101c_10bf_2744_c10a,
+ 0xbbf1_8d05_3a6a_3154,
+ 0xa0ec_f39e_f026_f602,
+ 0xfc00_9d49_96dc_5153,
+ 0xb900_0209_d5bd_08d3,
+ 0x189e_5fe4_470c_d73c
+ ),
+ new Fp(
+ 0x7ebd_546c_a157_5ed2,
+ 0xe47d_5a98_1d08_1b55,
+ 0x57b2_b625_b6d4_ca21,
+ 0xb0a1_ba04_2285_20cc,
+ 0x9873_8983_c210_7ff3,
+ 0x13dd_dbc4_799d_81d6
+ ),
+ new Fp(
+ 0x0931_9f2e_3983_4935,
+ 0x039e_952c_bdb0_5c21,
+ 0x55ba_77a9_a2f7_6493,
+ 0xfd04_e3df_c608_6467,
+ 0xfb95_832e_7d78_742e,
+ 0x0ef9_c24e_ccaf_5e0e
+ ),
+ new Fp(
+ 0x7609_0000_0002_fffd,
+ 0xebf4_000b_c40c_0002,
+ 0x5f48_9857_53c7_58ba,
+ 0x77ce_5853_7052_5745,
+ 0x5c07_1a97_a256_ec6d,
+ 0x15f6_5ec3_fa80_e493
+ )
+ };
+ }
+
+
+ public G1Affine(Fp x, Fp y, bool isInfinity)
+ {
+ this.X = x;
+ this.Y = y;
+ this.IsInfinity = isInfinity;
+ }
+
+ public static G1Affine Identity()
+ {
+ return new G1Affine(Fp.Zero(), Fp.One(), true);
+ }
+
+ public G1Projective ToProjective()
+ {
+ return new G1Projective(this.X, this.Y, this.IsInfinity ? Fp.Zero() : Fp.One());
+ }
+
+ public byte[] ToCompressed()
+ {
+ byte[] bytes = (this.IsInfinity ? Fp.Zero() : this.X).ToBytes();
+ bytes[0] |= 1 << 7;
+ bytes[0] |= (byte)(this.IsInfinity ? 1 << 6 : 0);
+ bytes[0] |= (byte)((!this.IsInfinity && this.Y.LexicographicallyLargest()) ? 1 << 5 : 0);
+ return bytes;
+ }
+
+
+ internal static G1Affine FromCompressed(byte[] bytes)
+ {
+ if (bytes.Length != 48)
+ {
+ throw new ArgumentException("Byte length must be 48 for G1");
+ }
+ // Obtain the three flags from the start of the byte sequence
+ bool compressionFlagSet = ((bytes[0] >> 7) & 1) == 1;
+ bool infinityFlagSet = ((bytes[0] >> 6) & 1) == 1;
+ bool sortFlagSet = ((bytes[0] >> 5) & 1) == 1;
+
+ // Attempt to obtain the x-coordinate
+ byte[] bytesWithoutFlagBits = new byte[48];
+ Array.Copy(bytes, 0, bytesWithoutFlagBits, 0, 48);
+ bytesWithoutFlagBits[0] &= 0b0001_1111;
+ Fp x = Fp.FromBytes(bytesWithoutFlagBits);
+
+ if (infinityFlagSet && compressionFlagSet && !sortFlagSet && x.IsZero())
+ {
+ return G1Affine.Identity();
+ }
+ else
+ {
+ // Recover a y-coordinate given x by y = sqrt(x^3 + 4)
+ Fp y = (x.Square() * x + Fp.B).SquareRoot();
+
+ // Switch to the correct y-coordinate if necessary.
+ if (y.LexicographicallyLargest() ^ sortFlagSet)
+ {
+ y = y.Neg();
+ }
+ bool isValid = !infinityFlagSet && compressionFlagSet;
+ if (!isValid)
+ {
+ throw new ArgumentException("Invalid compressed point");
+ }
+ return new G1Affine(x, y, infinityFlagSet);
+ }
+ }
+
+ public bool IsIdentity()
+ {
+ return this.IsInfinity;
+ }
+
+ internal static G1Affine Generator()
+ {
+ Fp x = new Fp(
+ 0x5cb3_8790_fd53_0c16,
+ 0x7817_fc67_9976_fff5,
+ 0x154f_95c7_143b_a1c1,
+ 0xf0ae_6acd_f3d0_e747,
+ 0xedce_6ecc_21db_f440,
+ 0x1201_7741_9e0b_fb75
+ );
+ Fp y = new Fp(
+ 0xbaac_93d5_0ce7_2271,
+ 0x8c22_631a_7918_fd8e,
+ 0xdd59_5f13_5707_25ce,
+ 0x51ac_5829_5040_5194,
+ 0x0e1c_8c3f_ad00_59c0,
+ 0x0bbc_3efc_5008_a26a
+ );
+ return new G1Affine(x, y, false);
+ }
+
+ internal G1Affine Neg()
+ {
+ return new G1Affine(this.X, this.IsInfinity ? Fp.One() : this.Y.Neg(), this.IsInfinity);
+ }
+
+ // TODO BLSICP
+ //internal G1Prepared ToPrepared()
+ //{
+ // return this.ToProjective().ToPrepared();
+ //}
+ }
+
+}
diff --git a/src/BLS/Models/G1Projective.cs b/src/BLS/Models/G1Projective.cs
new file mode 100644
index 00000000..f020ff80
--- /dev/null
+++ b/src/BLS/Models/G1Projective.cs
@@ -0,0 +1,483 @@
+using System;
+using System.Collections.Generic;
+
+namespace EdjCase.ICP.BLS.Models
+{
+ internal struct G1Projective
+ {
+ public Fp X { get; }
+ public Fp Y { get; }
+ public Fp Z { get; }
+
+ public G1Projective(Fp x, Fp y, Fp z)
+ {
+ this.X = x;
+ this.Y = y;
+ this.Z = z;
+ }
+
+
+ public G1Affine ToAffine()
+ {
+ Fp zinv = this.Z.Invert() ?? Fp.Zero();
+ if (zinv.IsZero())
+ {
+ return G1Affine.Identity();
+ }
+ Fp x = this.X.Multiply(zinv);
+ Fp y = this.Y.Multiply(zinv);
+ return new(x, y, false);
+ }
+ public static G1Projective FromCompressed(byte[] bytes)
+ {
+ return G1Affine.FromCompressed(bytes).ToProjective();
+ }
+
+ public bool IsIdentity()
+ {
+ return this.Z.IsZero();
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is G2Projective p)
+ {
+ return this.Equals(p);
+ }
+ return false;
+ }
+
+ public bool Equals(G1Projective other)
+ {
+ Fp x1 = this.X * other.Z;
+ Fp x2 = other.X * this.Z;
+
+ Fp y1 = this.Y * other.Z;
+ Fp y2 = other.Y * this.Z;
+
+ bool thisIsZero = this.Z.IsZero();
+ bool otherIsZero = other.Z.IsZero();
+
+ return (thisIsZero && otherIsZero)
+ || (!thisIsZero && !otherIsZero && x1.Equals(x2) && y1.Equals(y2));
+ }
+
+ public G1Projective Add(G1Projective rhs)
+ {
+ Fp t0 = this.X * rhs.X;
+ Fp t1 = this.Y * rhs.Y;
+ Fp t2 = this.Z * rhs.Z;
+ Fp t3 = this.X + this.Y;
+ Fp t4 = rhs.X + rhs.Y;
+ t3 = t3 * t4;
+ t4 = t0 + t1;
+ t3 = t3 - t4;
+ t4 = this.Y + this.Z;
+ Fp x3 = rhs.Y + rhs.Z;
+ t4 = t4 * x3;
+ x3 = t1 + t2;
+ t4 = t4 - x3;
+ x3 = this.X + this.Z;
+ Fp y3 = rhs.X + rhs.Z;
+ x3 = x3 * y3;
+ y3 = t0 + t2;
+ y3 = x3 - y3;
+ x3 = t0 + t0;
+ t0 = x3 + t0;
+ t2 = MultiplyBy3B(t2);
+ Fp z3 = t1 + t2;
+ t1 = t1 - t2;
+ y3 = MultiplyBy3B(y3);
+ x3 = t4 * y3;
+ t2 = t3 * t1;
+ x3 = t2 - x3;
+ y3 = y3 * t0;
+ t1 = t1 * z3;
+ y3 = t1 + y3;
+ t0 = t0 * t3;
+ z3 = z3 * t4;
+ z3 = z3 + t0;
+
+ return new G1Projective(x3, y3, z3);
+ }
+
+ public static G1Projective operator +(G1Projective lhs, G1Projective rhs)
+ {
+ return lhs.Add(rhs);
+ }
+
+ public G1Projective Subtract(G1Projective rhs)
+ {
+ return this + rhs.Neg();
+ }
+
+ public static G1Projective operator -(G1Projective lhs, G1Projective rhs)
+ {
+ return lhs.Subtract(rhs);
+ }
+
+ public static Fp MultiplyBy3B(Fp a)
+ {
+ a = a + a;
+ a = a + a;
+ return a + a + a;
+ }
+
+ internal G1Projective ClearH()
+ {
+ return this - this.MultiplyByX();
+ }
+
+ private G1Projective MultiplyByX()
+ {
+ G1Projective xself = G1Projective.Identity();
+
+ ulong x = Constants.BLS_X >> 1;
+ G1Projective tmp = this;
+ while (x != 0)
+ {
+ tmp = tmp.Double();
+
+ if (x % 2 == 1)
+ {
+ xself += tmp;
+ }
+ x >>= 1;
+ }
+ return xself.Neg();
+ }
+
+ private G1Projective Neg()
+ {
+ return new G1Projective(this.X, this.Y.Neg(), this.Z);
+ }
+
+ private G1Projective Double()
+ {
+ Fp t0 = this.Y.Square();
+ Fp z3 = t0 + t0;
+ z3 = z3 + z3;
+ z3 = z3 + z3;
+ Fp t1 = this.Y * this.Z;
+ Fp t2 = this.Z.Square();
+ t2 = MultiplyBy3B(t2);
+ Fp x3 = t2 * z3;
+ Fp y3 = t0 + t2;
+ z3 = t1 * z3;
+ t1 = t2 + t2;
+ t2 = t1 + t2;
+ t0 = t0 - t2;
+ y3 = t0 * y3;
+ y3 = x3 + y3;
+ t1 = this.X * this.Y;
+ x3 = t0 * t1;
+ x3 = x3 + x3;
+
+ if (this.IsIdentity())
+ {
+ return G1Projective.Identity();
+ }
+
+ return new G1Projective(x3, y3, z3);
+ }
+
+ private static G1Projective Identity()
+ {
+ return new G1Projective(Fp.Zero(), Fp.One(), Fp.Zero());
+ }
+
+
+ public static G1Projective HashToCurve(byte[] message, byte[] dst)
+ {
+ int outputLength = 64;
+ int hashSize = 32;
+ (Fp u1, Fp u2) = HashToFieldFp(message, dst, outputLength, hashSize);
+ G1Projective p1 = MapToCurveG1(u1);
+ G1Projective p2 = MapToCurveG1(u2);
+ return (p1 + p2).ClearH();
+ }
+
+ private static G1Projective MapToCurveG1(Fp u)
+ {
+ Fp usq = u.Square();
+ Fp xi_usq = G1Affine.SSWU_XI * usq;
+ Fp xisq_u4 = xi_usq.Square();
+ Fp nd_common = xisq_u4 + xi_usq;
+ Fp x_den = G1Affine.SSWU_ELLP_A * (nd_common.IsZero() ? G1Affine.SSWU_XI : nd_common.Neg());
+ Fp x0_num = G1Affine.SSWU_ELLP_B * (Fp.One() + nd_common);
+ Fp x_densq = x_den.Square();
+ Fp gx_den = x_densq * x_den;
+ Fp gx0_num = (x0_num.Square() + G1Affine.SSWU_ELLP_A * x_densq) * x0_num + G1Affine.SSWU_ELLP_B * gx_den;
+
+ Fp uV = gx0_num * gx_den;
+ Fp vsq = gx_den.Square();
+ Fp sqrt_candidate = uV * ChainPm3div4(uV * vsq);
+
+ bool gx0_square = (sqrt_candidate.Square() * gx_den).Equals(gx0_num);
+ Fp x1_num = x0_num * xi_usq;
+ Fp y1 = G1Affine.SQRT_M_XI_CUBED * usq * u * sqrt_candidate;
+
+ Fp x_num = gx0_square ? x0_num : x1_num;
+ Fp y = gx0_square ? sqrt_candidate : y1;
+ if (y.Sgn0() ^ u.Sgn0())
+ {
+ y = y.Neg();
+ }
+
+ G1Projective v = new(x_num, y * x_den, x_den);
+
+ return IsoMap(v);
+ }
+
+ private static Fp Square(Fp var0, int var1)
+ {
+ for (int i = 0; i < var1; i++)
+ {
+ var0 = var0.Square();
+ }
+ return var0;
+ }
+
+ private static Fp ChainPm3div4(Fp var0)
+ {
+ Fp var1 = var0.Square();
+ Fp var9 = var1 * var0;
+ Fp var5 = var1.Square();
+ Fp var2 = var9 * var1;
+ Fp var7 = var5 * var9;
+ Fp var10 = var2 * var5;
+ Fp var13 = var7 * var5;
+ Fp var4 = var10 * var5;
+ Fp var8 = var13 * var5;
+ Fp var15 = var4 * var5;
+ Fp var11 = var8 * var5;
+ Fp var3 = var15 * var5;
+ Fp var12 = var11 * var5;
+ var1 = var4.Square();
+ Fp var14 = var12 * var5;
+ Fp var6 = var1 * var9;
+ var5 = var1 * var2;
+ var1 = Square(var1, 12);
+ var1 *= var15;
+ var1 = Square(var1, 7);
+ var1 *= var8;
+ var1 = Square(var1, 4);
+ var1 *= var2;
+ var1 = Square(var1, 6);
+ var1 *= var7;
+ var1 = Square(var1, 7);
+ var1 *= var12;
+ var1 = Square(var1, 5);
+ var1 *= var5;
+ var1 = Square(var1, 2);
+ var1 *= var9;
+ var1 = Square(var1, 6);
+ var1 *= var4;
+ var1 = Square(var1, 6);
+ var1 *= var4;
+ var1 = Square(var1, 6);
+ var1 *= var10;
+ var1 = Square(var1, 3);
+ var1 *= var9;
+ var1 = Square(var1, 7);
+ var1 *= var4;
+ var1 = Square(var1, 4);
+ var1 *= var4;
+ var1 = Square(var1, 6);
+ var1 *= var8;
+ var1 = Square(var1, 6);
+ var1 *= var14;
+ var1 = Square(var1, 3);
+ var1 *= var0;
+ var1 = Square(var1, 8);
+ var1 *= var4;
+ var1 = Square(var1, 7);
+ var1 *= var12;
+ var1 = Square(var1, 5);
+ var1 *= var13;
+ var1 = Square(var1, 6);
+ var1 *= var4;
+ var1 = Square(var1, 6);
+ var1 *= var6;
+ var1 = Square(var1, 4);
+ var1 *= var10;
+ var1 = Square(var1, 8);
+ var1 *= var6;
+ var1 = Square(var1, 4);
+ var1 *= var4;
+ var1 = Square(var1, 7);
+ var1 *= var12;
+ var1 = Square(var1, 9);
+ var1 *= var11;
+ var1 = Square(var1, 2);
+ var1 *= var9;
+ var1 = Square(var1, 5);
+ var1 *= var7;
+ var1 = Square(var1, 7);
+ var1 *= var2;
+ var1 = Square(var1, 7);
+ var1 *= var10;
+ var1 = Square(var1, 6);
+ var1 *= var12;
+ var1 = Square(var1, 5);
+ var1 *= var6;
+ var1 = Square(var1, 5);
+ var1 *= var11;
+ var1 = Square(var1, 5);
+ var1 *= var11;
+ var1 = Square(var1, 8);
+ var1 *= var4;
+ var1 = Square(var1, 7);
+ var1 *= var3;
+ var1 = Square(var1, 9);
+ var1 *= var8;
+ var1 = Square(var1, 5);
+ var1 *= var4;
+ var1 = Square(var1, 3);
+ var1 *= var9;
+ var1 = Square(var1, 8);
+ var1 *= var8;
+ var1 = Square(var1, 3);
+ var1 *= var9;
+ var1 = Square(var1, 7);
+ var1 *= var10;
+ var1 = Square(var1, 9);
+ var1 *= var8;
+ var1 = Square(var1, 6);
+ var1 *= var3;
+ var1 = Square(var1, 6);
+ var1 *= var5;
+ var1 = Square(var1, 5);
+ var1 *= var5;
+ var1 = Square(var1, 5);
+ var1 *= var5;
+ var1 = Square(var1, 4);
+ var1 *= var4;
+ var1 = Square(var1, 3);
+ var1 *= var9;
+ var1 = Square(var1, 8);
+ var1 *= var3;
+ var1 = Square(var1, 7);
+ var1 *= var5;
+ var1 = Square(var1, 5);
+ var1 *= var5;
+ var1 = Square(var1, 5);
+ var1 *= var5;
+ var1 = Square(var1, 4);
+ var1 *= var8;
+ var1 = Square(var1, 4);
+ var1 *= var7;
+ var1 = Square(var1, 7);
+ var1 *= var5;
+ var1 = Square(var1, 5);
+ var1 *= var6;
+ var1 = Square(var1, 5);
+ var1 *= var5;
+ var1 = Square(var1, 5);
+ var1 *= var5;
+ var1 = Square(var1, 5);
+ var1 *= var5;
+ var1 = Square(var1, 5);
+ var1 *= var5;
+ var1 = Square(var1, 5);
+ var1 *= var5;
+ var1 = Square(var1, 5);
+ var1 *= var5;
+ var1 = Square(var1, 4);
+ var1 *= var4;
+ var1 = Square(var1, 6);
+ var1 *= var3;
+ var1 = Square(var1, 4);
+ var1 *= var2;
+ var1 = var1.Square();
+ return var1;
+ }
+
+
+ private static G1Projective IsoMap(G1Projective u)
+ {
+ Fp[][] coeffs = {
+ G1Affine.ISO11_XNUM,
+ G1Affine.ISO11_XDEN,
+ G1Affine.ISO11_YNUM,
+ G1Affine.ISO11_YDEN
+ };
+
+ Fp x = u.X;
+ Fp y = u.Y;
+ Fp z = u.Z;
+
+ Fp[] mapvals = new[] { Fp.Zero(), Fp.Zero(), Fp.Zero(), Fp.Zero() };
+
+ Fp[] zpows = new Fp[15];
+ zpows[0] = z;
+ for (int idx = 1; idx < zpows.Length; idx++)
+ {
+ zpows[idx] = zpows[idx - 1] * z;
+ }
+
+ for (int idx = 0; idx < 4; idx++)
+ {
+ Fp[] coeff = coeffs[idx];
+ int clast = coeff.Length - 1;
+ mapvals[idx] = coeff[clast];
+ for (int jdx = 0; jdx < clast; jdx++)
+ {
+ mapvals[idx] = mapvals[idx] * x + zpows[jdx] * coeff[clast - 1 - jdx];
+ }
+ }
+
+ mapvals[1] *= z;
+
+ mapvals[2] *= y;
+ mapvals[3] *= z;
+
+ return new G1Projective(
+ mapvals[0] * mapvals[3],
+ mapvals[2] * mapvals[1],
+ mapvals[1] * mapvals[3]
+ );
+ }
+
+ private static (Fp, Fp) HashToFieldFp(byte[] message, byte[] dst, int outputLength, int hashSize)
+ {
+ int byteLength = outputLength * 2;
+ Expander ex = Expander.Create(message, dst, byteLength, hashSize);
+ byte[] a = ex.ReadInto(outputLength, hashSize);
+ byte[] b = ex.ReadInto(outputLength, hashSize);
+ return (
+ BlsUtil.FromOkmFp(a),
+ BlsUtil.FromOkmFp(b)
+ );
+ }
+
+ // TODO BLSICP
+ //internal G1Prepared ToPrepared()
+ //{
+ // G1Affine q = this.IsIdentity() ? G1Affine.Generator() : this.ToAffine();
+ // List<(Fp, Fp, Fp)> coeffs = new();
+ // G1Projective cur = this;
+ // BlsUtil.MillerLoop(
+ // 0, // Dummy value instead of void
+ // (f) =>
+ // {
+ // cur = G1Prepared.DoublingStep(cur, out var values);
+ // coeffs.Add(values);
+ // return f; // Dummy
+ // },
+ // (f) =>
+ // {
+ // cur = G1Prepared.AdditionStep(cur, q, out var values);
+ // coeffs.Add(values);
+ // return f; // Dummy
+ // },
+ // (f) => f, // Dummy
+ // (f) => f // Dummy
+ // );
+
+ // return new G1Prepared(q.IsInfinity, coeffs);
+ //}
+
+
+ }
+}
diff --git a/src/BLS/Models/G2Affine.cs b/src/BLS/Models/G2Affine.cs
new file mode 100644
index 00000000..62d53535
--- /dev/null
+++ b/src/BLS/Models/G2Affine.cs
@@ -0,0 +1,533 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+
+namespace EdjCase.ICP.BLS.Models
+{
+ internal struct G2Affine
+ {
+ public Fp2 X { get; }
+ public Fp2 Y { get; }
+ public bool IsInfinity { get; }
+
+ public static readonly Fp2 B;
+
+ public static readonly Fp2 SSWU_XI;
+ public static readonly Fp2 SSWU_ELLP_A;
+ public static readonly Fp2 SSWU_ELLP_B;
+ public static readonly Fp2 SSWU_RV1;
+ public static readonly Fp2[] SSWU_ETAS;
+ public static readonly Fp2[] ISO3_XNUM;
+ public static readonly Fp2[] ISO3_XDEN;
+ public static readonly Fp2[] ISO3_YNUM;
+ public static readonly Fp2[] ISO3_YDEN;
+
+ static G2Affine()
+ {
+ Fp c0 = new(
+ 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 c1 = new(
+ 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 = new Fp2(c0, c1);
+ SSWU_XI = new(
+ new Fp(
+ 0x87eb_ffff_fff9_555c,
+ 0x656f_ffe5_da8f_fffa,
+ 0x0fd0_7493_45d3_3ad2,
+ 0xd951_e663_0665_76f4,
+ 0xde29_1a3d_41e9_80d3,
+ 0x0815_664c_7dfe_040d
+ ),
+ new Fp(
+ 0x43f5_ffff_fffc_aaae,
+ 0x32b7_fff2_ed47_fffd,
+ 0x07e8_3a49_a2e9_9d69,
+ 0xeca8_f331_8332_bb7a,
+ 0xef14_8d1e_a0f4_c069,
+ 0x040a_b326_3eff_0206
+ )
+ );
+ SSWU_ELLP_A = new Fp2(
+ Fp.Zero(),
+ new Fp(
+ 0xe53a_0000_0313_5242,
+ 0x0108_0c0f_def8_0285,
+ 0xe788_9edb_e340_f6bd,
+ 0x0b51_3751_2631_0601,
+ 0x02d6_9857_17c7_44ab,
+ 0x1220_b4e9_79ea_5467
+ )
+ );
+ SSWU_ELLP_B = new Fp2(
+ new Fp(
+ 0x22ea_0000_0cf8_9db2,
+ 0x6ec8_32df_7138_0aa4,
+ 0x6e1b_9440_3db5_a66e,
+ 0x75bf_3c53_a794_73ba,
+ 0x3dd3_a569_412c_0a34,
+ 0x125c_db5e_74dc_4fd1
+ ),
+ new Fp(
+ 0x22ea_0000_0cf8_9db2,
+ 0x6ec8_32df_7138_0aa4,
+ 0x6e1b_9440_3db5_a66e,
+ 0x75bf_3c53_a794_73ba,
+ 0x3dd3_a569_412c_0a34,
+ 0x125c_db5e_74dc_4fd1
+ )
+ );
+ ISO3_XNUM = new[]
+ {
+ new Fp2(
+ new Fp(
+ 0x47f6_71c7_1ce0_5e62,
+ 0x06dd_5707_1206_393e,
+ 0x7c80_cd2a_f3fd_71a2,
+ 0x0481_03ea_9e6c_d062,
+ 0xc545_16ac_c8d0_37f6,
+ 0x1380_8f55_0920_ea41
+ ),
+ new Fp(
+ 0x47f6_71c7_1ce0_5e62,
+ 0x06dd_5707_1206_393e,
+ 0x7c80_cd2a_f3fd_71a2,
+ 0x0481_03ea_9e6c_d062,
+ 0xc545_16ac_c8d0_37f6,
+ 0x1380_8f55_0920_ea41
+ )
+ ),
+ new Fp2(
+ Fp.Zero(),
+ new Fp(
+ 0x5fe5_5555_554c_71d0,
+ 0x873f_ffdd_236a_aaa3,
+ 0x6a6b_4619_b26e_f918,
+ 0x21c2_8884_0887_4945,
+ 0x2836_cda7_028c_abc5,
+ 0x0ac7_3310_a7fd_5abd
+ )
+ ),
+ new Fp2(
+ new Fp(
+ 0x0a0c_5555_5559_71c3,
+ 0xdb0c_0010_1f9e_aaae,
+ 0xb1fb_2f94_1d79_7997,
+ 0xd396_0742_ef41_6e1c,
+ 0xb700_40e2_c205_56f4,
+ 0x149d_7861_e581_393b
+ ),
+ new Fp(
+ 0xaff2_aaaa_aaa6_38e8,
+ 0x439f_ffee_91b5_5551,
+ 0xb535_a30c_d937_7c8c,
+ 0x90e1_4442_0443_a4a2,
+ 0x941b_66d3_8146_55e2,
+ 0x0563_9988_53fe_ad5e
+ )
+ ),
+ new Fp2(
+ new Fp(
+ 0x40aa_c71c_71c7_25ed,
+ 0x1909_5555_7a84_e38e,
+ 0xd817_050a_8f41_abc3,
+ 0xd864_85d4_c87f_6fb1,
+ 0x696e_b479_f885_d059,
+ 0x198e_1a74_3280_02d2
+ ),
+ Fp.Zero()
+ )
+ };
+ ISO3_XDEN = new[]
+ {
+ new Fp2(
+ Fp.Zero(),
+ new Fp(
+ 0x1f3a_ffff_ff13_ab97,
+ 0xf25b_fc61_1da3_ff3e,
+ 0xca37_57cb_3819_b208,
+ 0x3e64_2736_6f8c_ec18,
+ 0x0397_7bc8_6095_b089,
+ 0x04f6_9db1_3f39_a952
+ )
+ ),
+ new Fp2(
+ new Fp(
+ 0x4476_0000_0027_552e,
+ 0xdcb8_009a_4348_0020,
+ 0x6f7e_e9ce_4a6e_8b59,
+ 0xb103_30b7_c0a9_5bc6,
+ 0x6140_b1fc_fb1e_54b7,
+ 0x0381_be09_7f0b_b4e1
+ ),
+ new Fp(
+ 0x7588_ffff_ffd8_557d,
+ 0x41f3_ff64_6e0b_ffdf,
+ 0xf7b1_e8d2_ac42_6aca,
+ 0xb374_1acd_32db_b6f8,
+ 0xe9da_f5b9_482d_581f,
+ 0x167f_53e0_ba74_31b8
+ )
+ ),
+ Fp2.One()
+ };
+ ISO3_YNUM = new[]
+ {
+ new Fp2(
+ new Fp(
+ 0x96d8_f684_bdfc_77be,
+ 0xb530_e4f4_3b66_d0e2,
+ 0x184a_88ff_3796_52fd,
+ 0x57cb_23ec_fae8_04e1,
+ 0x0fd2_e39e_ada3_eba9,
+ 0x08c8_055e_31c5_d5c3
+ ),
+ new Fp(
+ 0x96d8_f684_bdfc_77be,
+ 0xb530_e4f4_3b66_d0e2,
+ 0x184a_88ff_3796_52fd,
+ 0x57cb_23ec_fae8_04e1,
+ 0x0fd2_e39e_ada3_eba9,
+ 0x08c8_055e_31c5_d5c3
+ )
+ ),
+ new Fp2(
+ Fp.Zero(),
+ new Fp(
+ 0xbf0a_71c7_1c91_b406,
+ 0x4d6d_55d2_8b76_38fd,
+ 0x9d82_f98e_5f20_5aee,
+ 0xa27a_a27b_1d1a_18d5,
+ 0x02c3_b2b2_d293_8e86,
+ 0x0c7d_1342_0b09_807f
+ )
+ ),
+ new Fp2(
+ new Fp(
+ 0xd7f9_5555_5553_1c74,
+ 0x21cf_fff7_48da_aaa8,
+ 0x5a9a_d186_6c9b_be46,
+ 0x4870_a221_0221_d251,
+ 0x4a0d_b369_c0a3_2af1,
+ 0x02b1_ccc4_29ff_56af
+ ),
+ new Fp(
+ 0xe205_aaaa_aaac_8e37,
+ 0xfcdc_0007_6879_5556,
+ 0x0c96_011a_8a15_37dd,
+ 0x1c06_a963_f163_406e,
+ 0x010d_f44c_82a8_81e6,
+ 0x174f_4526_0f80_8feb
+ )
+ ),
+ new Fp2(
+ new Fp(
+ 0xa470_bda1_2f67_f35c,
+ 0xc0fe_38e2_3327_b425,
+ 0xc9d3_d0f2_c6f0_678d,
+ 0x1c55_c993_5b5a_982e,
+ 0x27f6_c0e2_f074_6764,
+ 0x117c_5e6e_28aa_9054
+ ),
+ Fp.Zero()
+ )
+ };
+ ISO3_YDEN = new[]
+ {
+ new Fp2(
+ new Fp(
+ 0x0162_ffff_fa76_5adf,
+ 0x8f7b_ea48_0083_fb75,
+ 0x561b_3c22_59e9_3611,
+ 0x11e1_9fc1_a9c8_75d5,
+ 0xca71_3efc_0036_7660,
+ 0x03c6_a03d_41da_1151
+ ),
+ new Fp(
+ 0x0162_ffff_fa76_5adf,
+ 0x8f7b_ea48_0083_fb75,
+ 0x561b_3c22_59e9_3611,
+ 0x11e1_9fc1_a9c8_75d5,
+ 0xca71_3efc_0036_7660,
+ 0x03c6_a03d_41da_1151
+ )
+ ),
+ new Fp2(
+ Fp.Zero(),
+ new Fp(
+ 0x5db0_ffff_fd3b_02c5,
+ 0xd713_f523_58eb_fdba,
+ 0x5ea6_0761_a84d_161a,
+ 0xbb2c_75a3_4ea6_c44a,
+ 0x0ac6_7359_21c1_119b,
+ 0x0ee3_d913_bdac_fbf6
+ )
+ ),
+ new Fp2(
+ new Fp(
+ 0x66b1_0000_003a_ffc5,
+ 0xcb14_00e7_64ec_0030,
+ 0xa73e_5eb5_6fa5_d106,
+ 0x8984_c913_a0fe_09a9,
+ 0x11e1_0afb_78ad_7f13,
+ 0x0542_9d0e_3e91_8f52
+ ),
+ new Fp(
+ 0x534d_ffff_ffc4_aae6,
+ 0x5397_ff17_4c67_ffcf,
+ 0xbff2_73eb_870b_251d,
+ 0xdaf2_8271_5287_0915,
+ 0x393a_9cba_ca9e_2dc3,
+ 0x14be_74db_faee_5748
+ )
+ ),
+ Fp2.One()
+ };
+ SSWU_RV1 = new Fp2(
+ new Fp(
+ 0x7bcf_a7a2_5aa3_0fda,
+ 0xdc17_dec1_2a92_7e7c,
+ 0x2f08_8dd8_6b4e_bef1,
+ 0xd1ca_2087_da74_d4a7,
+ 0x2da2_5966_96ce_bc1d,
+ 0x0e2b_7eed_bbfd_87d2
+ ),
+ new Fp(
+ 0x7bcf_a7a2_5aa3_0fda,
+ 0xdc17_dec1_2a92_7e7c,
+ 0x2f08_8dd8_6b4e_bef1,
+ 0xd1ca_2087_da74_d4a7,
+ 0x2da2_5966_96ce_bc1d,
+ 0x0e2b_7eed_bbfd_87d2
+ )
+ );
+ SSWU_ETAS = new Fp2[]
+ {
+ new Fp2(
+ new Fp(
+ 0x05e5_1466_8ac7_36d2,
+ 0x9089_b4d6_b84f_3ea5,
+ 0x603c_384c_224a_8b32,
+ 0xf325_7909_536a_fea6,
+ 0x5c5c_dbab_ae65_6d81,
+ 0x075b_fa08_63c9_87e9
+ ),
+ new Fp(
+ 0x338d_9bfe_0808_7330,
+ 0x7b8e_48b2_bd83_cefe,
+ 0x530d_ad5d_306b_5be7,
+ 0x5a4d_7e8e_6c40_8b6d,
+ 0x6258_f7a6_232c_ab9b,
+ 0x0b98_5811_cce1_4db5
+ )
+ ),
+ new Fp2(
+ new Fp(
+ 0x8671_6401_f7f7_377b,
+ 0xa31d_b74b_f3d0_3101,
+ 0x1423_2543_c645_9a3c,
+ 0x0a29_ccf6_8744_8752,
+ 0xe8c2_b010_201f_013c,
+ 0x0e68_b9d8_6c9e_98e4
+ ),
+ new Fp(
+ 0x05e5_1466_8ac7_36d2,
+ 0x9089_b4d6_b84f_3ea5,
+ 0x603c_384c_224a_8b32,
+ 0xf325_7909_536a_fea6,
+ 0x5c5c_dbab_ae65_6d81,
+ 0x075b_fa08_63c9_87e9
+ )
+ ),
+ new Fp2(
+ new Fp(
+ 0x718f_dad2_4ee1_d90f,
+ 0xa58c_025b_ed82_76af,
+ 0x0c3a_1023_0ab7_976f,
+ 0xf0c5_4df5_c8f2_75e1,
+ 0x4ec2_478c_28ba_f465,
+ 0x1129_373a_90c5_08e6
+ ),
+ new Fp(
+ 0x019a_f5f9_80a3_680c,
+ 0x4ed7_da0e_6606_3afa,
+ 0x6003_5472_3b5d_9972,
+ 0x8b2f_958b_20d0_9d72,
+ 0x0474_938f_02d4_61db,
+ 0x0dcf_8b9e_0684_ab1c
+ )
+ ),
+ new Fp2(
+ new Fp(
+ 0xb864_0a06_7f5c_429f,
+ 0xcfd4_25f0_4b4d_c505,
+ 0x072d_7e2e_bb53_5cb1,
+ 0xd947_b5f9_d2b4_754d,
+ 0x46a7_1427_4077_4afb,
+ 0x0c31_864c_32fb_3b7e
+ ),
+ new Fp(
+ 0x718f_dad2_4ee1_d90f,
+ 0xa58c_025b_ed82_76af,
+ 0x0c3a_1023_0ab7_976f,
+ 0xf0c5_4df5_c8f2_75e1,
+ 0x4ec2_478c_28ba_f465,
+ 0x1129_373a_90c5_08e6
+ )
+ )
+ };
+ }
+
+ public G2Affine(Fp2 x, Fp2 y, bool isInfinity)
+ {
+ this.X = x;
+ this.Y = y;
+ this.IsInfinity = isInfinity;
+ }
+
+ public static G2Affine Identity()
+ {
+ return new G2Affine(Fp2.Zero(), Fp2.One(), true);
+ }
+
+ public G2Projective ToProjective()
+ {
+ return new G2Projective(this.X, this.Y, this.IsInfinity ? Fp2.Zero() : Fp2.One());
+ }
+
+ public G2Prepared ToPrepared()
+ {
+ return this.ToProjective().ToPrepared();
+ }
+
+ public static G2Affine Generator()
+ {
+ Fp2 x = new(
+ new Fp(
+ 0xF5F28FA202940A10,
+ 0xB3F5FB2687B4961A,
+ 0xA1A893B53E2AE580,
+ 0x9894999D1A3CAEE9,
+ 0x6F67B7631863366B,
+ 0x058191924350BCD7
+ ),
+ new Fp(
+ 0xA5A9C0759E23F606,
+ 0xAAA0C59DBCCD60C3,
+ 0x3BB17E18E2867806,
+ 0x1B1AB6CC8541B367,
+ 0xC2B6ED0EF2158547,
+ 0x11922A097360EDF3
+ )
+ );
+ Fp2 y = new(
+ new Fp(
+ 0x4C730AF860494C4A,
+ 0x597CFA1F5E369C5A,
+ 0xE7E6856CAA0A635A,
+ 0xBBEFB5E96E0D495F,
+ 0x07D3A975F0EF25A2,
+ 0x0083FD8E7E80DAE5
+ ),
+ new Fp(
+ 0xADC0FC92DF64B05D,
+ 0x18AA270A2B1461DC,
+ 0x86ADAC6A3BE4EBA0,
+ 0x79495C4EC93DA33A,
+ 0xE7175850A43CCAED,
+ 0x0B2BC2A163DE1BF2
+ )
+ );
+ return new G2Affine(x, y, false);
+ }
+
+ public bool IsIdentity()
+ {
+ return this.IsInfinity;
+ }
+
+ public static G2Affine FromCompressed(byte[] bytes)
+ {
+ if (bytes.Length != 96)
+ throw new ArgumentException("Byte array must be exactly 96 bytes long.");
+
+ // Obtain the three flags from the start of the byte sequence
+ bool compressionFlagSet = ((bytes[0] >> 7) & 1) == 1;
+ bool infinityFlagSet = ((bytes[0] >> 6) & 1) == 1;
+ bool sortFlagSet = ((bytes[0] >> 5) & 1) == 1;
+
+ // Attempt to obtain the x-coordinate components
+ byte[] xc1Bytes = new byte[48];
+ Array.Copy(bytes, 0, xc1Bytes, 0, 48);
+ xc1Bytes[0] &= 0b0001_1111; // Mask away the flag bits
+ Fp xc1 = Fp.FromBytes(xc1Bytes);
+
+ byte[] xc0Bytes = new byte[48];
+ Array.Copy(bytes, 48, xc0Bytes, 0, 48); // No need to mask flags for xc0
+ Fp xc0 = Fp.FromBytes(xc0Bytes);
+
+ Fp2 x = new Fp2(xc0, xc1);
+
+ if (infinityFlagSet && compressionFlagSet && !sortFlagSet && x.IsZero())
+ {
+ return G2Affine.Identity();
+ }
+ else
+ {
+ // Recover a y-coordinate given x by y = sqrt(x^3 + 4)
+ Fp2 y = (x.Square() * x + B).SquareRoot() ?? throw new Exception("Cant calculate square root");
+
+ // Switch to the correct y-coordinate if necessary.
+ if (y.LexicographicallyLargest() ^ sortFlagSet)
+ {
+ y = y.Neg();
+ }
+
+ bool isValid = !infinityFlagSet && compressionFlagSet;
+ if (!isValid)
+ {
+ throw new ArgumentException("Invalid compressed point");
+ }
+
+ return new G2Affine(x, y, infinityFlagSet);
+ }
+ }
+
+ internal byte[] ToCompressed()
+ {
+ Fp2 x = this.IsInfinity ? Fp2.Zero() : this.X;
+ byte[] bytes = new byte[96];
+
+ Array.Copy(x.C1.ToBytes(), 0, bytes, 0, 48);
+ Array.Copy(x.C0.ToBytes(), 0, bytes, 48, 48);
+
+ bytes[0] |= (byte)(1 << 7);
+
+ bytes[0] |= (byte)(this.IsInfinity ? 1 << 6 : 0);
+
+ bytes[0] |= (byte)(this.IsInfinity ? 0 : (this.Y.LexicographicallyLargest() ? 1 << 5 : 0));
+
+ return bytes;
+ }
+
+ internal G2Affine Neg()
+ {
+ return new G2Affine(this.X, this.IsInfinity ? Fp2.One() : this.Y.Neg(), this.IsInfinity);
+ }
+ }
+
+}
diff --git a/src/BLS/Models/G2Prepared.cs b/src/BLS/Models/G2Prepared.cs
new file mode 100644
index 00000000..7fe88f1b
--- /dev/null
+++ b/src/BLS/Models/G2Prepared.cs
@@ -0,0 +1,83 @@
+using System.Collections.Generic;
+
+namespace EdjCase.ICP.BLS.Models
+{
+ internal class G2Prepared
+ {
+ public bool IsInfinity { get; }
+ public (Fp2, Fp2, Fp2)[] Coefficients { get; }
+
+ public G2Prepared(bool isInfinity, (Fp2, Fp2, Fp2)[] coefficients)
+ {
+ this.IsInfinity = isInfinity;
+ this.Coefficients = coefficients;
+ }
+
+
+ internal static G2Projective DoublingStep(G2Projective value, out (Fp2, Fp2, Fp2) values)
+ {
+ Fp2 tmp0 = value.X.Square();
+ Fp2 tmp1 = value.Y.Square();
+ Fp2 tmp2 = tmp1.Square();
+ Fp2 tmp3 = (tmp1 + value.X).Square() - tmp0 - tmp2;
+ tmp3 += tmp3;
+ Fp2 tmp4 = tmp0 + tmp0 + tmp0;
+ Fp2 tmp6 = value.X + tmp4;
+ Fp2 tmp5 = tmp4.Square();
+ Fp2 zsquared = value.Z.Square();
+ Fp2 newX = tmp5 - tmp3 - tmp3;
+ Fp2 newZ = (value.Z + value.Y).Square() - tmp1 - zsquared;
+ Fp2 newY = (tmp3 - newX) * tmp4;
+ tmp2 += tmp2;
+ tmp2 += tmp2;
+ tmp2 += tmp2;
+ newY -= tmp2;
+ tmp3 = tmp4 * zsquared;
+ tmp3 += tmp3;
+ tmp3 = tmp3.Neg();
+ tmp6 = tmp6.Square() - tmp0 - tmp5;
+ tmp1 += tmp1;
+ tmp1 += tmp1;
+ tmp6 -= tmp1;
+ tmp0 = newZ * zsquared;
+ tmp0 += tmp0;
+
+ values = (tmp0, tmp3, tmp6);
+ return new G2Projective(newX, newY, newZ);
+ }
+
+ internal static G2Projective AdditionStep(G2Projective cur, G2Affine q, out (Fp2, Fp2, Fp2) values)
+ {
+ Fp2 zsquared = cur.Z.Square();
+ Fp2 ysquared = q.Y.Square();
+ Fp2 t0 = zsquared * q.X;
+ Fp2 t1 = ((q.Y + cur.Z).Square() - ysquared - zsquared) * zsquared;
+ Fp2 t2 = t0 - cur.X;
+ Fp2 t3 = t2.Square();
+ Fp2 t4 = t3 + t3;
+ t4 += t4;
+ Fp2 t5 = t4 * t2;
+ Fp2 t6 = t1 - cur.Y - cur.Y;
+ Fp2 t9 = t6 * q.X;
+ Fp2 t7 = t4 * cur.X;
+ Fp2 newX = t6.Square() - t5 - t7 - t7;
+ Fp2 newZ = (cur.Z + t2).Square() - zsquared - t3;
+ Fp2 t10 = q.Y + newZ;
+ Fp2 t8 = (t7 - newX) * t6;
+ t0 = cur.Y * t5;
+ t0 += t0;
+ Fp2 newY = t8 - t0;
+ t10 = t10.Square() - ysquared;
+ var ztsquared = newZ.Square();
+ t10 -= ztsquared;
+ t9 = t9 + t9 - t10;
+ t10 = newZ + newZ;
+ t6 = t6.Neg();
+ t1 = t6 + t6;
+
+ values = (t10, t1, t9);
+
+ return new G2Projective(newX, newY, newZ);
+ }
+ }
+}
diff --git a/src/BLS/Models/G2Projective.cs b/src/BLS/Models/G2Projective.cs
new file mode 100644
index 00000000..85fd1073
--- /dev/null
+++ b/src/BLS/Models/G2Projective.cs
@@ -0,0 +1,694 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.ConstrainedExecution;
+
+namespace EdjCase.ICP.BLS.Models
+{
+ internal struct G2Projective
+ {
+ public Fp2 X { get; }
+ public Fp2 Y { get; }
+ public Fp2 Z { get; }
+
+ public G2Projective(Fp2 x, Fp2 y, Fp2 z)
+ {
+ this.X = x;
+ this.Y = y;
+ this.Z = z;
+ }
+
+
+ public G2Affine ToAffine()
+ {
+ Fp2 zinv = this.Z.Invert() ?? Fp2.Zero();
+ if (zinv.IsZero())
+ {
+ return G2Affine.Identity();
+ }
+ Fp2 x = this.X.Multiply(zinv);
+ Fp2 y = this.Y.Multiply(zinv);
+ return new(x, y, false);
+ }
+
+ public static G2Projective FromCompressed(byte[] bytes)
+ {
+ return G2Affine.FromCompressed(bytes).ToProjective();
+ }
+
+ internal byte[] ToCompressed()
+ {
+ return this.ToAffine().ToCompressed();
+ }
+
+ public G2Projective Add(G2Projective rhs)
+ {
+ Fp2 t0 = this.X * rhs.X;
+ Fp2 t1 = this.Y * rhs.Y;
+ Fp2 t2 = this.Z * rhs.Z;
+ Fp2 t3 = this.X + this.Y;
+ Fp2 t4 = rhs.X + rhs.Y;
+ t3 = t3 * t4;
+ t4 = t0 + t1;
+ t3 = t3 - t4;
+ t4 = this.Y + this.Z;
+ Fp2 x3 = rhs.Y + rhs.Z;
+ t4 = t4 * x3;
+ x3 = t1 + t2;
+ t4 = t4 - x3;
+ x3 = this.X + this.Z;
+ Fp2 y3 = rhs.X + rhs.Z;
+ x3 = x3 * y3;
+ y3 = t0 + t2;
+ y3 = x3 - y3;
+ x3 = t0 + t0;
+ t0 = x3 + t0;
+ t2 = t2 * Fp2.B3;
+ Fp2 z3 = t1 + t2;
+ t1 = t1 - t2;
+ y3 = y3 * Fp2.B3;
+ x3 = t4 * y3;
+ t2 = t3 * t1;
+ x3 = t2 - x3;
+ y3 = y3 * t0;
+ t1 = t1 * z3;
+ y3 = t1 + y3;
+ t0 = t0 * t3;
+ z3 = z3 * t4;
+ z3 = z3 + t0;
+
+ return new G2Projective(x3, y3, z3);
+ }
+
+ public static G2Projective operator +(G2Projective lhs, G2Projective rhs)
+ {
+ return lhs.Add(rhs);
+ }
+
+
+ private G2Projective Subtract(G2Projective rhs)
+ {
+ return this.Add(rhs.Neg());
+ }
+
+ public static G2Projective operator -(G2Projective lhs, G2Projective rhs)
+ {
+ return lhs.Subtract(rhs);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is G2Projective p)
+ {
+ return this.Equals(p);
+ }
+ return false;
+ }
+
+ public bool Equals(G2Projective other)
+ {
+ Fp2 x1 = this.X * other.Z;
+ Fp2 x2 = other.X * this.Z;
+
+ Fp2 y1 = this.Y * other.Z;
+ Fp2 y2 = other.Y * this.Z;
+
+ bool thisIsZero = this.Z.IsZero();
+ bool otherIsZero = other.Z.IsZero();
+
+ return (thisIsZero && otherIsZero)
+ || (!thisIsZero && !otherIsZero && x1.Equals(x2) && y1.Equals(y2));
+ }
+
+
+ internal bool IsIdentity()
+ {
+ return this.Z.IsZero();
+ }
+
+ internal G2Projective ClearH()
+ {
+ G2Projective t1 = this.MultiplyByX();
+ G2Projective t2 = this.Psi();
+ return this.Double()
+ .Psi2()
+ .Add(t1
+ .Add(t2)
+ .MultiplyByX()
+ .Subtract(t1)
+ .Subtract(t2)
+ .Subtract(this)
+ );
+ }
+
+ private G2Projective Psi2()
+ {
+ Fp2 psi2CoeffX = new(
+ new Fp(
+ 0xcd03c9e48671f071,
+ 0x5dab22461fcda5d2,
+ 0x587042afd3851b95,
+ 0x8eb60ebe01bacb9e,
+ 0x03f97d6e83d050d2,
+ 0x18f0206554638741
+ ),
+ Fp.Zero()
+ );
+
+ return new G2Projective(
+ this.X * psi2CoeffX,
+ this.Y.Neg(),
+ this.Z
+ );
+ }
+
+ private G2Projective Psi()
+ {
+ Fp2 psiCoeffX = new(
+ Fp.Zero(),
+ new Fp(
+ 0x890dc9e4867545c3,
+ 0x2af322533285a5d5,
+ 0x50880866309b7e2c,
+ 0xa20d1b8c7e881024,
+ 0x14e4f04fe2db9068,
+ 0x14e56d3f1564853a
+ )
+ );
+ Fp2 psiCoeffY = new(
+ new Fp(
+ 0x3e2f585da55c9ad1,
+ 0x4294213d86c18183,
+ 0x382844c88b623732,
+ 0x92ad2afd19103e18,
+ 0x1d794e4fac7cf0b9,
+ 0x0bd592fc7d825ec8
+ ),
+ new Fp(
+ 0x7bcfa7a25aa30fda,
+ 0xdc17dec12a927e7c,
+ 0x2f088dd86b4ebef1,
+ 0xd1ca2087da74d4a7,
+ 0x2da2596696cebc1d,
+ 0x0e2b7eedbbfd87d2
+ )
+ );
+
+ return new G2Projective(
+ this.X.FrobeniusMap() * psiCoeffX,
+ this.Y.FrobeniusMap() * psiCoeffY,
+ this.Z.FrobeniusMap()
+ );
+ }
+
+ private G2Projective MultiplyByX()
+ {
+ G2Projective xself = G2Projective.Identity();
+ ulong x = Constants.BLS_X >> 1;
+ G2Projective acc = this;
+ while (x != 0)
+ {
+ acc = acc.Double();
+ if (x % 2 == 1)
+ {
+ xself += acc;
+ }
+ x >>= 1;
+ }
+ xself = xself.Neg();
+ return xself;
+ }
+
+ private G2Projective Neg()
+ {
+ return new G2Projective(this.X, this.Y.Neg(), this.Z);
+ }
+
+ private static G2Projective Identity()
+ {
+ return new G2Projective(Fp2.Zero(), Fp2.One(), Fp2.Zero());
+ }
+
+ private G2Projective Double()
+ {
+ Fp2 t0 = this.Y.Square();
+ Fp2 z3 = t0 + t0;
+ z3 = z3 + z3;
+ z3 = z3 + z3;
+ Fp2 t1 = this.Y * this.Z;
+ Fp2 t2 = this.Z.Square();
+ t2 = t2 * Fp2.B3;
+ Fp2 x3 = t2 * z3;
+ Fp2 y3 = t0 + t2;
+ z3 = t1 * z3;
+ t1 = t2 + t2;
+ t2 = t1 + t2;
+ t0 = t0 - t2;
+ y3 = t0 * y3;
+ y3 = x3 + y3;
+ t1 = this.X * this.Y;
+ x3 = t0 * t1;
+ x3 = x3 + x3;
+
+
+ G2Projective a = new(x3, y3, z3);
+ if (a.IsIdentity())
+ {
+ return G2Projective.Identity();
+ }
+ return a;
+ }
+
+
+
+ public static G2Projective HashToCurve(byte[] message, byte[] dst)
+ {
+ int outputLength = 128;
+ int hashSize = 32;
+ (Fp2 u1, Fp2 u2) = HashToFieldF2(message, dst, outputLength, hashSize);
+ G2Projective p1 = MapToCurveG2(u1);
+ G2Projective p2 = MapToCurveG2(u2);
+ return (p1 + p2).ClearH();
+ }
+
+ private static G2Projective MapToCurveG2(Fp2 u)
+ {
+ Fp2 usq = u.Square();
+ Fp2 xi_usq = G2Affine.SSWU_XI * usq;
+ Fp2 xisq_u4 = xi_usq.Square();
+ Fp2 nd_common = xisq_u4 + xi_usq;
+ Fp2 x_den = G2Affine.SSWU_ELLP_A * (nd_common.IsZero() ? G2Affine.SSWU_XI : nd_common.Neg());
+ Fp2 x0_num = G2Affine.SSWU_ELLP_B * (Fp2.One() + nd_common);
+ Fp2 x_densq = x_den.Square();
+ Fp2 gx_den = x_densq * x_den;
+ Fp2 gx0_num = (x0_num.Square() + G2Affine.SSWU_ELLP_A * x_densq) * x0_num + G2Affine.SSWU_ELLP_B * gx_den;
+ Fp2 vsq = gx_den.Square();
+ Fp2 v_3 = vsq * gx_den;
+ Fp2 v_4 = vsq.Square();
+ Fp2 uv_7 = gx0_num * v_3 * v_4;
+ Fp2 uv_15 = uv_7 * v_4.Square();
+ Fp2 sqrt_candidate = uv_7 * ChainP2m9div16(uv_15);
+ Fp2 y = sqrt_candidate;
+ Fp2 tmp = new Fp2(sqrt_candidate.C1.Neg(), sqrt_candidate.C0);
+ if ((tmp.Square() * gx_den).Equals(gx0_num))
+ {
+ y = tmp;
+ }
+ tmp = sqrt_candidate * G2Affine.SSWU_RV1;
+ if ((tmp.Square() * gx_den).Equals(gx0_num))
+ {
+ y = tmp;
+ }
+ tmp = new Fp2(tmp.C1, tmp.C0.Neg());
+ if ((tmp.Square() * gx_den).Equals(gx0_num))
+ {
+ y = tmp;
+ }
+ Fp2 gx1_num = gx0_num * xi_usq * xisq_u4;
+ sqrt_candidate = sqrt_candidate * usq * u;
+ bool eta_found = false;
+ foreach (Fp2 eta in G2Affine.SSWU_ETAS)
+ {
+ tmp = sqrt_candidate * eta;
+ if ((tmp.Square() * gx_den).Equals(gx1_num))
+ {
+ y = tmp;
+ eta_found = true;
+ }
+ }
+ Fp2 x_num = eta_found ? x0_num * xi_usq : x0_num;
+ if (u.Sgn0() ^ y.Sgn0())
+ {
+ y = y.Neg();
+ }
+
+ G2Projective v = new(x_num, y * x_den, x_den);
+ return IsoMap(v);
+ }
+
+ private static Fp2 Square(Fp2 var0, int var1)
+ {
+ for (int i = 0; i < var1; i++)
+ {
+ var0 = var0.Square();
+ }
+ return var0;
+ }
+
+ private static Fp2 ChainP2m9div16(Fp2 var0)
+ {
+ Fp2 var1 = var0.Square();
+ Fp2 var2 = var1 * var0;
+ Fp2 var15 = var2 * var1;
+ Fp2 var3 = var15 * var1;
+ Fp2 var14 = var3 * var1;
+ Fp2 var13 = var14 * var1;
+ Fp2 var5 = var13 * var1;
+ Fp2 var10 = var5 * var1;
+ Fp2 var9 = var10 * var1;
+ Fp2 var16 = var9 * var1;
+ Fp2 var4 = var16 * var1;
+ Fp2 var7 = var4 * var1;
+ Fp2 var6 = var7 * var1;
+ Fp2 var12 = var6 * var1;
+ Fp2 var8 = var12 * var1;
+ Fp2 var11 = var8 * var1;
+ var1 = var4.Square();
+ var1 = Square(var1, 2);
+ var1 *= var0;
+ var1 = Square(var1, 9);
+ var1 *= var12;
+ var1 = Square(var1, 4);
+ var1 *= var5;
+ var1 = Square(var1, 6);
+ var1 *= var14;
+ var1 = Square(var1, 4);
+ var1 *= var3;
+ var1 = Square(var1, 5);
+ var1 *= var2;
+ var1 = Square(var1, 8);
+ var1 *= var5;
+ var1 = Square(var1, 4);
+ var1 *= var3;
+ var1 = Square(var1, 4);
+ var1 *= var10;
+ var1 = Square(var1, 8);
+ var1 *= var8;
+ var1 = Square(var1, 6);
+ var1 *= var13;
+ var1 = Square(var1, 4);
+ var1 *= var5;
+ var1 = Square(var1, 3);
+ var1 *= var0;
+ var1 = Square(var1, 6);
+ var1 *= var10;
+ var1 = Square(var1, 8);
+ var1 *= var8;
+ var1 = Square(var1, 6);
+ var1 *= var4;
+ var1 = Square(var1, 8);
+ var1 *= var9;
+ var1 = Square(var1, 5);
+ var1 *= var10;
+ var1 = Square(var1, 6);
+ var1 *= var14;
+ var1 = Square(var1, 5);
+ var1 *= var10;
+ var1 = Square(var1, 2);
+ var1 *= var0;
+ var1 = Square(var1, 6);
+ var1 *= var10;
+ var1 = Square(var1, 7);
+ var1 *= var13;
+ var1 = Square(var1, 4);
+ var1 *= var3;
+ var1 = Square(var1, 6);
+ var1 *= var14;
+ var1 = Square(var1, 7);
+ var1 *= var3;
+ var1 = Square(var1, 5);
+ var1 *= var15;
+ var1 = Square(var1, 7);
+ var1 *= var3;
+ var1 = Square(var1, 5);
+ var1 *= var3;
+ var1 = Square(var1, 10);
+ var1 *= var9;
+ var1 = Square(var1, 3);
+ var1 *= var15;
+ var1 = Square(var1, 5);
+ var1 *= var5;
+ var1 = Square(var1, 8);
+ var1 *= var6;
+ var1 = Square(var1, 5);
+ var1 *= var7;
+ var1 = Square(var1, 6);
+ var1 *= var13;
+ var1 = Square(var1, 6);
+ var1 *= var10;
+ var1 = Square(var1, 6);
+ var1 *= var14;
+ var1 = Square(var1, 7);
+ var1 *= var16;
+ var1 = Square(var1, 5);
+ var1 *= var14;
+ var1 = Square(var1, 6);
+ var1 *= var10;
+ var1 = Square(var1, 6);
+ var1 *= var9;
+ var1 = Square(var1, 5);
+ var1 *= var10;
+ var1 = Square(var1, 2);
+ var1 *= var0;
+ var1 = Square(var1, 8);
+ var1 *= var15;
+ var1 = Square(var1, 7);
+ var1 *= var15;
+ var1 = Square(var1, 4);
+ var1 *= var2;
+ var1 = Square(var1, 7);
+ var1 *= var13;
+ var1 = Square(var1, 6);
+ var1 *= var10;
+ var1 = Square(var1, 4);
+ var1 *= var5;
+ var1 = Square(var1, 7);
+ var1 *= var13;
+ var1 = Square(var1, 6);
+ var1 *= var12;
+ var1 = Square(var1, 5);
+ var1 *= var7;
+ var1 = Square(var1, 5);
+ var1 *= var15;
+ var1 = Square(var1, 7);
+ var1 *= var12;
+ var1 = Square(var1, 5);
+ var1 *= var7;
+ var1 = Square(var1, 5);
+ var1 *= var4;
+ var1 = Square(var1, 4);
+ var1 *= var2;
+ var1 = Square(var1, 6);
+ var1 *= var15;
+ var1 = Square(var1, 6);
+ var1 *= var14;
+ var1 = Square(var1, 4);
+ var1 *= var2;
+ var1 = Square(var1, 4);
+ var1 *= var2;
+ var1 = Square(var1, 8);
+ var1 *= var14;
+ var1 = Square(var1, 5);
+ var1 *= var10;
+ var1 = Square(var1, 6);
+ var1 *= var3;
+ var1 = Square(var1, 5);
+ var1 *= var10;
+ var1 = Square(var1, 12);
+ var1 *= var9;
+ var1 = Square(var1, 4);
+ var1 *= var5;
+ var1 = Square(var1, 5);
+ var1 *= var5;
+ var1 = Square(var1, 6);
+ var1 *= var2;
+ var1 = Square(var1, 9);
+ var1 *= var6;
+ var1 = Square(var1, 5);
+ var1 *= var6;
+ var1 = Square(var1, 6);
+ var1 *= var2;
+ var1 = Square(var1, 6);
+ var1 *= var2;
+ var1 = Square(var1, 9);
+ var1 *= var7;
+ var1 = Square(var1, 7);
+ var1 *= var10;
+ var1 = Square(var1, 6);
+ var1 *= var6;
+ var1 = Square(var1, 5);
+ var1 *= var14;
+ var1 = Square(var1, 7);
+ var1 *= var7;
+ var1 = Square(var1, 2);
+ var1 *= var0;
+ var1 = Square(var1, 8);
+ var1 *= var13;
+ var1 = Square(var1, 4);
+ var1 *= var15;
+ var1 = Square(var1, 7);
+ var1 *= var3;
+ var1 = Square(var1, 8);
+ var1 *= var14;
+ var1 = Square(var1, 7);
+ var1 *= var5;
+ var1 = Square(var1, 10);
+ var1 *= var14;
+ var1 = Square(var1, 6);
+ var1 *= var13;
+ var1 = Square(var1, 6);
+ var1 *= var5;
+ var1 = Square(var1, 6);
+ var1 *= var11;
+ var1 = Square(var1, 5);
+ var1 *= var6;
+ var1 = Square(var1, 7);
+ var1 *= var10;
+ var1 = Square(var1, 5);
+ var1 *= var5;
+ var1 = Square(var1, 7);
+ var1 *= var11;
+ var1 = Square(var1, 5);
+ var1 *= var3;
+ var1 = Square(var1, 8);
+ var1 *= var12;
+ var1 = Square(var1, 6);
+ var1 *= var8;
+ var1 = Square(var1, 6);
+ var1 *= var2;
+ var1 = Square(var1, 7);
+ var1 *= var13;
+ var1 = Square(var1, 7);
+ var1 *= var13;
+ var1 = Square(var1, 6);
+ var1 *= var2;
+ var1 = Square(var1, 5);
+ var1 *= var3;
+ var1 = Square(var1, 10);
+ var1 *= var12;
+ var1 = Square(var1, 4);
+ var1 *= var0;
+ var1 = Square(var1, 9);
+ var1 *= var9;
+ var1 = Square(var1, 6);
+ var1 *= var10;
+ var1 = Square(var1, 7);
+ var1 *= var11;
+ var1 = Square(var1, 5);
+ var1 *= var4;
+ var1 = Square(var1, 4);
+ var1 *= var10;
+ var1 = Square(var1, 7);
+ var1 *= var8;
+ var1 = Square(var1, 5);
+ var1 *= var4;
+ var1 = Square(var1, 5);
+ var1 *= var4;
+ var1 = Square(var1, 5);
+ var1 *= var9;
+ var1 = Square(var1, 4);
+ var1 *= var5;
+ var1 = Square(var1, 6);
+ var1 *= var8;
+ var1 = var1.Square();
+ var1 *= var0;
+ var1 = Square(var1, 6);
+ var1 *= var3;
+ var1 = Square(var1, 10);
+ var1 *= var7;
+ var1 = Square(var1, 6);
+ var1 *= var4;
+ var1 = Square(var1, 6);
+ var1 *= var6;
+ var1 = Square(var1, 6);
+ var1 *= var5;
+ var1 = Square(var1, 6);
+ var1 *= var4;
+ var1 = Square(var1, 23);
+ var1 *= var3;
+ var1 = Square(var1, 6);
+ var1 *= var3;
+ var1 = Square(var1, 5);
+ var1 *= var2;
+ var1 = Square(var1, 6);
+ var1 *= var3;
+ var1 = Square(var1, 5);
+ return var1 * var2;
+ }
+
+ private static G2Projective IsoMap(G2Projective u)
+ {
+ Fp2[][] coeffs = {
+ G2Affine.ISO3_XNUM,
+ G2Affine.ISO3_XDEN,
+ G2Affine.ISO3_YNUM,
+ G2Affine.ISO3_YDEN
+ };
+
+ Fp2 x = u.X;
+ Fp2 y = u.Y;
+ Fp2 z = u.Z;
+
+ Fp2[] mapvals = new[] { Fp2.Zero(), Fp2.Zero(), Fp2.Zero(), Fp2.Zero() };
+
+ Fp2 zsq = z.Square();
+ Fp2[] zpows = new[] { z, zsq, zsq * z };
+
+ for (int idx = 0; idx < 4; idx++)
+ {
+ Fp2[] coeff = coeffs[idx];
+ int clast = coeff.Length - 1;
+ mapvals[idx] = coeff[clast];
+ for (int jdx = 0; jdx < clast; jdx++)
+ {
+ mapvals[idx] = mapvals[idx] * x + zpows[jdx] * coeff[clast - 1 - jdx];
+ }
+ }
+
+ mapvals[1] *= z;
+
+ mapvals[2] *= y;
+ mapvals[3] *= z;
+
+ return new G2Projective(mapvals[0] * mapvals[3], mapvals[2] * mapvals[1], mapvals[1] * mapvals[3]);
+ }
+
+ private static (Fp2, Fp2) HashToFieldF2(byte[] message, byte[] dst, int outputLength, int hashSize)
+ {
+ const int byteLength = 256;
+ Expander ex = Expander.Create(message, dst, byteLength, hashSize);
+ byte[] a = ex.ReadInto(outputLength, hashSize);
+ byte[] b = ex.ReadInto(outputLength, hashSize);
+ return (
+ FromOkmFp2(a),
+ FromOkmFp2(b)
+ );
+ }
+
+ private static Fp2 FromOkmFp2(byte[] okm)
+ {
+ if (okm.Length != 128)
+ {
+ throw new ArgumentException("Invalid OKM length");
+ }
+ Fp c0 = BlsUtil.FromOkmFp(okm[..64]);
+ Fp c1 = BlsUtil.FromOkmFp(okm[64..]);
+ return new Fp2(c0, c1);
+ }
+
+ internal G2Prepared ToPrepared()
+ {
+ G2Affine q = this.IsIdentity() ? G2Affine.Generator() : this.ToAffine();
+ List<(Fp2, Fp2, Fp2)> coeffs = new();
+ G2Projective cur = this;
+ BlsUtil.MillerLoop(
+ 0, // Dummy value instead of void
+ (f) =>
+ {
+ cur = G2Prepared.DoublingStep(cur, out var values);
+ coeffs.Add(values);
+ return f; // Dummy
+ },
+ (f) =>
+ {
+ cur = G2Prepared.AdditionStep(cur, q, out var values);
+ coeffs.Add(values);
+ return f; // Dummy
+ },
+ (f) => f, // Dummy
+ (f) => f // Dummy
+ );
+
+ return new G2Prepared(q.IsInfinity, coeffs.ToArray());
+ }
+ }
+}
diff --git a/src/BLS/Models/UInt128.cs b/src/BLS/Models/UInt128.cs
new file mode 100644
index 00000000..686ab4fc
--- /dev/null
+++ b/src/BLS/Models/UInt128.cs
@@ -0,0 +1,2639 @@
+// https://github.com/ricksladkey/dirichlet-numerics/blob/master/Dirichlet.Numerics/UInt128.cs
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Numerics;
+
+namespace Dirichlet.Numerics
+{
+ internal struct UInt128 : IFormattable, IComparable, IComparable, IEquatable
+ {
+ private struct UInt256
+ {
+ public ulong s0;
+ public ulong s1;
+ public ulong s2;
+ public ulong s3;
+
+ public uint r0 { get { return (uint)this.s0; } }
+ public uint r1 { get { return (uint)(this.s0 >> 32); } }
+ public uint r2 { get { return (uint)this.s1; } }
+ public uint r3 { get { return (uint)(this.s1 >> 32); } }
+ public uint r4 { get { return (uint)this.s2; } }
+ public uint r5 { get { return (uint)(this.s2 >> 32); } }
+ public uint r6 { get { return (uint)this.s3; } }
+ public uint r7 { get { return (uint)(this.s3 >> 32); } }
+
+ public UInt128 t0 { get { UInt128 result; UInt128.Create(out result, this.s0, this.s1); return result; } }
+ public UInt128 t1 { get { UInt128 result; UInt128.Create(out result, this.s2, this.s3); return result; } }
+
+ public static implicit operator BigInteger(UInt256 a)
+ {
+ return (BigInteger)a.s3 << 192 | (BigInteger)a.s2 << 128 | (BigInteger)a.s1 << 64 | a.s0;
+ }
+
+ public override string ToString()
+ {
+ return ((BigInteger)this).ToString();
+ }
+ }
+
+ private ulong s0;
+ private ulong s1;
+
+ private static readonly UInt128 maxValue = ~(UInt128)0;
+ private static readonly UInt128 zero = (UInt128)0;
+ private static readonly UInt128 one = (UInt128)1;
+
+ public static UInt128 MinValue { get { return zero; } }
+ public static UInt128 MaxValue { get { return maxValue; } }
+ public static UInt128 Zero { get { return zero; } }
+ public static UInt128 One { get { return one; } }
+
+ public static UInt128 Parse(string value)
+ {
+ UInt128 c;
+ if (!TryParse(value, out c))
+ throw new FormatException();
+ return c;
+ }
+
+ public static bool TryParse(string value, out UInt128 result)
+ {
+ return TryParse(value, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
+ }
+
+ public static bool TryParse(string value, NumberStyles style, IFormatProvider provider, out UInt128 result)
+ {
+ BigInteger a;
+ if (!BigInteger.TryParse(value, style, provider, out a))
+ {
+ result = Zero;
+ return false;
+ }
+ Create(out result, a);
+ return true;
+ }
+
+ public UInt128(long value)
+ {
+ Create(out this, value);
+ }
+
+ public UInt128(ulong value)
+ {
+ Create(out this, value);
+ }
+
+ public UInt128(decimal value)
+ {
+ Create(out this, value);
+ }
+
+ public UInt128(double value)
+ {
+ Create(out this, value);
+ }
+
+ public UInt128(BigInteger value)
+ {
+ Create(out this, value);
+ }
+
+ public static void Create(out UInt128 c, uint r0, uint r1, uint r2, uint r3)
+ {
+ c.s0 = (ulong)r1 << 32 | r0;
+ c.s1 = (ulong)r3 << 32 | r2;
+ }
+
+ public static void Create(out UInt128 c, ulong s0, ulong s1)
+ {
+ c.s0 = s0;
+ c.s1 = s1;
+ }
+
+ public static void Create(out UInt128 c, long a)
+ {
+ c.s0 = (ulong)a;
+ c.s1 = a < 0 ? ulong.MaxValue : 0;
+ }
+
+ public static void Create(out UInt128 c, ulong a)
+ {
+ c.s0 = a;
+ c.s1 = 0;
+ }
+
+ public static void Create(out UInt128 c, decimal a)
+ {
+ var bits = decimal.GetBits(decimal.Truncate(a));
+ Create(out c, (uint)bits[0], (uint)bits[1], (uint)bits[2], 0);
+ if (a < 0)
+ Negate(ref c);
+ }
+
+ public static void Create(out UInt128 c, BigInteger a)
+ {
+ var sign = a.Sign;
+ if (sign == -1)
+ a = -a;
+ c.s0 = (ulong)(a & ulong.MaxValue);
+ c.s1 = (ulong)(a >> 64);
+ if (sign == -1)
+ Negate(ref c);
+ }
+
+ public static void Create(out UInt128 c, double a)
+ {
+ var negate = false;
+ if (a < 0)
+ {
+ negate = true;
+ a = -a;
+ }
+ if (a <= ulong.MaxValue)
+ {
+ c.s0 = (ulong)a;
+ c.s1 = 0;
+ }
+ else
+ {
+ var shift = Math.Max((int)Math.Ceiling(Math.Log(a, 2)) - 63, 0);
+ c.s0 = (ulong)(a / Math.Pow(2, shift));
+ c.s1 = 0;
+ LeftShift(ref c, shift);
+ }
+ if (negate)
+ Negate(ref c);
+ }
+
+ private uint r0 { get { return (uint)this.s0; } }
+ private uint r1 { get { return (uint)(this.s0 >> 32); } }
+ private uint r2 { get { return (uint)this.s1; } }
+ private uint r3 { get { return (uint)(this.s1 >> 32); } }
+
+ public ulong S0 { get { return this.s0; } }
+ public ulong S1 { get { return this.s1; } }
+
+ public bool IsZero { get { return (this.s0 | this.s1) == 0; } }
+ public bool IsOne { get { return this.s1 == 0 && this.s0 == 1; } }
+ public bool IsPowerOfTwo { get { return (this & (this - 1)).IsZero; } }
+ public bool IsEven { get { return (this.s0 & 1) == 0; } }
+ public int Sign { get { return this.IsZero ? 0 : 1; } }
+
+ public override string ToString()
+ {
+ return ((BigInteger)this).ToString();
+ }
+
+ public string ToString(string format)
+ {
+ return ((BigInteger)this).ToString(format);
+ }
+
+ public string ToString(IFormatProvider provider)
+ {
+ return this.ToString(null, provider);
+ }
+
+ public string ToString(string format, IFormatProvider provider)
+ {
+ return ((BigInteger)this).ToString(format, provider);
+ }
+
+ public static explicit operator UInt128(double a)
+ {
+ UInt128 c;
+ Create(out c, a);
+ return c;
+ }
+
+ public static explicit operator UInt128(sbyte a)
+ {
+ UInt128 c;
+ Create(out c, a);
+ return c;
+ }
+
+ public static implicit operator UInt128(byte a)
+ {
+ UInt128 c;
+ Create(out c, a);
+ return c;
+ }
+
+ public static explicit operator UInt128(short a)
+ {
+ UInt128 c;
+ Create(out c, a);
+ return c;
+ }
+
+ public static implicit operator UInt128(ushort a)
+ {
+ UInt128 c;
+ Create(out c, a);
+ return c;
+ }
+
+ public static explicit operator UInt128(int a)
+ {
+ UInt128 c;
+ Create(out c, a);
+ return c;
+ }
+
+ public static implicit operator UInt128(uint a)
+ {
+ UInt128 c;
+ Create(out c, a);
+ return c;
+ }
+
+ public static explicit operator UInt128(long a)
+ {
+ UInt128 c;
+ Create(out c, a);
+ return c;
+ }
+
+ public static implicit operator UInt128(ulong a)
+ {
+ UInt128 c;
+ Create(out c, a);
+ return c;
+ }
+
+ public static explicit operator UInt128(decimal a)
+ {
+ UInt128 c;
+ Create(out c, a);
+ return c;
+ }
+
+ public static explicit operator UInt128(BigInteger a)
+ {
+ UInt128 c;
+ Create(out c, a);
+ return c;
+ }
+
+ public static explicit operator float(UInt128 a)
+ {
+ return ConvertToFloat(ref a);
+ }
+
+ public static explicit operator double(UInt128 a)
+ {
+ return ConvertToDouble(ref a);
+ }
+
+ public static float ConvertToFloat(ref UInt128 a)
+ {
+ if (a.s1 == 0)
+ return a.s0;
+ return a.s1 * (float)ulong.MaxValue + a.s0;
+ }
+
+ public static double ConvertToDouble(ref UInt128 a)
+ {
+ if (a.s1 == 0)
+ return a.s0;
+ return a.s1 * (double)ulong.MaxValue + a.s0;
+ }
+
+ public static explicit operator sbyte(UInt128 a)
+ {
+ return (sbyte)a.s0;
+ }
+
+ public static explicit operator byte(UInt128 a)
+ {
+ return (byte)a.s0;
+ }
+
+ public static explicit operator short(UInt128 a)
+ {
+ return (short)a.s0;
+ }
+
+ public static explicit operator ushort(UInt128 a)
+ {
+ return (ushort)a.s0;
+ }
+
+ public static explicit operator int(UInt128 a)
+ {
+ return (int)a.s0;
+ }
+
+ public static explicit operator uint(UInt128 a)
+ {
+ return (uint)a.s0;
+ }
+
+ public static explicit operator long(UInt128 a)
+ {
+ return (long)a.s0;
+ }
+
+ public static explicit operator ulong(UInt128 a)
+ {
+ return a.s0;
+ }
+
+ public static explicit operator decimal(UInt128 a)
+ {
+ if (a.s1 == 0)
+ return a.s0;
+ var shift = Math.Max(0, 32 - GetBitLength(a.s1));
+ UInt128 ashift;
+ RightShift(out ashift, ref a, shift);
+ return new decimal((int)a.r0, (int)a.r1, (int)a.r2, false, (byte)shift);
+ }
+
+ public static implicit operator BigInteger(UInt128 a)
+ {
+ if (a.s1 == 0)
+ return a.s0;
+ return (BigInteger)a.s1 << 64 | a.s0;
+ }
+
+ public static UInt128 operator <<(UInt128 a, int b)
+ {
+ UInt128 c;
+ LeftShift(out c, ref a, b);
+ return c;
+ }
+
+ public static UInt128 operator >>(UInt128 a, int b)
+ {
+ UInt128 c;
+ RightShift(out c, ref a, b);
+ return c;
+ }
+
+ public static UInt128 operator &(UInt128 a, UInt128 b)
+ {
+ UInt128 c;
+ And(out c, ref a, ref b);
+ return c;
+ }
+
+ public static uint operator &(UInt128 a, uint b)
+ {
+ return (uint)a.s0 & b;
+ }
+
+ public static uint operator &(uint a, UInt128 b)
+ {
+ return a & (uint)b.s0;
+ }
+
+ public static ulong operator &(UInt128 a, ulong b)
+ {
+ return a.s0 & b;
+ }
+
+ public static ulong operator &(ulong a, UInt128 b)
+ {
+ return a & b.s0;
+ }
+
+ public static UInt128 operator |(UInt128 a, UInt128 b)
+ {
+ UInt128 c;
+ Or(out c, ref a, ref b);
+ return c;
+ }
+
+ public static UInt128 operator ^(UInt128 a, UInt128 b)
+ {
+ UInt128 c;
+ ExclusiveOr(out c, ref a, ref b);
+ return c;
+ }
+
+ public static UInt128 operator ~(UInt128 a)
+ {
+ UInt128 c;
+ Not(out c, ref a);
+ return c;
+ }
+
+ public static UInt128 operator +(UInt128 a, UInt128 b)
+ {
+ UInt128 c;
+ Add(out c, ref a, ref b);
+ return c;
+ }
+
+ public static UInt128 operator +(UInt128 a, ulong b)
+ {
+ UInt128 c;
+ Add(out c, ref a, b);
+ return c;
+ }
+
+ public static UInt128 operator +(ulong a, UInt128 b)
+ {
+ UInt128 c;
+ Add(out c, ref b, a);
+ return c;
+ }
+
+ public static UInt128 operator ++(UInt128 a)
+ {
+ UInt128 c;
+ Add(out c, ref a, 1);
+ return c;
+ }
+
+ public static UInt128 operator -(UInt128 a, UInt128 b)
+ {
+ UInt128 c;
+ Subtract(out c, ref a, ref b);
+ return c;
+ }
+
+ public static UInt128 operator -(UInt128 a, ulong b)
+ {
+ UInt128 c;
+ Subtract(out c, ref a, b);
+ return c;
+ }
+
+ public static UInt128 operator -(ulong a, UInt128 b)
+ {
+ UInt128 c;
+ Subtract(out c, a, ref b);
+ return c;
+ }
+
+ public static UInt128 operator --(UInt128 a)
+ {
+ UInt128 c;
+ Subtract(out c, ref a, 1);
+ return c;
+ }
+
+ public static UInt128 operator +(UInt128 a)
+ {
+ return a;
+ }
+
+ public static UInt128 operator *(UInt128 a, uint b)
+ {
+ UInt128 c;
+ Multiply(out c, ref a, b);
+ return c;
+ }
+
+ public static UInt128 operator *(uint a, UInt128 b)
+ {
+ UInt128 c;
+ Multiply(out c, ref b, a);
+ return c;
+ }
+
+ public static UInt128 operator *(UInt128 a, ulong b)
+ {
+ UInt128 c;
+ Multiply(out c, ref a, b);
+ return c;
+ }
+
+ public static UInt128 operator *(ulong a, UInt128 b)
+ {
+ UInt128 c;
+ Multiply(out c, ref b, a);
+ return c;
+ }
+
+ public static UInt128 operator *(UInt128 a, UInt128 b)
+ {
+ UInt128 c;
+ Multiply(out c, ref a, ref b);
+ return c;
+ }
+
+ public static UInt128 operator /(UInt128 a, ulong b)
+ {
+ UInt128 c;
+ Divide(out c, ref a, b);
+ return c;
+ }
+
+ public static UInt128 operator /(UInt128 a, UInt128 b)
+ {
+ UInt128 c;
+ Divide(out c, ref a, ref b);
+ return c;
+ }
+
+ public static ulong operator %(UInt128 a, uint b)
+ {
+ return Remainder(ref a, b);
+ }
+
+ public static ulong operator %(UInt128 a, ulong b)
+ {
+ return Remainder(ref a, b);
+ }
+
+ public static UInt128 operator %(UInt128 a, UInt128 b)
+ {
+ UInt128 c;
+ Remainder(out c, ref a, ref b);
+ return c;
+ }
+
+ public static bool operator <(UInt128 a, UInt128 b)
+ {
+ return LessThan(ref a, ref b);
+ }
+
+ public static bool operator <(UInt128 a, int b)
+ {
+ return LessThan(ref a, b);
+ }
+
+ public static bool operator <(int a, UInt128 b)
+ {
+ return LessThan(a, ref b);
+ }
+
+ public static bool operator <(UInt128 a, uint b)
+ {
+ return LessThan(ref a, b);
+ }
+
+ public static bool operator <(uint a, UInt128 b)
+ {
+ return LessThan(a, ref b);
+ }
+
+ public static bool operator <(UInt128 a, long b)
+ {
+ return LessThan(ref a, b);
+ }
+
+ public static bool operator <(long a, UInt128 b)
+ {
+ return LessThan(a, ref b);
+ }
+
+ public static bool operator <(UInt128 a, ulong b)
+ {
+ return LessThan(ref a, b);
+ }
+
+ public static bool operator <(ulong a, UInt128 b)
+ {
+ return LessThan(a, ref b);
+ }
+
+ public static bool operator <=(UInt128 a, UInt128 b)
+ {
+ return !LessThan(ref b, ref a);
+ }
+
+ public static bool operator <=(UInt128 a, int b)
+ {
+ return !LessThan(b, ref a);
+ }
+
+ public static bool operator <=(int a, UInt128 b)
+ {
+ return !LessThan(ref b, a);
+ }
+
+ public static bool operator <=(UInt128 a, uint b)
+ {
+ return !LessThan(b, ref a);
+ }
+
+ public static bool operator <=(uint a, UInt128 b)
+ {
+ return !LessThan(ref b, a);
+ }
+
+ public static bool operator <=(UInt128 a, long b)
+ {
+ return !LessThan(b, ref a);
+ }
+
+ public static bool operator <=(long a, UInt128 b)
+ {
+ return !LessThan(ref b, a);
+ }
+
+ public static bool operator <=(UInt128 a, ulong b)
+ {
+ return !LessThan(b, ref a);
+ }
+
+ public static bool operator <=(ulong a, UInt128 b)
+ {
+ return !LessThan(ref b, a);
+ }
+
+ public static bool operator >(UInt128 a, UInt128 b)
+ {
+ return LessThan(ref b, ref a);
+ }
+
+ public static bool operator >(UInt128 a, int b)
+ {
+ return LessThan(b, ref a);
+ }
+
+ public static bool operator >(int a, UInt128 b)
+ {
+ return LessThan(ref b, a);
+ }
+
+ public static bool operator >(UInt128 a, uint b)
+ {
+ return LessThan(b, ref a);
+ }
+
+ public static bool operator >(uint a, UInt128 b)
+ {
+ return LessThan(ref b, a);
+ }
+
+ public static bool operator >(UInt128 a, long b)
+ {
+ return LessThan(b, ref a);
+ }
+
+ public static bool operator >(long a, UInt128 b)
+ {
+ return LessThan(ref b, a);
+ }
+
+ public static bool operator >(UInt128 a, ulong b)
+ {
+ return LessThan(b, ref a);
+ }
+
+ public static bool operator >(ulong a, UInt128 b)
+ {
+ return LessThan(ref b, a);
+ }
+
+ public static bool operator >=(UInt128 a, UInt128 b)
+ {
+ return !LessThan(ref a, ref b);
+ }
+
+ public static bool operator >=(UInt128 a, int b)
+ {
+ return !LessThan(ref a, b);
+ }
+
+ public static bool operator >=(int a, UInt128 b)
+ {
+ return !LessThan(a, ref b);
+ }
+
+ public static bool operator >=(UInt128 a, uint b)
+ {
+ return !LessThan(ref a, b);
+ }
+
+ public static bool operator >=(uint a, UInt128 b)
+ {
+ return !LessThan(a, ref b);
+ }
+
+ public static bool operator >=(UInt128 a, long b)
+ {
+ return !LessThan(ref a, b);
+ }
+
+ public static bool operator >=(long a, UInt128 b)
+ {
+ return !LessThan(a, ref b);
+ }
+
+ public static bool operator >=(UInt128 a, ulong b)
+ {
+ return !LessThan(ref a, b);
+ }
+
+ public static bool operator >=(ulong a, UInt128 b)
+ {
+ return !LessThan(a, ref b);
+ }
+
+ public static bool operator ==(UInt128 a, UInt128 b)
+ {
+ return a.Equals(b);
+ }
+
+ public static bool operator ==(UInt128 a, int b)
+ {
+ return a.Equals(b);
+ }
+
+ public static bool operator ==(int a, UInt128 b)
+ {
+ return b.Equals(a);
+ }
+
+ public static bool operator ==(UInt128 a, uint b)
+ {
+ return a.Equals(b);
+ }
+
+ public static bool operator ==(uint a, UInt128 b)
+ {
+ return b.Equals(a);
+ }
+
+ public static bool operator ==(UInt128 a, long b)
+ {
+ return a.Equals(b);
+ }
+
+ public static bool operator ==(long a, UInt128 b)
+ {
+ return b.Equals(a);
+ }
+
+ public static bool operator ==(UInt128 a, ulong b)
+ {
+ return a.Equals(b);
+ }
+
+ public static bool operator ==(ulong a, UInt128 b)
+ {
+ return b.Equals(a);
+ }
+
+ public static bool operator !=(UInt128 a, UInt128 b)
+ {
+ return !a.Equals(b);
+ }
+
+ public static bool operator !=(UInt128 a, int b)
+ {
+ return !a.Equals(b);
+ }
+
+ public static bool operator !=(int a, UInt128 b)
+ {
+ return !b.Equals(a);
+ }
+
+ public static bool operator !=(UInt128 a, uint b)
+ {
+ return !a.Equals(b);
+ }
+
+ public static bool operator !=(uint a, UInt128 b)
+ {
+ return !b.Equals(a);
+ }
+
+ public static bool operator !=(UInt128 a, long b)
+ {
+ return !a.Equals(b);
+ }
+
+ public static bool operator !=(long a, UInt128 b)
+ {
+ return !b.Equals(a);
+ }
+
+ public static bool operator !=(UInt128 a, ulong b)
+ {
+ return !a.Equals(b);
+ }
+
+ public static bool operator !=(ulong a, UInt128 b)
+ {
+ return !b.Equals(a);
+ }
+
+ public int CompareTo(UInt128 other)
+ {
+ if (this.s1 != other.s1)
+ return this.s1.CompareTo(other.s1);
+ return this.s0.CompareTo(other.s0);
+ }
+
+ public int CompareTo(int other)
+ {
+ if (this.s1 != 0 || other < 0)
+ return 1;
+ return this.s0.CompareTo((ulong)other);
+ }
+
+ public int CompareTo(uint other)
+ {
+ if (this.s1 != 0)
+ return 1;
+ return this.s0.CompareTo((ulong)other);
+ }
+
+ public int CompareTo(long other)
+ {
+ if (this.s1 != 0 || other < 0)
+ return 1;
+ return this.s0.CompareTo((ulong)other);
+ }
+
+ public int CompareTo(ulong other)
+ {
+ if (this.s1 != 0)
+ return 1;
+ return this.s0.CompareTo(other);
+ }
+
+ public int CompareTo(object obj)
+ {
+ if (obj == null)
+ return 1;
+ if (!(obj is UInt128))
+ throw new ArgumentException();
+ return this.CompareTo((UInt128)obj);
+ }
+
+ private static bool LessThan(ref UInt128 a, long b)
+ {
+ return b >= 0 && a.s1 == 0 && a.s0 < (ulong)b;
+ }
+
+ private static bool LessThan(long a, ref UInt128 b)
+ {
+ return a < 0 || b.s1 != 0 || (ulong)a < b.s0;
+ }
+
+ private static bool LessThan(ref UInt128 a, ulong b)
+ {
+ return a.s1 == 0 && a.s0 < b;
+ }
+
+ private static bool LessThan(ulong a, ref UInt128 b)
+ {
+ return b.s1 != 0 || a < b.s0;
+ }
+
+ private static bool LessThan(ref UInt128 a, ref UInt128 b)
+ {
+ if (a.s1 != b.s1)
+ return a.s1 < b.s1;
+ return a.s0 < b.s0;
+ }
+
+ public static bool Equals(ref UInt128 a, ref UInt128 b)
+ {
+ return a.s0 == b.s0 && a.s1 == b.s1;
+ }
+
+ public bool Equals(UInt128 other)
+ {
+ return this.s0 == other.s0 && this.s1 == other.s1;
+ }
+
+ public bool Equals(int other)
+ {
+ return other >= 0 && this.s0 == (uint)other && this.s1 == 0;
+ }
+
+ public bool Equals(uint other)
+ {
+ return this.s0 == other && this.s1 == 0;
+ }
+
+ public bool Equals(long other)
+ {
+ return other >= 0 && this.s0 == (ulong)other && this.s1 == 0;
+ }
+
+ public bool Equals(ulong other)
+ {
+ return this.s0 == other && this.s1 == 0;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (!(obj is UInt128))
+ return false;
+ return this.Equals((UInt128)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return this.s0.GetHashCode() ^ this.s1.GetHashCode();
+ }
+
+ public static void Multiply(out UInt128 c, ulong a, ulong b)
+ {
+ Multiply64(out c, a, b);
+ Debug.Assert((BigInteger)c == (BigInteger)a * (BigInteger)b);
+ }
+
+ public static void Multiply(out UInt128 c, ref UInt128 a, uint b)
+ {
+ if (a.s1 == 0)
+ Multiply64(out c, a.s0, b);
+ else
+ Multiply128(out c, ref a, b);
+ Debug.Assert((BigInteger)c == (BigInteger)a * (BigInteger)b % ((BigInteger)1 << 128));
+ }
+
+ public static void Multiply(out UInt128 c, ref UInt128 a, ulong b)
+ {
+ if (a.s1 == 0)
+ Multiply64(out c, a.s0, b);
+ else
+ Multiply128(out c, ref a, b);
+ Debug.Assert((BigInteger)c == (BigInteger)a * (BigInteger)b % ((BigInteger)1 << 128));
+ }
+
+ public static void Multiply(out UInt128 c, ref UInt128 a, ref UInt128 b)
+ {
+ if ((a.s1 | b.s1) == 0)
+ Multiply64(out c, a.s0, b.s0);
+ else if (a.s1 == 0)
+ Multiply128(out c, ref b, a.s0);
+ else if (b.s1 == 0)
+ Multiply128(out c, ref a, b.s0);
+ else
+ Multiply128(out c, ref a, ref b);
+ Debug.Assert((BigInteger)c == (BigInteger)a * (BigInteger)b % ((BigInteger)1 << 128));
+ }
+
+ private static void Multiply(out UInt256 c, ref UInt128 a, ref UInt128 b)
+ {
+#if true
+ UInt128 c00, c01, c10, c11;
+ Multiply64(out c00, a.s0, b.s0);
+ Multiply64(out c01, a.s0, b.s1);
+ Multiply64(out c10, a.s1, b.s0);
+ Multiply64(out c11, a.s1, b.s1);
+ var carry1 = (uint)0;
+ var carry2 = (uint)0;
+ c.s0 = c00.S0;
+ c.s1 = Add(Add(c00.s1, c01.s0, ref carry1), c10.s0, ref carry1);
+ c.s2 = Add(Add(Add(c01.s1, c10.s1, ref carry2), c11.s0, ref carry2), carry1, ref carry2);
+ c.s3 = c11.s1 + carry2;
+#else
+ // Karatsuba method.
+ // Warning: doesn't correctly handle overflow.
+ UInt128 z0, z1, z2;
+ Multiply64(out z0, a.s0, b.s0);
+ Multiply64(out z2, a.s1, b.s1);
+ Multiply64(out z1, a.s0 + a.s1, b.s0 + b.s1);
+ Subtract(ref z1, ref z2);
+ Subtract(ref z1, ref z0);
+ var carry1 = (uint)0;
+ var carry2 = (uint)0;
+ c.s0 = z0.S0;
+ c.s1 = Add(z0.s1, z1.s0, ref carry1);
+ c.s2 = Add(Add(z1.s1, z2.s0, ref carry2), carry1, ref carry2);
+ c.s3 = z2.s1 + carry2;
+#endif
+ Debug.Assert((BigInteger)c == (BigInteger)a * (BigInteger)b);
+ }
+
+ public static UInt128 Abs(UInt128 a)
+ {
+ return a;
+ }
+
+ public static UInt128 Square(ulong a)
+ {
+ UInt128 c;
+ Square(out c, a);
+ return c;
+ }
+
+ public static UInt128 Square(UInt128 a)
+ {
+ UInt128 c;
+ Square(out c, ref a);
+ return c;
+ }
+
+ public static void Square(out UInt128 c, ulong a)
+ {
+ Square64(out c, a);
+ }
+
+ public static void Square(out UInt128 c, ref UInt128 a)
+ {
+ if (a.s1 == 0)
+ Square64(out c, a.s0);
+ else
+ Multiply128(out c, ref a, ref a);
+ }
+
+ public static UInt128 Cube(ulong a)
+ {
+ UInt128 c;
+ Cube(out c, a);
+ return c;
+ }
+
+ public static UInt128 Cube(UInt128 a)
+ {
+ UInt128 c;
+ Cube(out c, ref a);
+ return c;
+ }
+
+ public static void Cube(out UInt128 c, ulong a)
+ {
+ UInt128 square;
+ Square(out square, a);
+ Multiply(out c, ref square, a);
+ }
+
+ public static void Cube(out UInt128 c, ref UInt128 a)
+ {
+ UInt128 square;
+ if (a.s1 == 0)
+ {
+ Square64(out square, a.s0);
+ Multiply(out c, ref square, a.s0);
+ }
+ else
+ {
+ Multiply128(out square, ref a, ref a);
+ Multiply128(out c, ref square, ref a);
+ }
+ }
+
+ public static void Add(out UInt128 c, ulong a, ulong b)
+ {
+ c.s0 = a + b;
+ c.s1 = 0;
+ if (c.s0 < a && c.s0 < b)
+ ++c.s1;
+ Debug.Assert((BigInteger)c == ((BigInteger)a + (BigInteger)b));
+ }
+
+ public static void Add(out UInt128 c, ref UInt128 a, ulong b)
+ {
+ c.s0 = a.s0 + b;
+ c.s1 = a.s1;
+ if (c.s0 < a.s0 && c.s0 < b)
+ ++c.s1;
+ Debug.Assert((BigInteger)c == ((BigInteger)a + (BigInteger)b) % ((BigInteger)1 << 128));
+ }
+
+ public static void Add(out UInt128 c, ref UInt128 a, ref UInt128 b)
+ {
+ c.s0 = a.s0 + b.s0;
+ c.s1 = a.s1 + b.s1;
+ if (c.s0 < a.s0 && c.s0 < b.s0)
+ ++c.s1;
+ Debug.Assert((BigInteger)c == ((BigInteger)a + (BigInteger)b) % ((BigInteger)1 << 128));
+ }
+
+ private static ulong Add(ulong a, ulong b, ref uint carry)
+ {
+ var c = a + b;
+ if (c < a && c < b)
+ ++carry;
+ return c;
+ }
+
+ public static void Add(ref UInt128 a, ulong b)
+ {
+ var sum = a.s0 + b;
+ if (sum < a.s0 && sum < b)
+ ++a.s1;
+ a.s0 = sum;
+ }
+
+ public static void Add(ref UInt128 a, ref UInt128 b)
+ {
+ var sum = a.s0 + b.s0;
+ if (sum < a.s0 && sum < b.s0)
+ ++a.s1;
+ a.s0 = sum;
+ a.s1 += b.s1;
+ }
+
+ public static void Add(ref UInt128 a, UInt128 b)
+ {
+ Add(ref a, ref b);
+ }
+
+ public static void Subtract(out UInt128 c, ref UInt128 a, ulong b)
+ {
+ c.s0 = a.s0 - b;
+ c.s1 = a.s1;
+ if (a.s0 < b)
+ --c.s1;
+ Debug.Assert((BigInteger)c == ((BigInteger)a - (BigInteger)b + ((BigInteger)1 << 128)) % ((BigInteger)1 << 128));
+ }
+
+ public static void Subtract(out UInt128 c, ulong a, ref UInt128 b)
+ {
+ c.s0 = a - b.s0;
+ c.s1 = 0 - b.s1;
+ if (a < b.s0)
+ --c.s1;
+ Debug.Assert((BigInteger)c == ((BigInteger)a - (BigInteger)b + ((BigInteger)1 << 128)) % ((BigInteger)1 << 128));
+ }
+
+ public static void Subtract(out UInt128 c, ref UInt128 a, ref UInt128 b)
+ {
+ c.s0 = a.s0 - b.s0;
+ c.s1 = a.s1 - b.s1;
+ if (a.s0 < b.s0)
+ --c.s1;
+ Debug.Assert((BigInteger)c == ((BigInteger)a - (BigInteger)b + ((BigInteger)1 << 128)) % ((BigInteger)1 << 128));
+ }
+
+ public static void Subtract(ref UInt128 a, ulong b)
+ {
+ if (a.s0 < b)
+ --a.s1;
+ a.s0 -= b;
+ }
+
+ public static void Subtract(ref UInt128 a, ref UInt128 b)
+ {
+ if (a.s0 < b.s0)
+ --a.s1;
+ a.s0 -= b.s0;
+ a.s1 -= b.s1;
+ }
+
+ public static void Subtract(ref UInt128 a, UInt128 b)
+ {
+ Subtract(ref a, ref b);
+ }
+
+ private static void Square64(out UInt128 w, ulong u)
+ {
+ var u0 = (ulong)(uint)u;
+ var u1 = u >> 32;
+ var carry = u0 * u0;
+ var r0 = (uint)carry;
+ var u0u1 = u0 * u1;
+ carry = (carry >> 32) + u0u1;
+ var r2 = carry >> 32;
+ carry = (uint)carry + u0u1;
+ w.s0 = carry << 32 | r0;
+ w.s1 = (carry >> 32) + r2 + u1 * u1;
+ Debug.Assert((BigInteger)w == (BigInteger)u * u);
+ }
+
+ private static void Multiply64(out UInt128 w, uint u, uint v)
+ {
+ w.s0 = (ulong)u * v;
+ w.s1 = 0;
+ Debug.Assert((BigInteger)w == (BigInteger)u * v);
+ }
+
+ private static void Multiply64(out UInt128 w, ulong u, uint v)
+ {
+ var u0 = (ulong)(uint)u;
+ var u1 = u >> 32;
+ var carry = u0 * v;
+ var r0 = (uint)carry;
+ carry = (carry >> 32) + u1 * v;
+ w.s0 = carry << 32 | r0;
+ w.s1 = carry >> 32;
+ Debug.Assert((BigInteger)w == (BigInteger)u * v);
+ }
+
+ private static void Multiply64(out UInt128 w, ulong u, ulong v)
+ {
+ var u0 = (ulong)(uint)u;
+ var u1 = u >> 32;
+ var v0 = (ulong)(uint)v;
+ var v1 = v >> 32;
+ var carry = u0 * v0;
+ var r0 = (uint)carry;
+ carry = (carry >> 32) + u0 * v1;
+ var r2 = carry >> 32;
+ carry = (uint)carry + u1 * v0;
+ w.s0 = carry << 32 | r0;
+ w.s1 = (carry >> 32) + r2 + u1 * v1;
+ Debug.Assert((BigInteger)w == (BigInteger)u * v);
+ }
+
+ private static void Multiply64(out UInt128 w, ulong u, ulong v, ulong c)
+ {
+ var u0 = (ulong)(uint)u;
+ var u1 = u >> 32;
+ var v0 = (ulong)(uint)v;
+ var v1 = v >> 32;
+ var carry = u0 * v0 + (uint)c;
+ var r0 = (uint)carry;
+ carry = (carry >> 32) + u0 * v1 + (c >> 32);
+ var r2 = carry >> 32;
+ carry = (uint)carry + u1 * v0;
+ w.s0 = carry << 32 | r0;
+ w.s1 = (carry >> 32) + r2 + u1 * v1;
+ Debug.Assert((BigInteger)w == (BigInteger)u * v + c);
+ }
+
+ private static ulong MultiplyHigh64(ulong u, ulong v, ulong c)
+ {
+ var u0 = (ulong)(uint)u;
+ var u1 = u >> 32;
+ var v0 = (ulong)(uint)v;
+ var v1 = v >> 32;
+ var carry = ((u0 * v0 + (uint)c) >> 32) + u0 * v1 + (c >> 32);
+ var r2 = carry >> 32;
+ carry = (uint)carry + u1 * v0;
+ return (carry >> 32) + r2 + u1 * v1;
+ }
+
+ private static void Multiply128(out UInt128 w, ref UInt128 u, uint v)
+ {
+ Multiply64(out w, u.s0, v);
+ w.s1 += u.s1 * v;
+ Debug.Assert((BigInteger)w == (BigInteger)u * v % ((BigInteger)1 << 128));
+ }
+
+ private static void Multiply128(out UInt128 w, ref UInt128 u, ulong v)
+ {
+ Multiply64(out w, u.s0, v);
+ w.s1 += u.s1 * v;
+ Debug.Assert((BigInteger)w == (BigInteger)u * v % ((BigInteger)1 << 128));
+ }
+
+ private static void Multiply128(out UInt128 w, ref UInt128 u, ref UInt128 v)
+ {
+ Multiply64(out w, u.s0, v.s0);
+ w.s1 += u.s1 * v.s0 + u.s0 * v.s1;
+ Debug.Assert((BigInteger)w == (BigInteger)u * v % ((BigInteger)1 << 128));
+ }
+
+ public static void Divide(out UInt128 w, ref UInt128 u, uint v)
+ {
+ if (u.s1 == 0)
+ Divide64(out w, u.s0, v);
+ else if (u.s1 <= uint.MaxValue)
+ Divide96(out w, ref u, v);
+ else
+ Divide128(out w, ref u, v);
+ }
+
+ public static void Divide(out UInt128 w, ref UInt128 u, ulong v)
+ {
+ if (u.s1 == 0)
+ Divide64(out w, u.s0, v);
+ else
+ {
+ var v0 = (uint)v;
+ if (v == v0)
+ {
+ if (u.s1 <= uint.MaxValue)
+ Divide96(out w, ref u, v0);
+ else
+ Divide128(out w, ref u, v0);
+ }
+ else
+ {
+ if (u.s1 <= uint.MaxValue)
+ Divide96(out w, ref u, v);
+ else
+ Divide128(out w, ref u, v);
+ }
+ }
+ }
+
+ public static void Divide(out UInt128 c, ref UInt128 a, ref UInt128 b)
+ {
+ if (LessThan(ref a, ref b))
+ c = Zero;
+ else if (b.s1 == 0)
+ Divide(out c, ref a, b.s0);
+ else if (b.s1 <= uint.MaxValue)
+ {
+ UInt128 rem;
+ Create(out c, DivRem96(out rem, ref a, ref b));
+ }
+ else
+ {
+ UInt128 rem;
+ Create(out c, DivRem128(out rem, ref a, ref b));
+ }
+ }
+
+ public static uint Remainder(ref UInt128 u, uint v)
+ {
+ if (u.s1 == 0)
+ return (uint)(u.s0 % v);
+ if (u.s1 <= uint.MaxValue)
+ return Remainder96(ref u, v);
+ return Remainder128(ref u, v);
+ }
+
+ public static ulong Remainder(ref UInt128 u, ulong v)
+ {
+ if (u.s1 == 0)
+ return u.s0 % v;
+ var v0 = (uint)v;
+ if (v == v0)
+ {
+ if (u.s1 <= uint.MaxValue)
+ return Remainder96(ref u, v0);
+ return Remainder128(ref u, v0);
+ }
+ if (u.s1 <= uint.MaxValue)
+ return Remainder96(ref u, v);
+ return Remainder128(ref u, v);
+ }
+
+ public static void Remainder(out UInt128 c, ref UInt128 a, ref UInt128 b)
+ {
+ if (LessThan(ref a, ref b))
+ c = a;
+ else if (b.s1 == 0)
+ Create(out c, Remainder(ref a, b.s0));
+ else if (b.s1 <= uint.MaxValue)
+ DivRem96(out c, ref a, ref b);
+ else
+ DivRem128(out c, ref a, ref b);
+ }
+
+ public static void Remainder(ref UInt128 a, ref UInt128 b)
+ {
+ UInt128 a2 = a;
+ Remainder(out a, ref a2, ref b);
+ }
+
+ private static void Remainder(out UInt128 c, ref UInt256 a, ref UInt128 b)
+ {
+ if (b.r3 == 0)
+ Remainder192(out c, ref a, ref b);
+ else
+ Remainder256(out c, ref a, ref b);
+ }
+
+ private static void Divide64(out UInt128 w, ulong u, ulong v)
+ {
+ w.s1 = 0;
+ w.s0 = u / v;
+ Debug.Assert((BigInteger)w == (BigInteger)u / v);
+ }
+
+ private static void Divide96(out UInt128 w, ref UInt128 u, uint v)
+ {
+ var r2 = u.r2;
+ var w2 = r2 / v;
+ var u0 = (ulong)(r2 - w2 * v);
+ var u0u1 = u0 << 32 | u.r1;
+ var w1 = (uint)(u0u1 / v);
+ u0 = u0u1 - w1 * v;
+ u0u1 = u0 << 32 | u.r0;
+ var w0 = (uint)(u0u1 / v);
+ w.s1 = w2;
+ w.s0 = (ulong)w1 << 32 | w0;
+ Debug.Assert((BigInteger)w == (BigInteger)u / v);
+ }
+
+ private static void Divide128(out UInt128 w, ref UInt128 u, uint v)
+ {
+ var r3 = u.r3;
+ var w3 = r3 / v;
+ var u0 = (ulong)(r3 - w3 * v);
+ var u0u1 = u0 << 32 | u.r2;
+ var w2 = (uint)(u0u1 / v);
+ u0 = u0u1 - w2 * v;
+ u0u1 = u0 << 32 | u.r1;
+ var w1 = (uint)(u0u1 / v);
+ u0 = u0u1 - w1 * v;
+ u0u1 = u0 << 32 | u.r0;
+ var w0 = (uint)(u0u1 / v);
+ w.s1 = (ulong)w3 << 32 | w2;
+ w.s0 = (ulong)w1 << 32 | w0;
+ Debug.Assert((BigInteger)w == (BigInteger)u / v);
+ }
+
+ private static void Divide96(out UInt128 w, ref UInt128 u, ulong v)
+ {
+ w.s0 = w.s1 = 0;
+ var dneg = GetBitLength((uint)(v >> 32));
+ var d = 32 - dneg;
+ var vPrime = v << d;
+ var v1 = (uint)(vPrime >> 32);
+ var v2 = (uint)vPrime;
+ var r0 = u.r0;
+ var r1 = u.r1;
+ var r2 = u.r2;
+ var r3 = (uint)0;
+ if (d != 0)
+ {
+ r3 = r2 >> dneg;
+ r2 = r2 << d | r1 >> dneg;
+ r1 = r1 << d | r0 >> dneg;
+ r0 <<= d;
+ }
+ var q1 = DivRem(r3, ref r2, ref r1, v1, v2);
+ var q0 = DivRem(r2, ref r1, ref r0, v1, v2);
+ w.s0 = (ulong)q1 << 32 | q0;
+ w.s1 = 0;
+ Debug.Assert((BigInteger)w == (BigInteger)u / v);
+ }
+
+ private static void Divide128(out UInt128 w, ref UInt128 u, ulong v)
+ {
+ w.s0 = w.s1 = 0;
+ var dneg = GetBitLength((uint)(v >> 32));
+ var d = 32 - dneg;
+ var vPrime = v << d;
+ var v1 = (uint)(vPrime >> 32);
+ var v2 = (uint)vPrime;
+ var r0 = u.r0;
+ var r1 = u.r1;
+ var r2 = u.r2;
+ var r3 = u.r3;
+ var r4 = (uint)0;
+ if (d != 0)
+ {
+ r4 = r3 >> dneg;
+ r3 = r3 << d | r2 >> dneg;
+ r2 = r2 << d | r1 >> dneg;
+ r1 = r1 << d | r0 >> dneg;
+ r0 <<= d;
+ }
+ w.s1 = DivRem(r4, ref r3, ref r2, v1, v2);
+ var q1 = DivRem(r3, ref r2, ref r1, v1, v2);
+ var q0 = DivRem(r2, ref r1, ref r0, v1, v2);
+ w.s0 = (ulong)q1 << 32 | q0;
+ Debug.Assert((BigInteger)w == (BigInteger)u / v);
+ }
+
+ private static uint Remainder96(ref UInt128 u, uint v)
+ {
+ var u0 = (ulong)(u.r2 % v);
+ var u0u1 = u0 << 32 | u.r1;
+ u0 = u0u1 % v;
+ u0u1 = u0 << 32 | u.r0;
+ return (uint)(u0u1 % v);
+ }
+
+ private static uint Remainder128(ref UInt128 u, uint v)
+ {
+ var u0 = (ulong)(u.r3 % v);
+ var u0u1 = u0 << 32 | u.r2;
+ u0 = u0u1 % v;
+ u0u1 = u0 << 32 | u.r1;
+ u0 = u0u1 % v;
+ u0u1 = u0 << 32 | u.r0;
+ return (uint)(u0u1 % v);
+ }
+
+ private static ulong Remainder96(ref UInt128 u, ulong v)
+ {
+ var dneg = GetBitLength((uint)(v >> 32));
+ var d = 32 - dneg;
+ var vPrime = v << d;
+ var v1 = (uint)(vPrime >> 32);
+ var v2 = (uint)vPrime;
+ var r0 = u.r0;
+ var r1 = u.r1;
+ var r2 = u.r2;
+ var r3 = (uint)0;
+ if (d != 0)
+ {
+ r3 = r2 >> dneg;
+ r2 = r2 << d | r1 >> dneg;
+ r1 = r1 << d | r0 >> dneg;
+ r0 <<= d;
+ }
+ DivRem(r3, ref r2, ref r1, v1, v2);
+ DivRem(r2, ref r1, ref r0, v1, v2);
+ return ((ulong)r1 << 32 | r0) >> d;
+ }
+
+ private static ulong Remainder128(ref UInt128 u, ulong v)
+ {
+ var dneg = GetBitLength((uint)(v >> 32));
+ var d = 32 - dneg;
+ var vPrime = v << d;
+ var v1 = (uint)(vPrime >> 32);
+ var v2 = (uint)vPrime;
+ var r0 = u.r0;
+ var r1 = u.r1;
+ var r2 = u.r2;
+ var r3 = u.r3;
+ var r4 = (uint)0;
+ if (d != 0)
+ {
+ r4 = r3 >> dneg;
+ r3 = r3 << d | r2 >> dneg;
+ r2 = r2 << d | r1 >> dneg;
+ r1 = r1 << d | r0 >> dneg;
+ r0 <<= d;
+ }
+ DivRem(r4, ref r3, ref r2, v1, v2);
+ DivRem(r3, ref r2, ref r1, v1, v2);
+ DivRem(r2, ref r1, ref r0, v1, v2);
+ return ((ulong)r1 << 32 | r0) >> d;
+ }
+
+ private static ulong DivRem96(out UInt128 rem, ref UInt128 a, ref UInt128 b)
+ {
+ var d = 32 - GetBitLength(b.r2);
+ UInt128 v;
+ LeftShift64(out v, ref b, d);
+ var r4 = (uint)LeftShift64(out rem, ref a, d);
+ var v1 = v.r2;
+ var v2 = v.r1;
+ var v3 = v.r0;
+ var r3 = rem.r3;
+ var r2 = rem.r2;
+ var r1 = rem.r1;
+ var r0 = rem.r0;
+ var q1 = DivRem(r4, ref r3, ref r2, ref r1, v1, v2, v3);
+ var q0 = DivRem(r3, ref r2, ref r1, ref r0, v1, v2, v3);
+ Create(out rem, r0, r1, r2, 0);
+ var div = (ulong)q1 << 32 | q0;
+ RightShift64(ref rem, d);
+ Debug.Assert((BigInteger)div == (BigInteger)a / (BigInteger)b);
+ Debug.Assert((BigInteger)rem == (BigInteger)a % (BigInteger)b);
+ return div;
+ }
+
+ private static uint DivRem128(out UInt128 rem, ref UInt128 a, ref UInt128 b)
+ {
+ var d = 32 - GetBitLength(b.r3);
+ UInt128 v;
+ LeftShift64(out v, ref b, d);
+ var r4 = (uint)LeftShift64(out rem, ref a, d);
+ var r3 = rem.r3;
+ var r2 = rem.r2;
+ var r1 = rem.r1;
+ var r0 = rem.r0;
+ var div = DivRem(r4, ref r3, ref r2, ref r1, ref r0, v.r3, v.r2, v.r1, v.r0);
+ Create(out rem, r0, r1, r2, r3);
+ RightShift64(ref rem, d);
+ Debug.Assert((BigInteger)div == (BigInteger)a / (BigInteger)b);
+ Debug.Assert((BigInteger)rem == (BigInteger)a % (BigInteger)b);
+ return div;
+ }
+
+ private static void Remainder192(out UInt128 c, ref UInt256 a, ref UInt128 b)
+ {
+ var d = 32 - GetBitLength(b.r2);
+ UInt128 v;
+ LeftShift64(out v, ref b, d);
+ var v1 = v.r2;
+ var v2 = v.r1;
+ var v3 = v.r0;
+ UInt256 rem;
+ LeftShift64(out rem, ref a, d);
+ var r6 = rem.r6;
+ var r5 = rem.r5;
+ var r4 = rem.r4;
+ var r3 = rem.r3;
+ var r2 = rem.r2;
+ var r1 = rem.r1;
+ var r0 = rem.r0;
+ DivRem(r6, ref r5, ref r4, ref r3, v1, v2, v3);
+ DivRem(r5, ref r4, ref r3, ref r2, v1, v2, v3);
+ DivRem(r4, ref r3, ref r2, ref r1, v1, v2, v3);
+ DivRem(r3, ref r2, ref r1, ref r0, v1, v2, v3);
+ Create(out c, r0, r1, r2, 0);
+ RightShift64(ref c, d);
+ Debug.Assert((BigInteger)c == (BigInteger)a % (BigInteger)b);
+ }
+
+ private static void Remainder256(out UInt128 c, ref UInt256 a, ref UInt128 b)
+ {
+ var d = 32 - GetBitLength(b.r3);
+ UInt128 v;
+ LeftShift64(out v, ref b, d);
+ var v1 = v.r3;
+ var v2 = v.r2;
+ var v3 = v.r1;
+ var v4 = v.r0;
+ UInt256 rem;
+ var r8 = (uint)LeftShift64(out rem, ref a, d);
+ var r7 = rem.r7;
+ var r6 = rem.r6;
+ var r5 = rem.r5;
+ var r4 = rem.r4;
+ var r3 = rem.r3;
+ var r2 = rem.r2;
+ var r1 = rem.r1;
+ var r0 = rem.r0;
+ DivRem(r8, ref r7, ref r6, ref r5, ref r4, v1, v2, v3, v4);
+ DivRem(r7, ref r6, ref r5, ref r4, ref r3, v1, v2, v3, v4);
+ DivRem(r6, ref r5, ref r4, ref r3, ref r2, v1, v2, v3, v4);
+ DivRem(r5, ref r4, ref r3, ref r2, ref r1, v1, v2, v3, v4);
+ DivRem(r4, ref r3, ref r2, ref r1, ref r0, v1, v2, v3, v4);
+ Create(out c, r0, r1, r2, r3);
+ RightShift64(ref c, d);
+ Debug.Assert((BigInteger)c == (BigInteger)a % (BigInteger)b);
+ }
+
+ private static ulong Q(uint u0, uint u1, uint u2, uint v1, uint v2)
+ {
+ var u0u1 = (ulong)u0 << 32 | u1;
+ var qhat = u0 == v1 ? uint.MaxValue : u0u1 / v1;
+ var r = u0u1 - qhat * v1;
+ if (r == (uint)r && v2 * qhat > (r << 32 | u2))
+ {
+ --qhat;
+ r += v1;
+ if (r == (uint)r && v2 * qhat > (r << 32 | u2))
+ {
+ --qhat;
+ r += v1;
+ }
+ }
+ return qhat;
+ }
+
+ private static uint DivRem(uint u0, ref uint u1, ref uint u2, uint v1, uint v2)
+ {
+ var qhat = Q(u0, u1, u2, v1, v2);
+ var carry = qhat * v2;
+ var borrow = (long)u2 - (uint)carry;
+ carry >>= 32;
+ u2 = (uint)borrow;
+ borrow >>= 32;
+ carry += qhat * v1;
+ borrow += (long)u1 - (uint)carry;
+ carry >>= 32;
+ u1 = (uint)borrow;
+ borrow >>= 32;
+ borrow += (long)u0 - (uint)carry;
+ if (borrow != 0)
+ {
+ --qhat;
+ carry = (ulong)u2 + v2;
+ u2 = (uint)carry;
+ carry >>= 32;
+ carry += (ulong)u1 + v1;
+ u1 = (uint)carry;
+ }
+ return (uint)qhat;
+ }
+
+ private static uint DivRem(uint u0, ref uint u1, ref uint u2, ref uint u3, uint v1, uint v2, uint v3)
+ {
+ var qhat = Q(u0, u1, u2, v1, v2);
+ var carry = qhat * v3;
+ var borrow = (long)u3 - (uint)carry;
+ carry >>= 32;
+ u3 = (uint)borrow;
+ borrow >>= 32;
+ carry += qhat * v2;
+ borrow += (long)u2 - (uint)carry;
+ carry >>= 32;
+ u2 = (uint)borrow;
+ borrow >>= 32;
+ carry += qhat * v1;
+ borrow += (long)u1 - (uint)carry;
+ carry >>= 32;
+ u1 = (uint)borrow;
+ borrow >>= 32;
+ borrow += (long)u0 - (uint)carry;
+ if (borrow != 0)
+ {
+ --qhat;
+ carry = (ulong)u3 + v3;
+ u3 = (uint)carry;
+ carry >>= 32;
+ carry += (ulong)u2 + v2;
+ u2 = (uint)carry;
+ carry >>= 32;
+ carry += (ulong)u1 + v1;
+ u1 = (uint)carry;
+ }
+ return (uint)qhat;
+ }
+
+ private static uint DivRem(uint u0, ref uint u1, ref uint u2, ref uint u3, ref uint u4, uint v1, uint v2, uint v3, uint v4)
+ {
+ var qhat = Q(u0, u1, u2, v1, v2);
+ var carry = qhat * v4;
+ var borrow = (long)u4 - (uint)carry;
+ carry >>= 32;
+ u4 = (uint)borrow;
+ borrow >>= 32;
+ carry += qhat * v3;
+ borrow += (long)u3 - (uint)carry;
+ carry >>= 32;
+ u3 = (uint)borrow;
+ borrow >>= 32;
+ carry += qhat * v2;
+ borrow += (long)u2 - (uint)carry;
+ carry >>= 32;
+ u2 = (uint)borrow;
+ borrow >>= 32;
+ carry += qhat * v1;
+ borrow += (long)u1 - (uint)carry;
+ carry >>= 32;
+ u1 = (uint)borrow;
+ borrow >>= 32;
+ borrow += (long)u0 - (uint)carry;
+ if (borrow != 0)
+ {
+ --qhat;
+ carry = (ulong)u4 + v4;
+ u4 = (uint)carry;
+ carry >>= 32;
+ carry += (ulong)u3 + v3;
+ u3 = (uint)carry;
+ carry >>= 32;
+ carry += (ulong)u2 + v2;
+ u2 = (uint)carry;
+ carry >>= 32;
+ carry += (ulong)u1 + v1;
+ u1 = (uint)carry;
+ }
+ return (uint)qhat;
+ }
+
+ public static void ModAdd(out UInt128 c, ref UInt128 a, ref UInt128 b, ref UInt128 modulus)
+ {
+ Add(out c, ref a, ref b);
+ if (!LessThan(ref c, ref modulus) || LessThan(ref c, ref a) && LessThan(ref c, ref b))
+ Subtract(ref c, ref modulus);
+ }
+
+ public static void ModSub(out UInt128 c, ref UInt128 a, ref UInt128 b, ref UInt128 modulus)
+ {
+ Subtract(out c, ref a, ref b);
+ if (LessThan(ref a, ref b))
+ Add(ref c, ref modulus);
+ }
+
+ public static void ModMul(out UInt128 c, ref UInt128 a, ref UInt128 b, ref UInt128 modulus)
+ {
+ if (modulus.s1 == 0)
+ {
+ UInt128 product;
+ Multiply64(out product, a.s0, b.s0);
+ Create(out c, UInt128.Remainder(ref product, modulus.s0));
+ }
+ else
+ {
+ UInt256 product;
+ Multiply(out product, ref a, ref b);
+ Remainder(out c, ref product, ref modulus);
+ }
+ }
+
+ public static void ModMul(ref UInt128 a, ref UInt128 b, ref UInt128 modulus)
+ {
+ if (modulus.s1 == 0)
+ {
+ UInt128 product;
+ Multiply64(out product, a.s0, b.s0);
+ Create(out a, UInt128.Remainder(ref product, modulus.s0));
+ }
+ else
+ {
+ UInt256 product;
+ Multiply(out product, ref a, ref b);
+ Remainder(out a, ref product, ref modulus);
+ }
+ }
+
+ public static void ModPow(out UInt128 result, ref UInt128 value, ref UInt128 exponent, ref UInt128 modulus)
+ {
+ result = one;
+ var v = value;
+ var e = exponent.s0;
+ if (exponent.s1 != 0)
+ {
+ for (var i = 0; i < 64; i++)
+ {
+ if ((e & 1) != 0)
+ ModMul(ref result, ref v, ref modulus);
+ ModMul(ref v, ref v, ref modulus);
+ e >>= 1;
+ }
+ e = exponent.s1;
+ }
+ while (e != 0)
+ {
+ if ((e & 1) != 0)
+ ModMul(ref result, ref v, ref modulus);
+ if (e != 1)
+ ModMul(ref v, ref v, ref modulus);
+ e >>= 1;
+ }
+ Debug.Assert(BigInteger.ModPow(value, exponent, modulus) == result);
+ }
+
+ public static void Shift(out UInt128 c, ref UInt128 a, int d)
+ {
+ if (d < 0)
+ RightShift(out c, ref a, -d);
+ else
+ LeftShift(out c, ref a, d);
+ }
+
+ public static void ArithmeticShift(out UInt128 c, ref UInt128 a, int d)
+ {
+ if (d < 0)
+ ArithmeticRightShift(out c, ref a, -d);
+ else
+ LeftShift(out c, ref a, d);
+ }
+
+ public static ulong LeftShift64(out UInt128 c, ref UInt128 a, int d)
+ {
+ if (d == 0)
+ {
+ c = a;
+ return 0;
+ }
+ var dneg = 64 - d;
+ c.s1 = a.s1 << d | a.s0 >> dneg;
+ c.s0 = a.s0 << d;
+ return a.s1 >> dneg;
+ }
+
+ private static ulong LeftShift64(out UInt256 c, ref UInt256 a, int d)
+ {
+ if (d == 0)
+ {
+ c = a;
+ return 0;
+ }
+ var dneg = 64 - d;
+ c.s3 = a.s3 << d | a.s2 >> dneg;
+ c.s2 = a.s2 << d | a.s1 >> dneg;
+ c.s1 = a.s1 << d | a.s0 >> dneg;
+ c.s0 = a.s0 << d;
+ return a.s3 >> dneg;
+ }
+
+ public static void LeftShift(out UInt128 c, ref UInt128 a, int b)
+ {
+ if (b < 64)
+ LeftShift64(out c, ref a, b);
+ else if (b == 64)
+ {
+ c.s0 = 0;
+ c.s1 = a.s0;
+ return;
+ }
+ else
+ {
+ c.s0 = 0;
+ c.s1 = a.s0 << (b - 64);
+ }
+ }
+
+ public static void RightShift64(out UInt128 c, ref UInt128 a, int b)
+ {
+ if (b == 0)
+ c = a;
+ else
+ {
+ c.s0 = a.s0 >> b | a.s1 << (64 - b);
+ c.s1 = a.s1 >> b;
+ }
+ }
+
+ public static void RightShift(out UInt128 c, ref UInt128 a, int b)
+ {
+ if (b < 64)
+ RightShift64(out c, ref a, b);
+ else if (b == 64)
+ {
+ c.s0 = a.s1;
+ c.s1 = 0;
+ }
+ else
+ {
+ c.s0 = a.s1 >> (b - 64);
+ c.s1 = 0;
+ }
+ }
+
+ public static void ArithmeticRightShift64(out UInt128 c, ref UInt128 a, int b)
+ {
+ if (b == 0)
+ c = a;
+ else
+ {
+ c.s0 = a.s0 >> b | a.s1 << (64 - b);
+ c.s1 = (ulong)((long)a.s1 >> b);
+ }
+ }
+
+ public static void ArithmeticRightShift(out UInt128 c, ref UInt128 a, int b)
+ {
+ if (b < 64)
+ ArithmeticRightShift64(out c, ref a, b);
+ else if (b == 64)
+ {
+ c.s0 = a.s1;
+ c.s1 = (ulong)((long)a.s1 >> 63);
+ }
+ else
+ {
+ c.s0 = a.s1 >> (b - 64);
+ c.s1 = (ulong)((long)a.s1 >> 63);
+ }
+ }
+
+ public static void And(out UInt128 c, ref UInt128 a, ref UInt128 b)
+ {
+ c.s0 = a.s0 & b.s0;
+ c.s1 = a.s1 & b.s1;
+ }
+
+ public static void Or(out UInt128 c, ref UInt128 a, ref UInt128 b)
+ {
+ c.s0 = a.s0 | b.s0;
+ c.s1 = a.s1 | b.s1;
+ }
+
+ public static void ExclusiveOr(out UInt128 c, ref UInt128 a, ref UInt128 b)
+ {
+ c.s0 = a.s0 ^ b.s0;
+ c.s1 = a.s1 ^ b.s1;
+ }
+
+ public static void Not(out UInt128 c, ref UInt128 a)
+ {
+ c.s0 = ~a.s0;
+ c.s1 = ~a.s1;
+ }
+
+ public static void Negate(ref UInt128 a)
+ {
+ var s0 = a.s0;
+ a.s0 = 0 - s0;
+ a.s1 = 0 - a.s1;
+ if (s0 > 0)
+ --a.s1;
+ }
+
+
+ public static void Negate(out UInt128 c, ref UInt128 a)
+ {
+ c.s0 = 0 - a.s0;
+ c.s1 = 0 - a.s1;
+ if (a.s0 > 0)
+ --c.s1;
+ Debug.Assert((BigInteger)c == (BigInteger)(~a + 1));
+ }
+
+ public static void Pow(out UInt128 result, ref UInt128 value, uint exponent)
+ {
+ result = one;
+ while (exponent != 0)
+ {
+
+ if ((exponent & 1) != 0)
+ {
+ var previous = result;
+ Multiply(out result, ref previous, ref value);
+ }
+ if (exponent != 1)
+ {
+ var previous = value;
+ Square(out value, ref previous);
+ }
+ exponent >>= 1;
+ }
+ }
+
+ public static UInt128 Pow(UInt128 value, uint exponent)
+ {
+ UInt128 result;
+ Pow(out result, ref value, exponent);
+ return result;
+ }
+
+ private const int maxRepShift = 53;
+ private static readonly ulong maxRep = (ulong)1 << maxRepShift;
+ private static readonly UInt128 maxRepSquaredHigh = (ulong)1 << (2 * maxRepShift - 64);
+
+ public static ulong FloorSqrt(UInt128 a)
+ {
+ if (a.s1 == 0 && a.s0 <= maxRep)
+ return (ulong)Math.Sqrt(a.s0);
+ var s = (ulong)Math.Sqrt(ConvertToDouble(ref a));
+ if (a.s1 < maxRepSquaredHigh)
+ {
+ UInt128 s2;
+ Square(out s2, s);
+ var r = a.s0 - s2.s0;
+ if (r > long.MaxValue)
+ --s;
+ else if (r - (s << 1) <= long.MaxValue)
+ ++s;
+ Debug.Assert((BigInteger)s * s <= a && (BigInteger)(s + 1) * (s + 1) > a);
+ return s;
+ }
+ s = FloorSqrt(ref a, s);
+ Debug.Assert((BigInteger)s * s <= a && (BigInteger)(s + 1) * (s + 1) > a);
+ return s;
+ }
+
+ public static ulong CeilingSqrt(UInt128 a)
+ {
+ if (a.s1 == 0 && a.s0 <= maxRep)
+ return (ulong)Math.Ceiling(Math.Sqrt(a.s0));
+ var s = (ulong)Math.Ceiling(Math.Sqrt(ConvertToDouble(ref a)));
+ if (a.s1 < maxRepSquaredHigh)
+ {
+ UInt128 s2;
+ Square(out s2, s);
+ var r = s2.s0 - a.s0;
+ if (r > long.MaxValue)
+ ++s;
+ else if (r - (s << 1) <= long.MaxValue)
+ --s;
+ Debug.Assert((BigInteger)(s - 1) * (s - 1) < a && (BigInteger)s * s >= a);
+ return s;
+ }
+ s = FloorSqrt(ref a, s);
+ UInt128 square;
+ Square(out square, s);
+ if (square.S0 != a.S0 || square.S1 != a.S1)
+ ++s;
+ Debug.Assert((BigInteger)(s - 1) * (s - 1) < a && (BigInteger)s * s >= a);
+ return s;
+ }
+
+ private static ulong FloorSqrt(ref UInt128 a, ulong s)
+ {
+ var sprev = (ulong)0;
+ UInt128 div;
+ UInt128 sum;
+ while (true)
+ {
+ // Equivalent to:
+ // snext = (a / s + s) / 2;
+ Divide(out div, ref a, s);
+ Add(out sum, ref div, s);
+ var snext = sum.S0 >> 1;
+ if (sum.S1 != 0)
+ snext |= (ulong)1 << 63;
+ if (snext == sprev)
+ {
+ if (snext < s)
+ s = snext;
+ break;
+ }
+ sprev = s;
+ s = snext;
+ }
+ return s;
+ }
+
+ public static ulong FloorCbrt(UInt128 a)
+ {
+ var s = (ulong)Math.Pow(ConvertToDouble(ref a), (double)1 / 3);
+ UInt128 s3;
+ Cube(out s3, s);
+ if (a < s3)
+ --s;
+ else
+ {
+ UInt128 sum;
+ Multiply(out sum, 3 * s, s + 1);
+ UInt128 diff;
+ Subtract(out diff, ref a, ref s3);
+ if (LessThan(ref sum, ref diff))
+ ++s;
+ }
+ Debug.Assert((BigInteger)s * s * s <= a && (BigInteger)(s + 1) * (s + 1) * (s + 1) > a);
+ return s;
+ }
+
+ public static ulong CeilingCbrt(UInt128 a)
+ {
+ var s = (ulong)Math.Ceiling(Math.Pow(ConvertToDouble(ref a), (double)1 / 3));
+ UInt128 s3;
+ Cube(out s3, s);
+ if (s3 < a)
+ ++s;
+ else
+ {
+ UInt128 sum;
+ Multiply(out sum, 3 * s, s + 1);
+ UInt128 diff;
+ Subtract(out diff, ref s3, ref a);
+ if (LessThan(ref sum, ref diff))
+ --s;
+ }
+ Debug.Assert((BigInteger)(s - 1) * (s - 1) * (s - 1) < a && (BigInteger)s * s * s >= a);
+ return s;
+ }
+
+ public static UInt128 Min(UInt128 a, UInt128 b)
+ {
+ if (LessThan(ref a, ref b))
+ return a;
+ return b;
+ }
+
+ public static UInt128 Max(UInt128 a, UInt128 b)
+ {
+ if (LessThan(ref b, ref a))
+ return a;
+ return b;
+ }
+
+ public static double Log(UInt128 a)
+ {
+ return Log(a, Math.E);
+ }
+
+ public static double Log10(UInt128 a)
+ {
+ return Log(a, 10);
+ }
+
+ public static double Log(UInt128 a, double b)
+ {
+ return Math.Log(ConvertToDouble(ref a), b);
+ }
+
+ public static UInt128 Add(UInt128 a, UInt128 b)
+ {
+ UInt128 c;
+ Add(out c, ref a, ref b);
+ return c;
+ }
+
+ public static UInt128 Subtract(UInt128 a, UInt128 b)
+ {
+ UInt128 c;
+ Subtract(out c, ref a, ref b);
+ return c;
+ }
+
+ public static UInt128 Multiply(UInt128 a, UInt128 b)
+ {
+ UInt128 c;
+ Multiply(out c, ref a, ref b);
+ return c;
+ }
+
+ public static UInt128 Divide(UInt128 a, UInt128 b)
+ {
+ UInt128 c;
+ Divide(out c, ref a, ref b);
+ return c;
+ }
+
+ public static UInt128 Remainder(UInt128 a, UInt128 b)
+ {
+ UInt128 c;
+ Remainder(out c, ref a, ref b);
+ return c;
+ }
+
+ public static UInt128 DivRem(UInt128 a, UInt128 b, out UInt128 remainder)
+ {
+ UInt128 c;
+ Divide(out c, ref a, ref b);
+ Remainder(out remainder, ref a, ref b);
+ return c;
+ }
+
+ public static UInt128 ModAdd(UInt128 a, UInt128 b, UInt128 modulus)
+ {
+ UInt128 c;
+ ModAdd(out c, ref a, ref b, ref modulus);
+ return c;
+ }
+
+ public static UInt128 ModSub(UInt128 a, UInt128 b, UInt128 modulus)
+ {
+ UInt128 c;
+ ModSub(out c, ref a, ref b, ref modulus);
+ return c;
+ }
+
+ public static UInt128 ModMul(UInt128 a, UInt128 b, UInt128 modulus)
+ {
+ UInt128 c;
+ ModMul(out c, ref a, ref b, ref modulus);
+ return c;
+ }
+
+ public static UInt128 ModPow(UInt128 value, UInt128 exponent, UInt128 modulus)
+ {
+ UInt128 result;
+ ModPow(out result, ref value, ref exponent, ref modulus);
+ return result;
+ }
+
+ public static UInt128 Negate(UInt128 a)
+ {
+ UInt128 c;
+ Negate(out c, ref a);
+ return c;
+ }
+
+ public static UInt128 GreatestCommonDivisor(UInt128 a, UInt128 b)
+ {
+ UInt128 c;
+ GreatestCommonDivisor(out c, ref a, ref b);
+ return c;
+ }
+
+ private static void RightShift64(ref UInt128 c, int d)
+ {
+ if (d == 0)
+ return;
+ c.s0 = c.s1 << (64 - d) | c.s0 >> d;
+ c.s1 >>= d;
+ }
+
+ public static void RightShift(ref UInt128 c, int d)
+ {
+ if (d < 64)
+ RightShift64(ref c, d);
+ else
+ {
+ c.s0 = c.s1 >> (d - 64);
+ c.s1 = 0;
+ }
+ }
+
+ public static void Shift(ref UInt128 c, int d)
+ {
+ if (d < 0)
+ RightShift(ref c, -d);
+ else
+ LeftShift(ref c, d);
+ }
+
+ public static void ArithmeticShift(ref UInt128 c, int d)
+ {
+ if (d < 0)
+ ArithmeticRightShift(ref c, -d);
+ else
+ LeftShift(ref c, d);
+ }
+
+ public static void RightShift(ref UInt128 c)
+ {
+ c.s0 = c.s1 << 63 | c.s0 >> 1;
+ c.s1 >>= 1;
+ }
+
+ private static void ArithmeticRightShift64(ref UInt128 c, int d)
+ {
+ if (d == 0)
+ return;
+ c.s0 = c.s1 << (64 - d) | c.s0 >> d;
+ c.s1 = (ulong)((long)c.s1 >> d);
+ }
+
+ public static void ArithmeticRightShift(ref UInt128 c, int d)
+ {
+ if (d < 64)
+ ArithmeticRightShift64(ref c, d);
+ else
+ {
+ c.s0 = (ulong)((long)c.s1 >> (d - 64));
+ c.s1 = 0;
+ }
+ }
+
+ public static void ArithmeticRightShift(ref UInt128 c)
+ {
+ c.s0 = c.s1 << 63 | c.s0 >> 1;
+ c.s1 = (ulong)((long)c.s1 >> 1);
+ }
+
+ private static ulong LeftShift64(ref UInt128 c, int d)
+ {
+ if (d == 0)
+ return 0;
+ var dneg = 64 - d;
+ var result = c.s1 >> dneg;
+ c.s1 = c.s1 << d | c.s0 >> dneg;
+ c.s0 <<= d;
+ return result;
+ }
+
+ public static void LeftShift(ref UInt128 c, int d)
+ {
+ if (d < 64)
+ LeftShift64(ref c, d);
+ else
+ {
+ c.s1 = c.s0 << (d - 64);
+ c.s0 = 0;
+ }
+ }
+
+ public static void LeftShift(ref UInt128 c)
+ {
+ c.s1 = c.s1 << 1 | c.s0 >> 63;
+ c.s0 <<= 1;
+ }
+
+ public static void Swap(ref UInt128 a, ref UInt128 b)
+ {
+ var as0 = a.s0;
+ var as1 = a.s1;
+ a.s0 = b.s0;
+ a.s1 = b.s1;
+ b.s0 = as0;
+ b.s1 = as1;
+ }
+
+ public static void GreatestCommonDivisor(out UInt128 c, ref UInt128 a, ref UInt128 b)
+ {
+ // Check whether one number is > 64 bits and the other is <= 64 bits and both are non-zero.
+ UInt128 a1, b1;
+ if ((a.s1 == 0) != (b.s1 == 0) && !a.IsZero && !b.IsZero)
+ {
+ // Perform a normal step so that both a and b are <= 64 bits.
+ if (LessThan(ref a, ref b))
+ {
+ a1 = a;
+ Remainder(out b1, ref b, ref a);
+ }
+ else
+ {
+ b1 = b;
+ Remainder(out a1, ref a, ref b);
+ }
+ }
+ else
+ {
+ a1 = a;
+ b1 = b;
+ }
+
+ // Make sure neither is zero.
+ if (a1.IsZero)
+ {
+ c = b1;
+ return;
+ }
+ if (b1.IsZero)
+ {
+ c = a1;
+ return;
+ }
+
+ // Ensure a >= b.
+ if (LessThan(ref a1, ref b1))
+ Swap(ref a1, ref b1);
+
+ // Lehmer-Euclid algorithm.
+ // See: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.31.693
+ while (a1.s1 != 0 && !b.IsZero)
+ {
+ // Extract the high 63 bits of a and b.
+ var norm = 63 - GetBitLength(a1.s1);
+ UInt128 ahat, bhat;
+ Shift(out ahat, ref a1, norm);
+ Shift(out bhat, ref b1, norm);
+ var uhat = (long)ahat.s1;
+ var vhat = (long)bhat.s1;
+
+ // Check whether q exceeds single-precision.
+ if (vhat == 0)
+ {
+ // Perform a normal step and try again.
+ UInt128 rem;
+ Remainder(out rem, ref a1, ref b1);
+ a1 = b1;
+ b1 = rem;
+ continue;
+ }
+
+ // Perform steps using signed single-precision arithmetic.
+ var x0 = (long)1;
+ var y0 = (long)0;
+ var x1 = (long)0;
+ var y1 = (long)1;
+ var even = true;
+ while (true)
+ {
+ // Calculate quotient, cosquence pair, and update uhat and vhat.
+ var q = uhat / vhat;
+ var x2 = x0 - q * x1;
+ var y2 = y0 - q * y1;
+ var t = uhat;
+ uhat = vhat;
+ vhat = t - q * vhat;
+ even = !even;
+
+ // Apply Jebelean's termination condition
+ // to check whether q is valid.
+ if (even)
+ {
+ if (vhat < -x2 || uhat - vhat < y2 - y1)
+ break;
+ }
+ else
+ {
+ if (vhat < -y2 || uhat - vhat < x2 - x1)
+ break;
+ }
+
+ // Adjust cosequence history.
+ x0 = x1; y0 = y1; x1 = x2; y1 = y2;
+ }
+
+ // Check whether a normal step is necessary.
+ if (x0 == 1 && y0 == 0)
+ {
+ UInt128 rem;
+ Remainder(out rem, ref a1, ref b1);
+ a1 = b1;
+ b1 = rem;
+ continue;
+ }
+
+ // Back calculate a and b from the last valid cosequence pairs.
+ UInt128 anew, bnew;
+ if (even)
+ {
+ AddProducts(out anew, y0, ref b1, x0, ref a1);
+ AddProducts(out bnew, x1, ref a1, y1, ref b1);
+ }
+ else
+ {
+ AddProducts(out anew, x0, ref a1, y0, ref b1);
+ AddProducts(out bnew, y1, ref b1, x1, ref a1);
+ }
+ a1 = anew;
+ b1 = bnew;
+ }
+
+ // Check whether we have any 64 bit work left.
+ if (!b1.IsZero)
+ {
+ var a2 = a1.s0;
+ var b2 = b1.s0;
+
+ // Perform 64 bit steps.
+ while (a2 > uint.MaxValue && b2 != 0)
+ {
+ var t = a2 % b2;
+ a2 = b2;
+ b2 = t;
+ }
+
+ // Check whether we have any 32 bit work left.
+ if (b2 != 0)
+ {
+ var a3 = (uint)a2;
+ var b3 = (uint)b2;
+
+ // Perform 32 bit steps.
+ while (b3 != 0)
+ {
+ var t = a3 % b3;
+ a3 = b3;
+ b3 = t;
+ }
+
+ Create(out c, a3);
+ }
+ else
+ Create(out c, a2);
+ }
+ else
+ c = a1;
+ }
+
+ private static void AddProducts(out UInt128 result, long x, ref UInt128 u, long y, ref UInt128 v)
+ {
+ // Compute x * u + y * v assuming y is negative and the result is positive and fits in 128 bits.
+ UInt128 product1;
+ Multiply(out product1, ref u, (ulong)x);
+ UInt128 product2;
+ Multiply(out product2, ref v, (ulong)(-y));
+ Subtract(out result, ref product1, ref product2);
+ }
+
+ public static int Compare(UInt128 a, UInt128 b)
+ {
+ return a.CompareTo(b);
+ }
+
+ private static byte[] bitLength = Enumerable.Range(0, byte.MaxValue + 1)
+ .Select(value =>
+ {
+ int count;
+ for (count = 0; value != 0; count++)
+ value >>= 1;
+ return (byte)count;
+ }).ToArray();
+
+ private static int GetBitLength(uint value)
+ {
+ var tt = value >> 16;
+ if (tt != 0)
+ {
+ var t = tt >> 8;
+ if (t != 0)
+ return bitLength[t] + 24;
+ return bitLength[tt] + 16;
+ }
+ else
+ {
+ var t = value >> 8;
+ if (t != 0)
+ return bitLength[t] + 8;
+ return bitLength[value];
+ }
+ }
+
+ private static int GetBitLength(ulong value)
+ {
+ var r1 = value >> 32;
+ if (r1 != 0)
+ return GetBitLength((uint)r1) + 32;
+ return GetBitLength((uint)value);
+ }
+
+ public static void Reduce(out UInt128 w, ref UInt128 u, ref UInt128 v, ref UInt128 n, ulong k0)
+ {
+ UInt128 carry;
+ Multiply64(out carry, u.s0, v.s0);
+ var t0 = carry.s0;
+ Multiply64(out carry, u.s1, v.s0, carry.s1);
+ var t1 = carry.s0;
+ var t2 = carry.s1;
+
+ var m = t0 * k0;
+ Multiply64(out carry, m, n.s1, MultiplyHigh64(m, n.s0, t0));
+ Add(ref carry, t1);
+ t0 = carry.s0;
+ Add(out carry, carry.s1, t2);
+ t1 = carry.s0;
+ t2 = carry.s1;
+
+ Multiply64(out carry, u.s0, v.s1, t0);
+ t0 = carry.s0;
+ Multiply64(out carry, u.s1, v.s1, carry.s1);
+ Add(ref carry, t1);
+ t1 = carry.s0;
+ Add(out carry, carry.s1, t2);
+ t2 = carry.s0;
+ var t3 = carry.s1;
+
+ m = t0 * k0;
+ Multiply64(out carry, m, n.s1, MultiplyHigh64(m, n.s0, t0));
+ Add(ref carry, t1);
+ t0 = carry.s0;
+ Add(out carry, carry.s1, t2);
+ t1 = carry.s0;
+ t2 = t3 + carry.s1;
+
+ Create(out w, t0, t1);
+ if (t2 != 0 || !LessThan(ref w, ref n))
+ Subtract(ref w, ref n);
+ }
+
+ public static void Reduce(out UInt128 w, ref UInt128 t, ref UInt128 n, ulong k0)
+ {
+ UInt128 carry;
+ var t0 = t.s0;
+ var t1 = t.s1;
+ var t2 = (ulong)0;
+
+ for (var i = 0; i < 2; i++)
+ {
+ var m = t0 * k0;
+ Multiply64(out carry, m, n.s1, MultiplyHigh64(m, n.s0, t0));
+ Add(ref carry, t1);
+ t0 = carry.s0;
+ Add(out carry, carry.s1, t2);
+ t1 = carry.s0;
+ t2 = carry.s1;
+ }
+
+ Create(out w, t0, t1);
+ if (t2 != 0 || !LessThan(ref w, ref n))
+ Subtract(ref w, ref n);
+ }
+
+ public static UInt128 Reduce(UInt128 u, UInt128 v, UInt128 n, ulong k0)
+ {
+ UInt128 w;
+ Reduce(out w, ref u, ref v, ref n, k0);
+ return w;
+ }
+
+ public static UInt128 Reduce(UInt128 t, UInt128 n, ulong k0)
+ {
+ UInt128 w;
+ Reduce(out w, ref t, ref n, k0);
+ return w;
+ }
+ }
+}
diff --git a/src/BLS/Properties/AssemblyInfo.cs b/src/BLS/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..265facf3
--- /dev/null
+++ b/src/BLS/Properties/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("BLS.Tests")]
+[assembly: InternalsVisibleTo("Performance.Tests")]
+[assembly: InternalsVisibleTo("Sample.CLI")]
diff --git a/src/BLS/README.md b/src/BLS/README.md
index bbec26dd..0a46dcee 100644
--- a/src/BLS/README.md
+++ b/src/BLS/README.md
@@ -1,13 +1,17 @@
-# Cryptography.BLS - Minimal BLS library used for ICP BLS signature verification
+# Cryptography.BLS - Minimal BLS library used for ICP BLS signature verification
- Nuget: [`EdjCase.ICP.BLS`](https://www.nuget.org/packages/EdjCase.ICP.BLS)
# Overview
+
This is a simple library to validate BLS signatures (ICP flavor only).
-It acts as a wrapper around the https://github.com/herumi/bls WASM library
+Implementation is a conversion from the following projects
+https://github.com/filecoin-project/bls-signatures
+https://github.com/zkcrypto/bls12_381
# Usage
+
```cs
bool isValid = EdjCase.ICP.BLS.BlsUtil.VerifySignature(publicKey, messageHash, signature);
-```
+```
\ No newline at end of file
diff --git a/src/BLS/WasmBlsCrytography.cs b/src/BLS/WasmBlsCrytography.cs
deleted file mode 100644
index e1affca1..00000000
--- a/src/BLS/WasmBlsCrytography.cs
+++ /dev/null
@@ -1,329 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Diagnostics;
-using System.Drawing;
-using System.IO;
-using System.Net;
-using System.Runtime.InteropServices;
-using System.Text;
-using Wasmtime;
-
-namespace EdjCase.ICP.BLS
-{
- ///
- /// Class with functions around BLS signatures (ICP flavor only)
- ///
- public class WasmBlsCryptography : IBlsCryptography
- {
- private const int PublicKeyLength = 96;
- private const int SignatureLength = 48;
-
- private static object intializeLock = new object();
- private static bool isInitialized = false;
- private static WasmBlsInstance? instance;
-
- ///
- public bool VerifySignature(
- byte[] publicKey,
- byte[] messageHash,
- byte[] signature
- )
- {
- if (signature.Length != SignatureLength)
- {
- throw new ArgumentOutOfRangeException(nameof(signature), signature.Length, $"Signature must be {SignatureLength} bytes long.");
- }
- if (publicKey.Length != PublicKeyLength)
- {
- throw new ArgumentOutOfRangeException(nameof(publicKey), publicKey.Length, $"Public Key must be {PublicKeyLength} bytes long.");
- }
-
- return VerifySignatureInternal(
- publicKey,
- messageHash,
- signature
- );
- }
-
- private static bool VerifySignatureInternal(
- byte[] publicKey,
- byte[] messageHash,
- byte[] signature
- )
- {
- try
- {
-
- EnsureInitialized();
- }
- catch (DllNotFoundException ex)
- {
- throw new Exception("Unable to load the wasmtime bls library", ex);
- }
- byte[] blsPublicKey = instance!.PublicKeyDeserialize(publicKey);
-
- byte[] blsSignature = instance.SignatureDeserialize(signature);
-
- return instance.Verify(blsSignature, blsPublicKey, messageHash);
- }
-
-
- private static void EnsureInitialized()
- {
- lock (WasmBlsCryptography.intializeLock)
- {
- if (!WasmBlsCryptography.isInitialized)
- {
- if (!Environment.Is64BitProcess)
- {
- throw new PlatformNotSupportedException("not 64-bit system");
- }
- if (instance == null)
- {
- instance = WasmBlsInstance.Create();
- }
- instance.Init(Constants.BLS12_381, Constants.COMPILED_TIME_VAR);
-
- instance.SetEthSerialization(true);
- instance.SetMapToMode(WasmBlsInstance.MapToMode.HashToCurve);
- string s = "1 0x24aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8 0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e 0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801 0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be";
- byte[] gen = instance.PublicKeySetHexStr(s);
- instance.SetGeneratorOfPublicKey(gen);
- string dst = "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_";
- instance.MclBnG1SetDst(dst);
- WasmBlsCryptography.isInitialized = true;
- }
- }
- }
- }
- internal class WasmBlsInstance
- {
- private readonly Instance instance;
-
- public WasmBlsInstance(Instance instance)
- {
- this.instance = instance;
- }
-
- public void Init(int curve, int compiledVar)
- {
- int error = this.instance
- .GetFunction("blsInit")!
- .Invoke(curve, compiledVar);
- if (error != 0)
- {
- throw new Exception("BLS failed to initialize with error code: " + error);
- }
- }
-
- public void SetEthSerialization(bool isEth)
- {
- this.instance
- .GetAction("blsSetETHserialization")!
- .Invoke(isEth ? 1 : 0);
- }
-
- public void SetMapToMode(MapToMode mode)
- {
- int error = this.instance
- .GetFunction("blsSetMapToMode")!
- .Invoke((int)mode);
-
- if (error != 0)
- {
- throw new Exception("Failed while invoking: SetMapToMode");
- }
- }
-
- public void SetGeneratorOfPublicKey(byte[] publicKey)
- {
- using MallocScope scope = this.DisposableMalloc(publicKey.Length);
- scope.SetValue(publicKey);
-
- int error = this.instance
- .GetFunction("blsSetGeneratorOfPublicKey")!
- .Invoke(scope.Address);
- if (error != 0)
- {
- throw new Exception("Failed while invoking: SetGeneratorOfPublicKey");
- }
- }
-
- private MallocScope DisposableMalloc(int size)
- {
- int address = this.Malloc(size);
- return new MallocScope(this, address, size);
- }
-
-
- public int Malloc(int size)
- {
- return this.instance
- .GetFunction("blsMalloc")!
- .Invoke(size);
- }
-
- public void Free(int address)
- {
- this.instance
- .GetAction("blsFree")!
- .Invoke(address);
- }
-
- public void MclBnG1SetDst(string dst)
- {
- using MallocScope dstScope = this.DisposableMalloc(dst.Length);
- dstScope.SetValue(Encoding.ASCII.GetBytes(dst));
- int error = this.instance
- .GetFunction("mclBnG1_setDst")!
- .Invoke(dstScope.Address, dst.Length);
- if (error != 0)
- {
- throw new Exception("Failed while invoking: SetDstG1:" + dst);
- }
- }
-
- public byte[] PublicKeyDeserialize(byte[] publicKeyBytes)
- {
- using MallocScope keyScope = this.DisposableMalloc(Constants.PUBLICKEY_SIZE);
- using MallocScope bytesScope = this.DisposableMalloc(publicKeyBytes.Length);
- bytesScope.SetValue(publicKeyBytes);
-
- int publicKeyBytesRead = this.instance
- .GetFunction("blsPublicKeyDeserialize")!
- .Invoke(keyScope.Address, bytesScope.Address, publicKeyBytes.Length);
- if (publicKeyBytesRead != publicKeyBytes.Length)
- {
- throw new Exception($"Error deserializing BLS public key");
- }
- return keyScope.GetValue();
- }
-
- public byte[] SignatureDeserialize(byte[] signatureBytes)
- {
- using MallocScope sigScope = this.DisposableMalloc(Constants.SIGNATURE_SIZE);
- using MallocScope bytesScope = this.DisposableMalloc(signatureBytes.Length);
- bytesScope.SetValue(signatureBytes);
-
-
- int signatureBytesRead = this.instance
- .GetFunction("blsSignatureDeserialize")!
- .Invoke(sigScope.Address, bytesScope.Address, signatureBytes.Length);
- if (signatureBytesRead != signatureBytes.LongLength)
- {
- throw new Exception($"Error deserializing BLS signature, length: {signatureBytesRead}");
- }
- return sigScope.GetValue();
- }
-
- public bool Verify(byte[] signature, byte[] publicKey, byte[] message)
- {
- using MallocScope sigScope = this.DisposableMalloc(signature.Length);
- sigScope.SetValue(signature);
-
- using MallocScope keyScope = this.DisposableMalloc(publicKey.Length);
- keyScope.SetValue(publicKey);
-
- using MallocScope msgScope = this.DisposableMalloc(message.Length);
- msgScope.SetValue(message);
-
- int verifyResult = this.instance
- .GetFunction("blsVerify")!
- .Invoke(sigScope.Address, keyScope.Address, msgScope.Address, message.Length);
- return verifyResult == 1;
- }
-
- public byte[] PublicKeySetHexStr(string hex)
- {
- byte[] hexBytes = Encoding.ASCII.GetBytes(hex);
- using MallocScope hexScope = this.DisposableMalloc(hexBytes.Length);
- hexScope.SetValue(hexBytes);
-
- using MallocScope keyScope = this.DisposableMalloc(Constants.PUBLICKEY_SIZE);
- int error = this.instance
- .GetFunction("blsPublicKeySetHexStr")!
- .Invoke(keyScope.Address, hexScope.Address, hex.Length);
-
- if (error != 0)
- {
- throw new ArgumentException("blsPublicKeySetStr:" + hex);
- }
- return keyScope.GetValue();
- }
-
- public static WasmBlsInstance Create()
- {
- var engine = new Engine(new Config().WithReferenceTypes(true));
- using Stream stream = typeof(WasmBlsInstance)
- .Assembly
- .GetManifestResourceStream("EdjCase.ICP.BLS.bls.wasm");
- var module = Module.FromStream(engine, "hello", stream);
-
- var linker = new Linker(engine);
- var store = new Store(engine);
-
-
-
- Instance instance = linker.Instantiate(store, module);
-
-
-
- // TODO dispose?
-
- return new WasmBlsInstance(instance);
- }
- internal enum MapToMode
- {
- Original = 0, // for backward compatibility
- HashToCurve = 5, // irtf-cfrg-hash-to-curve
- }
-
- private class MallocScope : IDisposable
- {
- private WasmBlsInstance blsLib;
- public int Address { get; }
- public int Size { get; }
-
- public MallocScope(WasmBlsInstance blsLib, int address, int size)
- {
- this.blsLib = blsLib;
- this.Address = address;
- this.Size = size;
- }
-
- public void Dispose()
- {
- this.blsLib.Free(this.Address);
- }
-
- public void SetValue(Span value)
- {
- Span dst = this.blsLib.instance.GetMemory("memory")!
- .GetSpan(this.Address, this.Size);
- value.CopyTo(dst);
- }
-
- public void SetValue(T value)
- where T : unmanaged
- {
- this.blsLib.instance.GetMemory("memory")!
- .Write(this.Address, value);
- }
-
- public byte[] GetValue()
- {
- return this.blsLib.instance.GetMemory("memory")!
- .GetSpan(this.Address, this.Size)
- .ToArray();
- }
-
- public T GetValue()
- where T : unmanaged
- {
- return this.blsLib.instance.GetMemory("memory")!
- .Read(this.Address);
- }
- }
- }
-
-}
diff --git a/src/BLS/bls.wasm b/src/BLS/bls.wasm
deleted file mode 100644
index 2a296003..00000000
Binary files a/src/BLS/bls.wasm and /dev/null differ
diff --git a/src/Candid/Properties/AssemblyInfo.cs b/src/Candid/Properties/AssemblyInfo.cs
index a0e1824d..f4f42ad6 100644
--- a/src/Candid/Properties/AssemblyInfo.cs
+++ b/src/Candid/Properties/AssemblyInfo.cs
@@ -1,8 +1,10 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Candid.Tests")]
+[assembly: InternalsVisibleTo("BLS.Tests")]
[assembly: InternalsVisibleTo("WebSockets.Tests")]
[assembly: InternalsVisibleTo("Performance.Tests")]
[assembly: InternalsVisibleTo("EdjCase.ICP.ClientGenerator")]
[assembly: InternalsVisibleTo("EdjCase.ICP.Agent")]
[assembly: InternalsVisibleTo("EdjCase.ICP.WebSockets")]
+[assembly: InternalsVisibleTo("Sample.CLI")]
diff --git a/src/Candid/README.md b/src/Candid/README.md
index 42055813..7c685df5 100644
--- a/src/Candid/README.md
+++ b/src/Candid/README.md
@@ -224,4 +224,4 @@ OptionalValue -> Opt T
EmptyValue -> Empty
ReservedValue -> Reserved
NullValue -> Null
-```
+```
\ No newline at end of file
diff --git a/src/ClientGenerator/README.md b/src/ClientGenerator/README.md
index 80251bba..62c4c8be 100644
--- a/src/ClientGenerator/README.md
+++ b/src/ClientGenerator/README.md
@@ -170,4 +170,4 @@ var rewriter = new MyCustomCSharpSyntaxRewriter();
syntax = syntax.Rewrite(rewriter);
(string clientFile, List<(string Name, string Contents)> typeFiles) = syntax.GenerateFileContents();
// Write string contents to files...
-```
+```
\ No newline at end of file
diff --git a/src/WebSockets/README.md b/src/WebSockets/README.md
index ce1abbe6..a6acb65e 100644
--- a/src/WebSockets/README.md
+++ b/src/WebSockets/README.md
@@ -23,7 +23,7 @@ IWebSocketAgent webSocket = await new WebSocketBuilder(canisterId, g
.WithRootKey(rootKey) // Used to specify the network root public key, required if using a dev or other ic network
.WithCustomCandidConverter(customConverter) // Used if `AppMessage` requires a custom CandidConverter for serialization
.WithCustomClient(client) // Used to override the default WebSocket client
- .WithCustomBlsCryptography(bls) // Used to override the default bls library. See `WebGL Builds` below
+ .WithCustomBlsCryptography(bls) // Used to override the default bls library.
.BuildAndConnectAsync(cancellationToken);
```
@@ -128,16 +128,4 @@ public class WebSocketManager : MonoBehaviour
}
}
}
-```
-
-## Unity WebGL Builds
-Due to how WebGL works by converting C# to JS/WASM using IL2CPP there are a few additional steps to avoid
-incompatibilities.
-- WebGlBlsCryptography - The BLS signature verification relies on a 3rd party library and due to that library not being directly compatible with the WebGL builds, `WebGlBlsCryptography` needs to be used instead of the default `WasmBlsCryptography` class.
-
- ```cs
- var bls = new WebGlBlsCrytography();
- IWebSocketAgent webSocket = await new WebSocketBuilder(canisterId, gatewayUri)
- .WithCustomBlsCryptography(bls)
- .BuildAsync();
- ```
+```
\ No newline at end of file
diff --git a/src/WebSockets/WebSocketBuilder.cs b/src/WebSockets/WebSocketBuilder.cs
index 89eef600..19110096 100644
--- a/src/WebSockets/WebSocketBuilder.cs
+++ b/src/WebSockets/WebSocketBuilder.cs
@@ -184,7 +184,7 @@ public IWebSocketAgent Build()
}
if (this.bls == null)
{
- this.bls = new WasmBlsCryptography();
+ this.bls = new DefaultBlsCryptograhy();
}
return new WebSocketAgent(
this.canisterId,
diff --git a/test/BLS.Tests/BLS.Tests.csproj b/test/BLS.Tests/BLS.Tests.csproj
new file mode 100644
index 00000000..fd5181a2
--- /dev/null
+++ b/test/BLS.Tests/BLS.Tests.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net6.0
+ enable
+ enable
+ BLS.Tests
+
+ false
+ true
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/test/BLS.Tests/BlsUtilTests.cs b/test/BLS.Tests/BlsUtilTests.cs
new file mode 100644
index 00000000..15de721f
--- /dev/null
+++ b/test/BLS.Tests/BlsUtilTests.cs
@@ -0,0 +1,127 @@
+using EdjCase.ICP.BLS;
+using EdjCase.ICP.BLS.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BLS.Tests
+{
+ public class BlsUtilTests
+ {
+ [Fact]
+ public void SubtractWithoutBorrow()
+ {
+ (ulong value, ulong borrow) = BlsUtil.SubtractWithBorrow(10, 5, 0);
+ Assert.Equal(5UL, value);
+ Assert.Equal(0UL, borrow);
+ }
+
+ [Fact]
+ public void SubtractWithBorrow()
+ {
+ (ulong value, ulong borrow) = BlsUtil.SubtractWithBorrow(5, 10, 0);
+ Assert.Equal(18446744073709551611UL, value); // 2^64 - 5
+ Assert.Equal(1UL, borrow);
+ }
+
+ [Fact]
+ public void SubtractWithZero()
+ {
+ (ulong value, ulong borrow) = BlsUtil.SubtractWithBorrow(0, 0, 0);
+ Assert.Equal(0UL, value);
+ Assert.Equal(0UL, borrow);
+ }
+
+ [Fact]
+ public void SubtractMaxValues()
+ {
+ (ulong value, ulong borrow) = BlsUtil.SubtractWithBorrow(ulong.MaxValue, ulong.MaxValue, 0);
+ Assert.Equal(0UL, value);
+ Assert.Equal(0UL, borrow);
+ }
+
+
+ [Fact]
+ public void SubtractWithEffectiveBorrow()
+ {
+ (ulong value, ulong borrow) = BlsUtil.SubtractWithBorrow(5UL, 4UL, 1UL); // Effective borrow
+ Assert.Equal(0UL, value);
+ Assert.Equal(0UL, borrow);
+ }
+
+ [Fact]
+ public void SubtractLeadingToBorrowWithNonZeroBorrowInput()
+ {
+ // This case tests when the initial borrow itself causes a borrow.
+ (ulong value, ulong borrow) = BlsUtil.SubtractWithBorrow(0UL, 0UL, 1UL); // Non-zero borrow on zero subtraction
+ Assert.Equal(18446744073709551615UL, value); // 2^64 - 1, due to underflow
+ Assert.Equal(1UL, borrow); // Borrow occurred
+ }
+
+ [Fact]
+ public void SubtractWithZeroAndNonZeroBorrow()
+ {
+ // Testing subtraction of 0 with a non-zero borrow, expecting underflow
+ (ulong value, ulong borrow) = BlsUtil.SubtractWithBorrow(0UL, 0UL, 12345UL); // Any non-zero borrow
+ Assert.Equal(18446744073709551615UL, value); // 2^64 - 1, due to underflow
+ Assert.Equal(1UL, borrow); // Borrow occurred
+ }
+
+ // [Fact]
+ // public void MultiMillerLoop_Equal()
+ // {
+ // G1Affine a1 = G1Affine.Generator();
+ // G2Affine b1 = G2Affine.Generator();
+
+ // G1Affine a2 = new G1Affine(
+ // G1Affine.Generator() * Scalar.FromRaw(new ulong[] { 1, 2, 3, 4 }).Invert().Square()
+ // );
+ // G2Affine b2 = new G2Affine(
+ // G2Affine.Generator() * Scalar.FromRaw(new ulong[] { 4, 2, 2, 4 }).Invert().Square()
+ // );
+
+ // G1Affine a3 = G1Affine.Identity();
+ // G2Affine b3 = new G2Affine(
+ // G2Affine.Generator() * Scalar.FromRaw(new ulong[] { 9, 2, 2, 4 }).Invert().Square()
+ // );
+
+ // G1Affine a4 = new G1Affine(
+ // G1Affine.Generator() * Scalar.FromRaw(new ulong[] { 5, 5, 5, 5 }).Invert().Square()
+ // );
+ // G2Affine b4 = G2Affine.Identity();
+
+ // G1Affine a5 = new G1Affine(
+ // G1Affine.Generator() * Scalar.FromRaw(new ulong[] { 323, 32, 3, 1 }).Invert().Square()
+ // );
+ // G2Affine b5 = new G2Affine(
+ // G2Affine.Generator() * Scalar.FromRaw(new ulong[] { 4, 2, 2, 9099 }).Invert().Square()
+ // );
+
+ // G2Prepared b1Prepared = new G2Prepared(b1);
+ // G2Prepared b2Prepared = new G2Prepared(b2);
+ // G2Prepared b3Prepared = new G2Prepared(b3);
+ // G2Prepared b4Prepared = new G2Prepared(b4);
+ // G2Prepared b5Prepared = new G2Prepared(b5);
+
+ // var expected = Pairing.Pair(a1, b1)
+ // + Pairing.Pair(a2, b2)
+ // + Pairing.Pair(a3, b3)
+ // + Pairing.Pair(a4, b4)
+ // + Pairing.Pair(a5, b5);
+
+ // var test = MultiMillerLoop(new[]
+ // {
+ // (a1, b1Prepared),
+ // (a2, b2Prepared),
+ // (a3, b3Prepared),
+ // (a4, b4Prepared),
+ // (a5, b5Prepared)
+ //}).FinalExponentiation();
+
+ // Assert.Equal(expected, test);
+ // }
+
+ }
+}
diff --git a/test/BLS.Tests/DefaultBlsCryptographyTests.cs b/test/BLS.Tests/DefaultBlsCryptographyTests.cs
new file mode 100644
index 00000000..3de8ce03
--- /dev/null
+++ b/test/BLS.Tests/DefaultBlsCryptographyTests.cs
@@ -0,0 +1,92 @@
+using EdjCase.ICP.BLS;
+using EdjCase.ICP.BLS.Models;
+using EdjCase.ICP.Candid.Utilities;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+
+namespace BLS.Tests
+{
+ public class DefaultBlsCryptographyTests
+ {
+ [Theory]
+ [InlineData(
+ "814c0e6ec71fab583b08bd81373c255c3c371b2e84863c98a4f1e08b74235d14fb5d9c0cd546d9685f913a0c0b2cc5341583bf4b4392e467db96d65b9bb4cb717112f8472e0d5a4d14505ffd7484b01291091c5f87b98883463f98091a0baaae",
+ "0d69632d73746174652d726f6f74e6c01e909b4923345ce5970962bcfe3004bfd8474a21dae28f50692502f46d90",
+ "ace9fcdd9bc977e05d6328f889dc4e7c99114c737a494653cb27a1f55c06f4555e0f160980af5ead098acc195010b2f7"
+ )]
+ [InlineData(
+ "9933e1f89e8a3c4d7fdcccdbd518089e2bd4d8180a261f18d9c247a52768ebce98dc7328a39814a8f911086a1dd50cbe015e2a53b7bf78b55288893daa15c346640e8831d72a12bdedd979d28470c34823b8d1c3f4795d9c3984a247132e94fe",
+ "0d69632d73746174652d726f6f74b294b418b11ebe5dd7dd1dcb099e4e0372b9a42aef7a7a37fb4f25667d705ea9",
+ "89a2be21b5fa8ac9fab1527e041327ce899d7da971436a1f2165393947b4d942365bfe5488710e61a619ba48388a21b1"
+ )]
+ public void VerifySignature(string publicKeyHex, string msgHex, string signatureHex)
+ {
+ byte[] publicKey = ByteUtil.FromHexString(publicKeyHex);
+ byte[] msg = ByteUtil.FromHexString(msgHex);
+ byte[] signature = ByteUtil.FromHexString(signatureHex);
+
+ bool isValid = new DefaultBlsCryptograhy().VerifySignature(publicKey, msg, signature);
+ Assert.True(isValid);
+ }
+
+ [Theory]
+ [InlineData(
+ "814c0e6ec71fab583b08bd81373c255c3c371b2e84863c98a4f1e08b74235d14fb5d9c0cd546d9685f913a0c0b2cc5341583bf4b4392e467db96d65b9bb4cb717112f8472e0d5a4d14505ffd7484b01291091c5f87b98883463f98091a0baaae",
+ "0d69632d73746174652d726f6f74e6c01e909b4923345ce5970962bcfe3004bfd8474a21dae28f50692502f46d90",
+ "89a2be21b5fa8ac9fab1527e041327ce899d7da971436a1f2165393947b4d942365bfe5488710e61a619ba48388a21b1"
+ )]
+ [InlineData(
+ "9933e1f89e8a3c4d7fdcccdbd518089e2bd4d8180a261f18d9c247a52768ebce98dc7328a39814a8f911086a1dd50cbe015e2a53b7bf78b55288893daa15c346640e8831d72a12bdedd979d28470c34823b8d1c3f4795d9c3984a247132e94fe",
+ "0d69632d73746174652d726f6f74b294b418b11ebe5dd7dd1dcb099e4e0372b9a42aef7a7a37fb4f25667d705ea9",
+ "ace9fcdd9bc977e05d6328f889dc4e7c99114c737a494653cb27a1f55c06f4555e0f160980af5ead098acc195010b2f7"
+ )]
+ public void VerifySignature__Invalid(string publicKeyHex, string msgHex, string signatureHex)
+ {
+ byte[] publicKey = ByteUtil.FromHexString(publicKeyHex);
+ byte[] msg = ByteUtil.FromHexString(msgHex);
+ byte[] signature = ByteUtil.FromHexString(signatureHex);
+
+ bool isValid = new DefaultBlsCryptograhy().VerifySignature(publicKey, msg, signature);
+ Assert.False(isValid);
+ }
+
+ // [Theory]
+ // [InlineData(
+ // "",
+ // "hMW6jNJQe93jbkaX0CfwuJUHmHHFh+7DY819un+7BfxKvHjAFQ9Glqr4jhBDqXPD"
+ // )]
+ // public void HashToCurveG1(
+ // string msgHex,
+ // string g1CompressedBase64
+ // )
+ // {
+ // byte[] msg = ByteUtil.FromHexString(msgHex);
+ // byte[] g1Compressed = Convert.FromBase64String(g1CompressedBase64);
+ // G1Projective g1Expected = G1Projective.FromCompressed(g1Compressed);
+
+ // G1Projective g1Actual = G1Projective.HashToCurve(msg, DefaultBlsCryptograhy.DomainSeperator);
+
+ // Assert.True(g1Expected.Equals(g1Actual));
+ // }
+ // [Theory]
+ // [InlineData(
+ // "",
+ // "qKqzA+M+0U9KkEAEqSvSb/yWnB0efUt/DAQVCnPhhFqRHlGistNp1c7wZWDFrJ9XFcAVZpk9RGmAXfPh8ptTZIGoMr8nUbaQj67Wd20GLVhVIYiSMpmdcrZ51uOLtc//"
+ // )]
+ // public void HashToCurveG2(
+ // string msgHex,
+ // string g2CompressedBase64
+ // )
+ // {
+ // byte[] msg = ByteUtil.FromHexString(msgHex);
+ // byte[] g2Compressed = Convert.FromBase64String(g2CompressedBase64);
+ // G2Projective g2Expected = G2Projective.FromCompressed(g2Compressed);
+
+ // G2Projective g2Actual = G2Projective.HashToCurve(msg, DefaultBlsCryptograhy.DomainSeperator);
+
+ // Assert.True(g2Expected.Equals(g2Actual));
+ // }
+
+
+ }
+}
diff --git a/test/BLS.Tests/FPTests.cs b/test/BLS.Tests/FPTests.cs
new file mode 100644
index 00000000..a65417fd
--- /dev/null
+++ b/test/BLS.Tests/FPTests.cs
@@ -0,0 +1,307 @@
+using EdjCase.ICP.BLS.Models;
+
+
+namespace BLS.Tests
+{
+ public class FpTests
+ {
+ [Theory]
+ [InlineData(new ulong[] { 1, 2, 3, 4, 5, 6 }, new ulong[] { 1, 2, 3, 4, 5, 6 }, true)] // Equal
+ [InlineData(new ulong[] { 7, 2, 3, 4, 5, 6 }, new ulong[] { 1, 2, 3, 4, 5, 6 }, false)] // Not equal
+ [InlineData(new ulong[] { 1, 7, 3, 4, 5, 6 }, new ulong[] { 1, 2, 3, 4, 5, 6 }, false)] // Not equal
+ [InlineData(new ulong[] { 1, 2, 7, 4, 5, 6 }, new ulong[] { 1, 2, 3, 4, 5, 6 }, false)] // Not equal
+ [InlineData(new ulong[] { 1, 2, 3, 7, 5, 6 }, new ulong[] { 1, 2, 3, 4, 5, 6 }, false)] // Not equal
+ [InlineData(new ulong[] { 1, 2, 3, 4, 7, 6 }, new ulong[] { 1, 2, 3, 4, 5, 6 }, false)] // Not equal
+ [InlineData(new ulong[] { 1, 2, 3, 4, 5, 7 }, new ulong[] { 1, 2, 3, 4, 5, 6 }, false)] // Not equal
+ public void Equals_Equal(ulong[] valuesA, ulong[] valuesB, bool expected)
+ {
+ // Assuming Fp constructor takes an array of ulongs
+ Fp a = new (
+ valuesA[0],
+ valuesA[1],
+ valuesA[2],
+ valuesA[3],
+ valuesA[4],
+ valuesA[5]
+ );
+ Fp b = new(
+ valuesB[0],
+ valuesB[1],
+ valuesB[2],
+ valuesB[3],
+ valuesB[4],
+ valuesB[5]
+ );
+
+ Assert.Equal(expected, a.Equals(b));
+ }
+ [Fact]
+ public void Multiply_Equal()
+ {
+ Fp a = new(
+ 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 = new(
+ 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 = new(
+ 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.Equal(c, a * b);
+ }
+
+ [Fact]
+ public void Add_Equal()
+ {
+ Fp a = new(
+ 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 = new(
+ 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 = new(
+ 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.Equal(c, a + b);
+ }
+
+ [Fact]
+ public void Subtract_Equal()
+ {
+ Fp a = new(
+ 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 = new(
+ 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 = new(
+ 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.Equal(c, a - b);
+ }
+
+ [Fact]
+ public void Square_Equal()
+ {
+ Fp a = new(
+ 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 = new(
+ 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.Equal(b, a.Square());
+ }
+
+ [Fact]
+ public void Neg_Equal()
+ {
+ Fp a = new(
+ 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 = new(
+ 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.Equal(b, a.Neg());
+ }
+
+ [Fact]
+ public void FromBytes_SquareEqual()
+ {
+ Fp a = new(
+ 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.ToBytes();
+ Fp b = Fp.FromBytes(tmp);
+ Assert.Equal(a, b);
+ }
+ }
+ [Fact]
+ public void FromBytes_NegativeOne()
+ {
+
+ var negativeOneBytes = 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.Equal(Fp.One().Neg(), Fp.FromBytes(negativeOneBytes));
+ }
+
+ [Fact]
+ public void FromBytes_InvalidBytes_Error()
+ {
+ var invalidBytes = 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.Throws(() => Fp.FromBytes(invalidBytes));
+ }
+
+ [Fact]
+ public void FromBytes_OverflowBytes_Error()
+ {
+ var overflowBytes = new byte[48];
+ Array.Fill(overflowBytes, (byte)0xff);
+ Assert.Throws(() => Fp.FromBytes(overflowBytes));
+ }
+
+ [Fact]
+ public void Invert()
+ {
+ Fp a = new(
+ 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 = new(
+ 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.Equal(b, a.Invert());
+ }
+
+ [Fact]
+ public void Zero_NotLexicographicallyLargest()
+ {
+ var zero = Fp.Zero();
+ Assert.False(zero.LexicographicallyLargest());
+ }
+
+ [Fact]
+ public void One_NotLexicographicallyLargest()
+ {
+ var one = Fp.One();
+ Assert.False(one.LexicographicallyLargest());
+ }
+
+ [Fact]
+ public void SpecificValue1_NotLexicographicallyLargest()
+ {
+ Fp fp = new (
+ 0xa1fa_ffff_fffe_5557,
+ 0x995b_fff9_76a3_fffe,
+ 0x03f4_1d24_d174_ceb4,
+ 0xf654_7998_c199_5dbd,
+ 0x778a_468f_507a_6034,
+ 0x0205_5993_1f7f_8103
+ );
+ Assert.False(fp.LexicographicallyLargest());
+ }
+
+ [Fact]
+ public void SpecificValue2_LexicographicallyLargest()
+ {
+ Fp fp = new (
+ 0x1804_0000_0001_5554,
+ 0x8550_0005_3ab0_0001,
+ 0x633c_b57c_253c_276f,
+ 0x6e22_d1ec_31eb_b502,
+ 0xd391_6126_f2d1_4ca2,
+ 0x17fb_b857_1a00_6596
+ );
+ Assert.True(fp.LexicographicallyLargest());
+ }
+
+ [Fact]
+ public void SpecificValue3_LexicographicallyLargest()
+ {
+ Fp fp = new (
+ 0x43f5_ffff_fffc_aaae,
+ 0x32b7_fff2_ed47_fffd,
+ 0x07e8_3a49_a2e9_9d69,
+ 0xeca8_f331_8332_bb7a,
+ 0xef14_8d1e_a0f4_c069,
+ 0x040a_b326_3eff_0206
+ );
+ Assert.True(fp.LexicographicallyLargest());
+ }
+
+ }
+}
diff --git a/test/BLS.Tests/Fp12Tests.cs b/test/BLS.Tests/Fp12Tests.cs
new file mode 100644
index 00000000..55ae2f83
--- /dev/null
+++ b/test/BLS.Tests/Fp12Tests.cs
@@ -0,0 +1,373 @@
+using EdjCase.ICP.BLS.Models;
+
+
+namespace BLS.Tests
+{
+ public class Fp12Tests
+ {
+ [Fact]
+ public void TestArithmetic()
+ {
+ var a = new Fp12(
+ new Fp6(
+ new Fp2(
+ new Fp(
+ 0x47f9_cb98_b1b8_2d58,
+ 0x5fe9_11eb_a3aa_1d9d,
+ 0x96bf_1b5f_4dd8_1db3,
+ 0x8100_d272_c925_9f5b,
+ 0xafa2_0b96_7464_0eab,
+ 0x09bb_cea7_d8d9_497d
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0x46f9_cb98_b162_d858,
+ 0x0be9_109c_f7aa_1d57,
+ 0xc791_bc55_fece_41d2,
+ 0xf84c_5770_4e38_5ec2,
+ 0xcb49_c1d9_c010_e60f,
+ 0x0acd_b8e1_58bf_e348
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0xcee5_cb98_b15c_2db4,
+ 0x7159_1082_d23a_1d51,
+ 0xd762_30e9_44a1_7ca4,
+ 0xd19e_3dd3_549d_d5b6,
+ 0xa972_dc17_01fa_66e3,
+ 0x12e3_1f2d_d6bd_e7d6
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0x47f9_cb98_b1b8_2d58,
+ 0x5fe9_11eb_a3aa_1d9d,
+ 0x96bf_1b5f_4dd2_1db3,
+ 0x8100_d27c_c925_9f5b,
+ 0xafa2_0b96_7464_0eab,
+ 0x09bb_cea7_d8d9_497d
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0x46f9_cb98_b162_d858,
+ 0x0be9_109c_f7aa_1d57,
+ 0xc791_bc55_fece_41d2,
+ 0xf84c_5770_4e38_5ec2,
+ 0xcb49_c1d9_c010_e60f,
+ 0x0acd_b8e1_58bf_e3c8
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0xcee5_cb98_b15c_2db4,
+ 0x7159_1082_d23a_1d51,
+ 0xd762_30e9_44a1_7ca4,
+ 0xd19e_3dd3_549d_d5b6,
+ 0xa972_dc17_01fa_66e3,
+ 0x12e3_1f2d_d6bd_e7d6
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0x47f9_cb98_b1b8_2d58,
+ 0x5fe9_11eb_a3aa_1d9d,
+ 0x96bf_1b5f_4dd8_1db3,
+ 0x8100_d272_c925_9f5b,
+ 0xafa2_0b96_7464_0eab,
+ 0x09bb_cea7_d8d9_497d
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0x46f9_cb98_b162_d858,
+ 0x0be9_109c_f7aa_1d57,
+ 0xc791_bc55_fece_41d2,
+ 0xf84c_5770_4e38_5ec2,
+ 0xcb49_c1d9_c010_e60f,
+ 0x0acd_b8e1_58bf_e348
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0xcee5_cb98_b15c_2db4,
+ 0x7159_1082_d23a_1d51,
+ 0xd762_30e9_44a1_7ca4,
+ 0xd19e_3dd3_549d_d5b6,
+ 0xa972_dc17_01fa_66e3,
+ 0x12e3_1f2d_d6bd_e7d6
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0x47f9_cb98_b1b8_2d58,
+ 0x5fe9_11eb_a3aa_1d9d,
+ 0x96bf_1b5f_4dd2_1db3,
+ 0x8100_d27c_c925_9f5b,
+ 0xafa2_0b96_7464_0eab,
+ 0x09bb_cea7_d8d9_497d
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0x46f9_cb98_b162_d858,
+ 0x0be9_109c_f7aa_1d57,
+ 0xc791_bc55_fece_41d2,
+ 0xf84c_5770_4e38_5ec2,
+ 0xcb49_c1d9_c010_e60f,
+ 0x0acd_b8e1_58bf_e3c8
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0xcee5_cb98_b15c_2db4,
+ 0x7159_1082_d23a_1d51,
+ 0xd762_30e9_44a1_7ca4,
+ 0xd19e_3dd3_549d_d5b6,
+ 0xa972_dc17_01fa_66e3,
+ 0x12e3_1f2d_d6bd_e7d6
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0x47f9_cb98_b1b8_2d58,
+ 0x5fe9_11eb_a3aa_1d9d,
+ 0x96bf_1b5f_4dd8_1db3,
+ 0x8100_d272_c925_9f5b,
+ 0xafa2_0b96_7464_0eab,
+ 0x09bb_cea7_d8d9_497d
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0x46f9_cb98_b162_d858,
+ 0x0be9_109c_f7aa_1d57,
+ 0xc791_bc55_fece_41d2,
+ 0xf84c_5770_4e38_5ec2,
+ 0xcb49_c1d9_c010_e60f,
+ 0x0acd_b8e1_58bf_e348
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0xcee5_cb98_b15c_2db4,
+ 0x7159_1082_d23a_1d51,
+ 0xd762_30e9_44a1_7ca4,
+ 0xd19e_3dd3_549d_d5b6,
+ 0xa972_dc17_01fa_66e3,
+ 0x12e3_1f2d_d6bd_e7d6
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0x47f9_cb98_b1b8_2d58,
+ 0x5fe9_11eb_a3aa_1d9d,
+ 0x96bf_1b5f_4dd2_1db3,
+ 0x8100_d27c_c925_9f5b,
+ 0xafa2_0b96_7464_0eab,
+ 0x09bb_cea7_d8d9_497d
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0x46f9_cb98_b162_d858,
+ 0x0be9_109c_f7aa_1d57,
+ 0xc791_bc55_fece_41d2,
+ 0xf84c_5770_4e38_5ec2,
+ 0xcb49_c1d9_c010_e60f,
+ 0x0acd_b8e1_58bf_e3c8
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0xcee5_cb98_b15c_2db4,
+ 0x7159_1082_d23a_1d51,
+ 0xd762_30e9_44a1_7ca4,
+ 0xd19e_3dd3_549d_d5b6,
+ 0xa972_dc17_01fa_66e3,
+ 0x12e3_1f2d_d6bd_e7d6
+ ),
+ new Fp(
+ 0xad2a_cb98_b173_2d9d,
+ 0x2cfd_10dd_0696_1d64,
+ 0x0739_6b86_c6ef_24e8,
+ 0xbd76_e2fd_b1bf_c820,
+ 0x6afe_a7f6_de94_d0d5,
+ 0x1099_4b0c_5744_c040
+ )
+ )
+ )
+ );
+
+
+ a = a.Square().Invert()!.Value.Square().Add(c);
+ b = b.Square().Invert()!.Value.Square().Add(a);
+ c = c.Square().Invert()!.Value.Square().Add(b);
+
+ Assert.Equal(a.Square(), a.Multiply(a));
+ Assert.Equal(b.Square(), b.Multiply(b));
+ Assert.Equal(c.Square(), c.Multiply(c));
+
+ Assert.Equal((a.Add(b)).Multiply(c.Square()), (c.Square().Multiply(a)).Add(c.Square().Multiply(b)));
+
+ Assert.Equal(a.Invert()!.Value.Multiply(b.Invert()!.Value), (a.Multiply(b)).Invert());
+ Assert.Equal(a.Invert()!.Value.Multiply(a), Fp12.One());
+
+ Assert.NotEqual(a, a.FrobeniusMap());
+ Assert.Equal(a, a.FrobeniusMap().FrobeniusMap().FrobeniusMap().FrobeniusMap().FrobeniusMap().FrobeniusMap().FrobeniusMap().FrobeniusMap().FrobeniusMap().FrobeniusMap().FrobeniusMap().FrobeniusMap());
+ }
+ }
+}
diff --git a/test/BLS.Tests/Fp2Tests.cs b/test/BLS.Tests/Fp2Tests.cs
new file mode 100644
index 00000000..cf413454
--- /dev/null
+++ b/test/BLS.Tests/Fp2Tests.cs
@@ -0,0 +1,471 @@
+using EdjCase.ICP.BLS.Models;
+
+
+namespace BLS.Tests
+{
+ public class Fp2Tests
+ {
+ [Fact]
+ public void Equals_Equal()
+ {
+ Fp2 a = new Fp2(
+ new Fp(1, 2, 3, 4, 5, 6),
+ new Fp(7, 8, 9, 10, 11, 12)
+ );
+ Fp2 b = new Fp2(
+ new Fp(1, 2, 3, 4, 5, 6),
+ new Fp(7, 8, 9, 10, 11, 12)
+ );
+
+ Assert.True(a.Equals(b));
+ }
+
+ [Fact]
+ public void Equals_NotEqual_FirstComponentDiffers()
+ {
+ Fp2 a = new Fp2(
+ new Fp(2, 2, 3, 4, 5, 6),
+ new Fp(7, 8, 9, 10, 11, 12)
+ );
+ Fp2 b = new Fp2(
+ new Fp(1, 2, 3, 4, 5, 6),
+ new Fp(7, 8, 9, 10, 11, 12)
+ );
+
+ Assert.False(a.Equals(b));
+ }
+
+ [Fact]
+ public void Equals_NotEqual_SecondComponentDiffers()
+ {
+ Fp2 a = new Fp2(
+ new Fp(1, 2, 3, 4, 5, 6),
+ new Fp(2, 8, 9, 10, 11, 12)
+ );
+ Fp2 b = new Fp2(
+ new Fp(1, 2, 3, 4, 5, 6),
+ new Fp(7, 8, 9, 10, 11, 12)
+ );
+
+ Assert.False(a.Equals(b));
+ }
+
+ [Fact]
+ public void Square_Equal()
+ {
+ Fp2 a = new Fp2(
+ new Fp(
+ 0xc9a2_1831_63ee_70d4,
+ 0xbc37_70a7_196b_5c91,
+ 0xa247_f8c1_304c_5f44,
+ 0xb01f_c2a3_726c_80b5,
+ 0xe1d2_93e5_bbd9_19c9,
+ 0x04b7_8e80_020e_f2ca
+ ),
+ new Fp(
+ 0x952e_a446_0462_618f,
+ 0x238d_5edd_f025_c62f,
+ 0xf6c9_4b01_2ea9_2e72,
+ 0x03ce_24ea_c1c9_3808,
+ 0x0559_50f9_45da_483c,
+ 0x010a_768d_0df4_eabc
+ )
+ );
+ Fp2 expected = new Fp2(
+ new Fp(
+ 0xa1e0_9175_a4d2_c1fe,
+ 0x8b33_acfc_204e_ff12,
+ 0xe244_15a1_1b45_6e42,
+ 0x61d9_96b1_b6ee_1936,
+ 0x1164_dbe8_667c_853c,
+ 0x0788_557a_cc7d_9c79
+ ),
+ new Fp(
+ 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.Equal(expected, a.Square());
+ }
+
+ [Fact]
+ public void Test_Multiplication()
+ {
+ Fp2 a = new Fp2(
+ new Fp(0xc9a2_1831_63ee_70d4, 0xbc37_70a7_196b_5c91, 0xa247_f8c1_304c_5f44, 0xb01f_c2a3_726c_80b5, 0xe1d2_93e5_bbd9_19c9, 0x04b7_8e80_020e_f2ca),
+ new Fp(0x952e_a446_0462_618f, 0x238d_5edd_f025_c62f, 0xf6c9_4b01_2ea9_2e72, 0x03ce_24ea_c1c9_3808, 0x0559_50f9_45da_483c, 0x010a_768d_0df4_eabc)
+ );
+
+ Fp2 b = new Fp2(
+ new Fp(0xa1e0_9175_a4d2_c1fe, 0x8b33_acfc_204e_ff12, 0xe244_15a1_1b45_6e42, 0x61d9_96b1_b6ee_1936, 0x1164_dbe8_667c_853c, 0x0788_557a_cc7d_9c79),
+ new Fp(0xda6a_87cc_6f48_fa36, 0x0fc7_b488_277c_1903, 0x9445_ac4a_dc44_8187, 0x0261_6d5b_c909_9209, 0xdbed_4677_2db5_8d48, 0x11b9_4d50_76c7_b7b1)
+ );
+
+ Fp2 expected = new Fp2(
+ new Fp(0xf597_483e_27b4_e0f7, 0x610f_badf_811d_ae5f, 0x8432_af91_7714_327a, 0x6a9a_9603_cf88_f09e, 0xf05a_7bf8_bad0_eb01, 0x0954_9131_c003_ffae),
+ new Fp(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.Equal(expected, a * b);
+ }
+
+ [Fact]
+ public void Addition_Equal()
+ {
+ Fp2 a = new Fp2(
+ new Fp(
+ 0xc9a2_1831_63ee_70d4,
+ 0xbc37_70a7_196b_5c91,
+ 0xa247_f8c1_304c_5f44,
+ 0xb01f_c2a3_726c_80b5,
+ 0xe1d2_93e5_bbd9_19c9,
+ 0x04b7_8e80_020e_f2ca
+ ),
+ new Fp(
+ 0x952e_a446_0462_618f,
+ 0x238d_5edd_f025_c62f,
+ 0xf6c9_4b01_2ea9_2e72,
+ 0x03ce_24ea_c1c9_3808,
+ 0x0559_50f9_45da_483c,
+ 0x010a_768d_0df4_eabc
+ )
+ );
+ Fp2 b = new Fp2(
+ new Fp(
+ 0xa1e0_9175_a4d2_c1fe,
+ 0x8b33_acfc_204e_ff12,
+ 0xe244_15a1_1b45_6e42,
+ 0x61d9_96b1_b6ee_1936,
+ 0x1164_dbe8_667c_853c,
+ 0x0788_557a_cc7d_9c79
+ ),
+ new Fp(
+ 0xda6a_87cc_6f48_fa36,
+ 0x0fc7_b488_277c_1903,
+ 0x9445_ac4a_dc44_8187,
+ 0x0261_6d5b_c909_9209,
+ 0xdbed_4677_2db5_8d48,
+ 0x11b9_4d50_76c7_b7b1
+ )
+ );
+ Fp2 c = new Fp2(
+ new Fp(
+ 0x6b82_a9a7_08c1_32d2,
+ 0x476b_1da3_39ba_5ba4,
+ 0x848c_0e62_4b91_cd87,
+ 0x11f9_5955_295a_99ec,
+ 0xf337_6fce_2255_9f06,
+ 0x0c3f_e3fa_ce8c_8f43
+ ),
+ new Fp(
+ 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.Equal(c, a + b);
+ }
+
+ [Fact]
+ public void Subtract_Equal()
+ {
+ Fp2 a = new Fp2(
+ new Fp(
+ 0xc9a2_1831_63ee_70d4,
+ 0xbc37_70a7_196b_5c91,
+ 0xa247_f8c1_304c_5f44,
+ 0xb01f_c2a3_726c_80b5,
+ 0xe1d2_93e5_bbd9_19c9,
+ 0x04b7_8e80_020e_f2ca
+ ),
+ new Fp(
+ 0x952e_a446_0462_618f,
+ 0x238d_5edd_f025_c62f,
+ 0xf6c9_4b01_2ea9_2e72,
+ 0x03ce_24ea_c1c9_3808,
+ 0x0559_50f9_45da_483c,
+ 0x010a_768d_0df4_eabc
+ )
+ );
+ Fp2 b = new Fp2(
+ new Fp(
+ 0xa1e0_9175_a4d2_c1fe,
+ 0x8b33_acfc_204e_ff12,
+ 0xe244_15a1_1b45_6e42,
+ 0x61d9_96b1_b6ee_1936,
+ 0x1164_dbe8_667c_853c,
+ 0x0788_557a_cc7d_9c79
+ ),
+ new Fp(
+ 0xda6a_87cc_6f48_fa36,
+ 0x0fc7_b488_277c_1903,
+ 0x9445_ac4a_dc44_8187,
+ 0x0261_6d5b_c909_9209,
+ 0xdbed_4677_2db5_8d48,
+ 0x11b9_4d50_76c7_b7b1
+ )
+ );
+ Fp2 c = new Fp2(
+ new Fp(
+ 0xe1c0_86bb_bf1b_5981,
+ 0x4faf_c3a9_aa70_5d7e,
+ 0x2734_b5c1_0bb7_e726,
+ 0xb2bd_7776_af03_7a3e,
+ 0x1b89_5fb3_98a8_4164,
+ 0x1730_4aef_6f11_3cec
+ ),
+ new Fp(
+ 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.Equal(c, a - b);
+ }
+
+ [Fact]
+ public void Negation_Equal()
+ {
+ Fp2 a = new(
+ new Fp(
+ 0xc9a2_1831_63ee_70d4,
+ 0xbc37_70a7_196b_5c91,
+ 0xa247_f8c1_304c_5f44,
+ 0xb01f_c2a3_726c_80b5,
+ 0xe1d2_93e5_bbd9_19c9,
+ 0x04b7_8e80_020e_f2ca
+ ),
+ new Fp(
+ 0x952e_a446_0462_618f,
+ 0x238d_5edd_f025_c62f,
+ 0xf6c9_4b01_2ea9_2e72,
+ 0x03ce_24ea_c1c9_3808,
+ 0x0559_50f9_45da_483c,
+ 0x010a_768d_0df4_eabc
+ )
+ );
+ Fp2 b = new(
+ new Fp(
+ 0xf05c_e7ce_9c11_39d7,
+ 0x6274_8f57_97e8_a36d,
+ 0xc4e8_d9df_c664_96df,
+ 0xb457_88e1_8118_9209,
+ 0x6949_13d0_8772_930d,
+ 0x1549_836a_3770_f3cf
+ ),
+ new Fp(
+ 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.Equal(b, a.Neg());
+ }
+
+ [Fact]
+ public void SquareRoot_Equal()
+ {
+ Fp2 a = new Fp2(
+ new Fp(
+ 0x2bee_d146_27d7_f9e9,
+ 0xb661_4e06_660e_5dce,
+ 0x06c4_cc7c_2f91_d42c,
+ 0x996d_7847_4b7a_63cc,
+ 0xebae_bc4c_820d_574e,
+ 0x1886_5e12_d93f_d845
+ ),
+ new Fp(
+ 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.Equal(a, a.SquareRoot()!.Value.Square());
+
+ Fp2 b = new Fp2(
+ new Fp(
+ 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.Equal(b, b.SquareRoot()!.Value.Square());
+
+ Fp2 c = new Fp2(
+ new Fp(
+ 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.Equal(c, c.SquareRoot()!.Value.Square());
+
+ Fp2 d = new Fp2(
+ new Fp(
+ 0xc5fa_1bc8_fd00_d7f6,
+ 0x3830_ca45_4606_003b,
+ 0x2b28_7f11_04b1_02da,
+ 0xa7fb_30f2_8230_f23e,
+ 0x339c_db9e_e953_dbf0,
+ 0x0d78_ec51_d989_fc57
+ ),
+ new Fp(
+ 0x27ec_4898_cf87_f613,
+ 0x9de1_394e_1abb_05a5,
+ 0x0947_f85d_c170_fc14,
+ 0x586f_bc69_6b61_14b7,
+ 0x2b34_75a4_077d_7169,
+ 0x13e1_c895_cc4b_6c22
+ )
+ );
+
+ Assert.Null(d.SquareRoot());
+ }
+ [Fact]
+ public void Invert_Equal()
+ {
+ Fp2 a = new Fp2(
+ new Fp(
+ 0x1128_ecad_6754_9455,
+ 0x9e7a_1cff_3a4e_a1a8,
+ 0xeb20_8d51_e08b_cf27,
+ 0xe98a_d408_11f5_fc2b,
+ 0x736c_3a59_232d_511d,
+ 0x10ac_d42d_29cf_cbb6
+ ),
+ new Fp(
+ 0xd328_e37c_c2f5_8d41,
+ 0x948d_f085_8a60_5869,
+ 0x6032_f9d5_6f93_a573,
+ 0x2be4_83ef_3fff_dc87,
+ 0x30ef_61f8_8f48_3c2a,
+ 0x1333_f55a_3572_5be0
+ )
+ );
+
+ Fp2 b = new Fp2(
+ new Fp(
+ 0x0581_a133_3d4f_48a6,
+ 0x5824_2f6e_f074_8500,
+ 0x0292_c955_349e_6da5,
+ 0xba37_721d_dd95_fcd0,
+ 0x70d1_6790_3aa5_dfc5,
+ 0x1189_5e11_8b58_a9d5
+ ),
+ new Fp(
+ 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.Equal(b, a.Invert());
+
+ Assert.True(Fp2.Zero().Invert() is null);
+ }
+
+ [Fact]
+ public void LexicographicLargest()
+ {
+ Assert.False(Fp2.Zero().LexicographicallyLargest());
+ Assert.False(Fp2.One().LexicographicallyLargest());
+
+ Assert.True(new Fp2(
+ new Fp(
+ 0x1128_ecad_6754_9455,
+ 0x9e7a_1cff_3a4e_a1a8,
+ 0xeb20_8d51_e08b_cf27,
+ 0xe98a_d408_11f5_fc2b,
+ 0x736c_3a59_232d_511d,
+ 0x10ac_d42d_29cf_cbb6
+ ),
+ new Fp(
+ 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.False(new Fp2(
+ new Fp(
+ 0x1128_ecad_6754_9455,
+ 0x9e7a_1cff_3a4e_a1a8,
+ 0xeb20_8d51_e08b_cf27,
+ 0xe98a_d408_11f5_fc2b,
+ 0x736c_3a59_232d_511d,
+ 0x10ac_d42d_29cf_cbb6
+ ).Neg(),
+ new Fp(
+ 0xd328_e37c_c2f5_8d41,
+ 0x948d_f085_8a60_5869,
+ 0x6032_f9d5_6f93_a573,
+ 0x2be4_83ef_3fff_dc87,
+ 0x30ef_61f8_8f48_3c2a,
+ 0x1333_f55a_3572_5be0
+ ).Neg()
+ ).LexicographicallyLargest());
+
+ Assert.False(new Fp2(
+ new Fp(
+ 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.True(new Fp2(
+ new Fp(
+ 0x1128_ecad_6754_9455,
+ 0x9e7a_1cff_3a4e_a1a8,
+ 0xeb20_8d51_e08b_cf27,
+ 0xe98a_d408_11f5_fc2b,
+ 0x736c_3a59_232d_511d,
+ 0x10ac_d42d_29cf_cbb6
+ ).Neg(),
+ Fp.Zero()
+ ).LexicographicallyLargest());
+ }
+
+ }
+}
diff --git a/test/BLS.Tests/Fp6Tests.cs b/test/BLS.Tests/Fp6Tests.cs
new file mode 100644
index 00000000..9b5f63e1
--- /dev/null
+++ b/test/BLS.Tests/Fp6Tests.cs
@@ -0,0 +1,197 @@
+using EdjCase.ICP.BLS.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BLS.Tests
+{
+ public class Fp6Tests
+ {
+ [Fact]
+ public void Arithmetic_SquareAndMultiply_Equal()
+ {
+ var a = new Fp6(
+ new Fp2(
+ new Fp(
+ 0x47f9_cb98_b1b8_2d58,
+ 0x5fe9_11eb_a3aa_1d9d,
+ 0x96bf_1b5f_4dd8_1db3,
+ 0x8100_d27c_c925_9f5b,
+ 0xafa2_0b96_7464_0eab,
+ 0x09bb_cea7_d8d9_497d
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0x46f9_cb98_b162_d858,
+ 0x0be9_109c_f7aa_1d57,
+ 0xc791_bc55_fece_41d2,
+ 0xf84c_5770_4e38_5ec2,
+ 0xcb49_c1d9_c010_e60f,
+ 0x0acd_b8e1_58bf_e3c8
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0xcee5_cb98_b15c_2db4,
+ 0x7159_1082_d23a_1d51,
+ 0xd762_30e9_44a1_7ca4,
+ 0xd19e_3dd3_549d_d5b6,
+ 0xa972_dc17_01fa_66e3,
+ 0x12e3_1f2d_d6bd_e7d6
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0xf120_cb98_b16f_d84b,
+ 0x5fb5_10cf_f3de_1d61,
+ 0x0f21_a5d0_69d8_c251,
+ 0xaa1f_d62f_34f2_839a,
+ 0x5a13_3515_7f89_913f,
+ 0x14a3_fe32_9643_c247
+ ),
+ new Fp(
+ 0x3516_cb98_b16c_82f9,
+ 0x926d_10c2_e126_1d5f,
+ 0x1709_e01a_0cc2_5fba,
+ 0x96c8_c960_b825_3f14,
+ 0x4927_c234_207e_51a9,
+ 0x18ae_b158_d542_c44e
+ )
+ ),
+ new Fp2(
+ new Fp(
+ 0xbf0d_cb98_b169_82fc,
+ 0xa679_10b7_1d1a_1d5c,
+ 0xb7c1_47c2_b8fb_06ff,
+ 0x1efa_710d_47d2_e7ce,
+ 0xed20_a79c_7e27_653c,
+ 0x02b8_5294_dac1_dfba
+ ),
+ new Fp(
+ 0x9d52_cb98_b180_82e5,
+ 0x621d_1111_5176_1d6f,
+ 0xe798_8260_3b48_af43,
+ 0x0ad3_1637_a4f4_da37,
+ 0xaeac_737c_5ac1_cf2e,
+ 0x006e_7e73_5b48_b824
+ )
+ ),
+ new Fp2(
+ new Fp(
+ 0xe148_cb98_b17d_2d93,
+ 0x94d5_1104_3ebe_1d6c,
+ 0xef80_bca9_de32_4cac,
+ 0xf77c_0969_2827_95b1,
+ 0x9dc1_009a_fbb6_8f97,
+ 0x0479_3199_9a47_ba2b
+ ),
+ new Fp(
+ 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(
+ new Fp(
+ 0x6934_cb98_b176_82ef,
+ 0xfa45_10ea_194e_1d67,
+ 0xff51_313d_2405_877e,
+ 0xd0cd_efcc_2e8d_0ca5,
+ 0x7bea_1ad8_3da0_106b,
+ 0x0c8e_97e6_1845_be39
+ ),
+ new Fp(
+ 0x4779_cb98_b18d_82d8,
+ 0xb5e9_1144_4daa_1d7a,
+ 0x2f28_6bda_a653_2fc2,
+ 0xbca6_94f6_8bae_ff0f,
+ 0x3d75_e6b8_1a3a_7a5d,
+ 0x0a44_c3c4_98cc_96a3
+ )
+ ),
+ new Fp2(
+ new Fp(
+ 0x8b6f_cb98_b18a_2d86,
+ 0xe8a1_1137_3af2_1d77,
+ 0x3710_a624_493c_cd2b,
+ 0xa94f_8828_0ee1_ba89,
+ 0x2c8a_73d6_bb2f_3ac7,
+ 0x0e4f_76ea_d7cb_98aa
+ ),
+ new Fp(
+ 0xcf65_cb98_b186_d834,
+ 0x1b59_112a_283a_1d74,
+ 0x3ef8_e06d_ec26_6a95,
+ 0x95f8_7b59_9214_7603,
+ 0x1b9f_00f5_5c23_fb31,
+ 0x125a_2a11_16ca_9ab1
+ )
+ ),
+ new Fp2(
+ new Fp(
+ 0x135b_cb98_b183_82e2,
+ 0x4e11_111d_1582_1d72,
+ 0x46e1_1ab7_8f10_07fe,
+ 0x82a1_6e8b_1547_317d,
+ 0x0ab3_8e13_fd18_bb9b,
+ 0x1664_dd37_55c9_9cb8
+ ),
+ new Fp(
+ 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.Equal(a.Square(), a * a);
+ Assert.Equal(b.Square(), b * b);
+ Assert.Equal(c.Square(), c * c);
+
+ Assert.Equal((a + b) * c.Square(), (c * c * a) + (c * c * b));
+
+ Assert.Equal(a.Invert()! * b.Invert()!, (a * b).Invert()!);
+ Assert.Equal(a.Invert()! * a, Fp6.One());
+ }
+
+ }
+}
diff --git a/test/BLS.Tests/G1AffineTests.cs b/test/BLS.Tests/G1AffineTests.cs
new file mode 100644
index 00000000..c6ffc60f
--- /dev/null
+++ b/test/BLS.Tests/G1AffineTests.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BLS.Tests
+{
+ internal class G1AffineTests
+ {
+ }
+}
diff --git a/test/BLS.Tests/GlobalUsings.cs b/test/BLS.Tests/GlobalUsings.cs
new file mode 100644
index 00000000..8c927eb7
--- /dev/null
+++ b/test/BLS.Tests/GlobalUsings.cs
@@ -0,0 +1 @@
+global using Xunit;
\ No newline at end of file
diff --git a/test/Candid.Tests/BlsTests.cs b/test/Candid.Tests/BlsTests.cs
deleted file mode 100644
index 96ed756f..00000000
--- a/test/Candid.Tests/BlsTests.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-//using EdjCase.ICP.BLS;
-//using EdjCase.ICP.Candid.Utilities;
-//using Xunit;
-
-//namespace EdjCase.ICP.Candid.Tests
-//{
-// public class BlsTests
-// {
-// [Theory]
-// [InlineData(
-// "814c0e6ec71fab583b08bd81373c255c3c371b2e84863c98a4f1e08b74235d14fb5d9c0cd546d9685f913a0c0b2cc5341583bf4b4392e467db96d65b9bb4cb717112f8472e0d5a4d14505ffd7484b01291091c5f87b98883463f98091a0baaae",
-// "0d69632d73746174652d726f6f74e6c01e909b4923345ce5970962bcfe3004bfd8474a21dae28f50692502f46d90",
-// "ace9fcdd9bc977e05d6328f889dc4e7c99114c737a494653cb27a1f55c06f4555e0f160980af5ead098acc195010b2f7"
-// )]
-// [InlineData(
-// "9933e1f89e8a3c4d7fdcccdbd518089e2bd4d8180a261f18d9c247a52768ebce98dc7328a39814a8f911086a1dd50cbe015e2a53b7bf78b55288893daa15c346640e8831d72a12bdedd979d28470c34823b8d1c3f4795d9c3984a247132e94fe",
-// "0d69632d73746174652d726f6f74b294b418b11ebe5dd7dd1dcb099e4e0372b9a42aef7a7a37fb4f25667d705ea9",
-// "89a2be21b5fa8ac9fab1527e041327ce899d7da971436a1f2165393947b4d942365bfe5488710e61a619ba48388a21b1"
-// )]
-// public void VerifySignature(string publicKeyHex, string hashHex, string signatureHex)
-// {
-// byte[] publicKey = ByteUtil.FromHexString(publicKeyHex);
-// byte[] hash = ByteUtil.FromHexString(hashHex);
-// byte[] signature = ByteUtil.FromHexString(signatureHex);
-// bool isValid = BlsUtil.VerifySignature(publicKey, hash, signature);
-// Assert.True(isValid);
-// }
-// }
-//}
diff --git a/test/Candid.Tests/Candid.Tests.csproj b/test/Candid.Tests/Candid.Tests.csproj
index 3c43b8de..a8fc9650 100644
--- a/test/Candid.Tests/Candid.Tests.csproj
+++ b/test/Candid.Tests/Candid.Tests.csproj
@@ -6,7 +6,7 @@
false
- ICP.Candid.Tests
+ EdjCase.ICP.Candid.Tests
true
diff --git a/test/Candid.Tests/CandidTextParsingTests.cs b/test/Candid.Tests/CandidTextParsingTests.cs
index abc2b917..c4e7101e 100644
--- a/test/Candid.Tests/CandidTextParsingTests.cs
+++ b/test/Candid.Tests/CandidTextParsingTests.cs
@@ -243,6 +243,7 @@ record {
executed_timestamp_seconds:nat64
}
) query";
+ text = text.Replace("\r", "");
CandidId proposalReferenceId = CandidId.Create("rec_1");
List expectedModes = new List { FuncMode.Query };
List expectedArgTypes = new()
diff --git a/test/Candid.Tests/Generators/ClientGeneratorTests.cs b/test/Candid.Tests/Generators/ClientGeneratorTests.cs
index cf947854..3d254e47 100644
--- a/test/Candid.Tests/Generators/ClientGeneratorTests.cs
+++ b/test/Candid.Tests/Generators/ClientGeneratorTests.cs
@@ -125,7 +125,7 @@ bool variantsUseProperties
private static string GetFileText(string fileName)
{
Assembly assembly = Assembly.GetExecutingAssembly();
- string resourceName = "ICP.Candid.Tests.Generators.Files." + fileName;
+ string resourceName = "EdjCase.ICP.Candid.Tests.Generators.Files." + fileName;
using (Stream? stream = assembly.GetManifestResourceStream(resourceName))
{
diff --git a/test/Performance.Tests/Benchmarks/HashingBenchmarks.cs b/test/Performance.Tests/Benchmarks/HashingBenchmarks.cs
index 3daa41e2..1f222693 100644
--- a/test/Performance.Tests/Benchmarks/HashingBenchmarks.cs
+++ b/test/Performance.Tests/Benchmarks/HashingBenchmarks.cs
@@ -19,4 +19,4 @@ public void Crc32()
{
CRC32.ComputeHash(this.data);
}
-}
\ No newline at end of file
+}
diff --git a/test/Performance.Tests/Benchmarks/SignatureBenchmarks.cs b/test/Performance.Tests/Benchmarks/SignatureBenchmarks.cs
index 84bd735b..e90220d9 100644
--- a/test/Performance.Tests/Benchmarks/SignatureBenchmarks.cs
+++ b/test/Performance.Tests/Benchmarks/SignatureBenchmarks.cs
@@ -1,5 +1,8 @@
using BenchmarkDotNet.Attributes;
+using EdjCase.ICP.BLS;
+using EdjCase.ICP.BLS.Models;
using EdjCase.ICP.Candid.Crypto;
+using EdjCase.ICP.Candid.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -10,21 +13,27 @@ namespace Performance.Tests.Benchmarks
{
public class SignatureBenchmarks
{
- private const int N = 10000;
- private readonly byte[] data;
- private readonly SHA256HashFunction sha256;
+ private readonly DefaultBlsCryptograhy bls;
+ private readonly byte[] publicKey;
+ private readonly byte[] msgHash;
+ private readonly byte[] signature;
public SignatureBenchmarks()
{
- this.data = new byte[N];
- new Random(42).NextBytes(this.data);
- this.sha256 = SHA256HashFunction.Create();
+ this.bls = new DefaultBlsCryptograhy();
+ //this.publicKey = ByteUtil.FromHexString("a7623a93cdb56c4d23d99c14216afaab3dfd6d4f9eb3db23d038280b6d5cb2caaee2a19dd92c9df7001dede23bf036bc0f33982dfb41e8fa9b8e96b5dc3e83d55ca4dd146c7eb2e8b6859cb5a5db815db86810b8d12cee1588b5dbf34a4dc9a5");
+ //this.msgHash = ByteUtil.FromHexString("hello");
+ //this.signature = ByteUtil.FromHexString("b89e13a212c830586eaa9ad53946cd968718ebecc27eda849d9232673dcd4f440e8b5df39bf14a88048c15e16cbcaabe");
+ this.publicKey = ByteUtil.FromHexString("b2be11dc8e54ee74dbc07569fd74fe03b5f52ad71cd49a8579b6c6387891f5a20ad980ec2747618c1b9ad35846a68a3e");
+ this.msgHash = ByteUtil.FromHexString("");
+ this.signature = ByteUtil.FromHexString("b53cfdf8b488a286df1ed20432e2bbc4e6361003757dfda3a4fd6cd98de95e5513f7c448d70b2681e14547a6ced47e7c10e28432e8abcb34de1dc28f39328fd2a13db12a4c6a30bd17b0e42881a429003e4c24583ba0f29a40fd836cf05e1a40");
}
[Benchmark]
- public void SHA256()
+ public void Verify()
{
- this.sha256.ComputeHash(this.data);
+ this.bls.VerifySignature(this.publicKey, this.msgHash, this.signature);
}
+
}
}
diff --git a/test/Performance.Tests/Performance.Tests.csproj b/test/Performance.Tests/Performance.Tests.csproj
index abc68155..e3f2df5a 100644
--- a/test/Performance.Tests/Performance.Tests.csproj
+++ b/test/Performance.Tests/Performance.Tests.csproj
@@ -14,6 +14,7 @@
+
diff --git a/test/Performance.Tests/Program.cs b/test/Performance.Tests/Program.cs
index 34d3f5f4..87e9cf0e 100644
--- a/test/Performance.Tests/Program.cs
+++ b/test/Performance.Tests/Program.cs
@@ -1,9 +1,10 @@
using BenchmarkDotNet.Running;
using EdjCase.ICP.Agent.Agents.Http;
using EdjCase.ICP.Candid.Utilities;
+using Performance.Tests.Benchmarks;
-//BenchmarkRunner.Run();
-BenchmarkRunner.Run(typeof(Program).Assembly);
+BenchmarkRunner.Run();
+//BenchmarkRunner.Run(typeof(Program).Assembly);
@@ -31,4 +32,4 @@ public Task PostAsync(string url, byte[] cborBody, CancellationTok
{
return Task.FromResult(this.response);
}
-}
\ No newline at end of file
+}