diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000000..07843745db
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,14 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
+{
+ "name": "C# (.NET)",
+ "image": "mcr.microsoft.com/devcontainers/dotnet:1-7.0-jammy",
+ "postCreateCommand": "dotnet restore && dotnet build",
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "ms-dotnettools.csdevkit"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000..0c37abbc2d
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,64 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text eol=lf
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+*.sln text eol=crlf
+#*.csproj text eol=crlf
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+*.jpg binary
+*.png binary
+*.gif binary
+*.ico binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000000..7b043f14a7
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,35 @@
+# Description
+
+
+
+Fixes # (issue)
+
+## Type of change
+
+
+
+- [ ] Bug fix (non-breaking change which fixes an issue)
+- [ ] New feature (non-breaking change which adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
+- [ ] This change requires a documentation update
+
+# How Has This Been Tested?
+
+
+
+- [ ] Test A
+- [ ] Test B
+
+**Test Configuration**:
+
+
+# Checklist:
+
+- [ ] My code follows the style guidelines of this project
+- [ ] I have performed a self-review of my code
+- [ ] I have commented my code, particularly in hard-to-understand areas
+- [ ] I have made corresponding changes to the documentation
+- [ ] My changes generate no new warnings
+- [ ] I have added tests that prove my fix is effective or that my feature works
+- [ ] New and existing unit tests pass locally with my changes
+- [ ] Any dependent changes have been merged and published in downstream modules
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 44dac993db..bce1dc7c79 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -36,44 +36,46 @@ jobs:
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
+ path-to-lcov: ./coverage/lcov.net7.0.info
- PublishMyGet:
- if: github.ref == 'refs/heads/master' && startsWith(github.repository, 'neo-project/')
- needs: Test
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
- - name: Setup .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: ${{ env.DOTNET_VERSION }}
- - name: Set Version
- run: git rev-list --count HEAD | xargs printf 'CI%05d' | xargs -I{} echo 'VERSION_SUFFIX={}' >> $GITHUB_ENV
- - name : Pack (Neo)
- run: |
- dotnet pack \
- --configuration Debug \
- --output ./out \
- --version-suffix ${{ env.VERSION_SUFFIX }}
- - name: Remove Unwanted Files
- working-directory: ./out
- run: |
- rm -v Neo.CLI*
- rm -v Neo.GUI*
- - name: Publish to MyGet
- working-directory: ./out
- run: |
- for filename in *.nupkg; do
- dotnet nuget push "${filename}" \
- --source https://www.myget.org/F/neo/api/v3/index.json \
- --api-key "${{ secrets.MYGET_TOKEN }}" \
- --disable-buffering \
- --no-service-endpoint;
- done;
- shell: bash
+ # MyGet isn't working
+ # PublishMyGet:
+ # if: github.ref == 'refs/heads/master' && startsWith(github.repository, 'neo-project/')
+ # needs: Test
+ # runs-on: ubuntu-latest
+ # steps:
+ # - name: Checkout
+ # uses: actions/checkout@v4
+ # with:
+ # fetch-depth: 0
+ # - name: Setup .NET
+ # uses: actions/setup-dotnet@v4
+ # with:
+ # dotnet-version: ${{ env.DOTNET_VERSION }}
+ # - name: Set Version
+ # run: git rev-list --count HEAD | xargs printf 'CI%05d' | xargs -I{} echo 'VERSION_SUFFIX={}' >> $GITHUB_ENV
+ # - name : Pack (Neo)
+ # run: |
+ # dotnet pack \
+ # --configuration Debug \
+ # --output ./out \
+ # --version-suffix ${{ env.VERSION_SUFFIX }}
+ # - name: Remove Unwanted Files
+ # working-directory: ./out
+ # run: |
+ # rm -v Neo.CLI*
+ # rm -v Neo.GUI*
+ # - name: Publish to MyGet
+ # working-directory: ./out
+ # run: |
+ # for filename in *.nupkg; do
+ # dotnet nuget push "${filename}" \
+ # --source https://www.myget.org/F/neo/api/v3/index.json \
+ # --api-key "${{ secrets.MYGET_TOKEN }}" \
+ # --disable-buffering \
+ # --no-service-endpoint;
+ # done;
+ # shell: bash
Release:
if: github.ref == 'refs/heads/master' && startsWith(github.repository, 'neo-project/')
@@ -129,6 +131,12 @@ jobs:
dotnet pack ./src/Neo.ConsoleService \
--configuration Release \
--output ./out
+ - name : Pack (Neo.Cryptography.BLS12_381)
+ if: steps.check_tag.outputs.statusCode == '404'
+ run: |
+ dotnet pack ./src/Neo.Cryptography.BLS12_381 \
+ --configuration Release \
+ --output ./out
- name: Publish to NuGet
if: steps.check_tag.outputs.statusCode == '404'
run: |
diff --git a/NuGet.Config b/NuGet.Config
index c06788942f..5922e754af 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -2,7 +2,6 @@
-
-
\ No newline at end of file
+
diff --git a/README.md b/README.md
index 8f64dcc854..37d1f00827 100644
--- a/README.md
+++ b/README.md
@@ -88,7 +88,11 @@
-
+
+
+
+
+
## Table of Contents
diff --git a/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj
index 12b6e861e4..12d32a3361 100644
--- a/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj
+++ b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj
@@ -1,15 +1,15 @@
-
-
-
- Exe
- net7.0
- Neo
- enable
- false
-
-
-
-
-
-
-
+
+
+
+ Exe
+ net7.0
+ Neo
+ enable
+ false
+
+
+
+
+
+
+
diff --git a/global.json b/global.json
index 943dd9e463..423c2e2269 100644
--- a/global.json
+++ b/global.json
@@ -1,7 +1,7 @@
-{
- "sdk": {
- "version": "7.0.404",
- "rollForward": "latestFeature",
- "allowPrerelease": false
- }
+{
+ "sdk": {
+ "version": "7.0.404",
+ "rollForward": "latestFeature",
+ "allowPrerelease": false
+ }
}
diff --git a/neo.sln b/neo.sln
index 2168c241b3..dc50d0f4d7 100644
--- a/neo.sln
+++ b/neo.sln
@@ -32,6 +32,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.CLI", "src\Neo.CLI\Neo.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.ConsoleService.Tests", "tests\Neo.ConsoleService.Tests\Neo.ConsoleService.Tests.csproj", "{B40F8584-5AFB-452C-AEFA-009C80CC23A9}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Cryptography.BLS12_381", "src\Neo.Cryptography.BLS12_381\Neo.Cryptography.BLS12_381.csproj", "{D48C1FAB-3471-4CA0-8688-25E6F43F2C25}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Cryptography.BLS12_381.Tests", "tests\Neo.Cryptography.BLS12_381.Tests\Neo.Cryptography.BLS12_381.Tests.csproj", "{387CCF6C-9A26-43F6-A639-0A82E91E10D8}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -86,6 +90,14 @@ Global
{B40F8584-5AFB-452C-AEFA-009C80CC23A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B40F8584-5AFB-452C-AEFA-009C80CC23A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B40F8584-5AFB-452C-AEFA-009C80CC23A9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D48C1FAB-3471-4CA0-8688-25E6F43F2C25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D48C1FAB-3471-4CA0-8688-25E6F43F2C25}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D48C1FAB-3471-4CA0-8688-25E6F43F2C25}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D48C1FAB-3471-4CA0-8688-25E6F43F2C25}.Release|Any CPU.Build.0 = Release|Any CPU
+ {387CCF6C-9A26-43F6-A639-0A82E91E10D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {387CCF6C-9A26-43F6-A639-0A82E91E10D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {387CCF6C-9A26-43F6-A639-0A82E91E10D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {387CCF6C-9A26-43F6-A639-0A82E91E10D8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -103,6 +115,8 @@ Global
{02ABDE42-9880-43B4-B6F7-8D618602A277} = {B5339DF7-5D1D-43BA-B332-74B825E1770E}
{BDFBE455-4C1F-4FC4-B5FC-1387B93A8687} = {B5339DF7-5D1D-43BA-B332-74B825E1770E}
{B40F8584-5AFB-452C-AEFA-009C80CC23A9} = {EDE05FA8-8E73-4924-BC63-DD117127EEE1}
+ {D48C1FAB-3471-4CA0-8688-25E6F43F2C25} = {B5339DF7-5D1D-43BA-B332-74B825E1770E}
+ {387CCF6C-9A26-43F6-A639-0A82E91E10D8} = {EDE05FA8-8E73-4924-BC63-DD117127EEE1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BCBA19D9-F868-4C6D-8061-A2B91E06E3EC}
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index ed3a744939..35a8fa289c 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -4,8 +4,8 @@
2015-2023 The Neo Project
3.6.2
+ 11.0
The Neo Project
- net7.0
neo.png
https://github.com/neo-project/neo
MIT
@@ -22,4 +22,8 @@
+
+
+
+
diff --git a/src/Neo.VM/IsExternalInit.cs b/src/IsExternalInit.cs
similarity index 100%
rename from src/Neo.VM/IsExternalInit.cs
rename to src/IsExternalInit.cs
diff --git a/src/Neo.CLI/CLI/MainService.Contracts.cs b/src/Neo.CLI/CLI/MainService.Contracts.cs
index e2aecbb598..b2a90dda11 100644
--- a/src/Neo.CLI/CLI/MainService.Contracts.cs
+++ b/src/Neo.CLI/CLI/MainService.Contracts.cs
@@ -49,7 +49,7 @@ private void OnDeployCommand(string filePath, string? manifestPath = null, JObje
ConsoleHelper.Info("Gas consumed: ", $"{new BigDecimal((BigInteger)tx.SystemFee, NativeContract.GAS.Decimals)}");
ConsoleHelper.Info("Network fee: ", $"{new BigDecimal((BigInteger)tx.NetworkFee, NativeContract.GAS.Decimals)}");
ConsoleHelper.Info("Total fee: ", $"{new BigDecimal((BigInteger)(tx.SystemFee + tx.NetworkFee), NativeContract.GAS.Decimals)} GAS");
- if (!ReadUserInput("Relay tx? (no|yes)").IsYes()) // Add this in case just want to get hash but not relay
+ if (!ConsoleHelper.ReadUserInput("Relay tx? (no|yes)").IsYes()) // Add this in case just want to get hash but not relay
{
return;
}
@@ -111,7 +111,7 @@ private void OnUpdateCommand(UInt160 scriptHash, string filePath, string manifes
ConsoleHelper.Info("Gas consumed: ", $"{new BigDecimal((BigInteger)tx.SystemFee, NativeContract.GAS.Decimals)}");
ConsoleHelper.Info("Network fee: ", $"{new BigDecimal((BigInteger)tx.NetworkFee, NativeContract.GAS.Decimals)}");
ConsoleHelper.Info("Total fee: ", $"{new BigDecimal((BigInteger)(tx.SystemFee + tx.NetworkFee), NativeContract.GAS.Decimals)} GAS");
- if (!ReadUserInput("Relay tx? (no|yes)").IsYes()) // Add this in case just want to get hash but not relay
+ if (!ConsoleHelper.ReadUserInput("Relay tx? (no|yes)").IsYes()) // Add this in case just want to get hash but not relay
{
return;
}
@@ -173,7 +173,7 @@ private void OnInvokeCommand(UInt160 scriptHash, string operation, JArray? contr
$"{new BigDecimal((BigInteger)tx.NetworkFee, NativeContract.GAS.Decimals)}\t",
"Total fee: ",
$"{new BigDecimal((BigInteger)(tx.SystemFee + tx.NetworkFee), NativeContract.GAS.Decimals)} GAS");
- if (!ReadUserInput("Relay tx? (no|yes)").IsYes())
+ if (!ConsoleHelper.ReadUserInput("Relay tx? (no|yes)").IsYes())
{
return;
}
diff --git a/src/Neo.CLI/CLI/MainService.NEP17.cs b/src/Neo.CLI/CLI/MainService.NEP17.cs
index cd0356e81b..131d171dc6 100644
--- a/src/Neo.CLI/CLI/MainService.NEP17.cs
+++ b/src/Neo.CLI/CLI/MainService.NEP17.cs
@@ -67,7 +67,7 @@ private void OnTransferCommand(UInt160 tokenHash, UInt160 to, decimal amount, UI
ConsoleHelper.Error(GetExceptionMessage(e));
return;
}
- if (!ReadUserInput("Relay tx(no|yes)").IsYes())
+ if (!ConsoleHelper.ReadUserInput("Relay tx(no|yes)").IsYes())
{
return;
}
diff --git a/src/Neo.CLI/CLI/MainService.Wallet.cs b/src/Neo.CLI/CLI/MainService.Wallet.cs
index 041aab3a0c..4f65390f85 100644
--- a/src/Neo.CLI/CLI/MainService.Wallet.cs
+++ b/src/Neo.CLI/CLI/MainService.Wallet.cs
@@ -44,7 +44,7 @@ private void OnOpenWallet(string path)
ConsoleHelper.Error("File does not exist");
return;
}
- string password = ReadUserInput("password", true);
+ string password = ConsoleHelper.ReadUserInput("password", true);
if (password.Length == 0)
{
ConsoleHelper.Info("Cancelled");
@@ -87,7 +87,7 @@ private void OnUpgradeWalletCommand(string path)
ConsoleHelper.Error("File does not exist.");
return;
}
- string password = ReadUserInput("password", true);
+ string password = ConsoleHelper.ReadUserInput("password", true);
if (password.Length == 0)
{
ConsoleHelper.Info("Cancelled");
@@ -114,7 +114,7 @@ private void OnCreateAddressCommand(ushort count = 1)
string path = "address.txt";
if (File.Exists(path))
{
- if (!ReadUserInput($"The file '{path}' already exists, do you want to overwrite it? (yes|no)", false).IsYes())
+ if (!ConsoleHelper.ReadUserInput($"The file '{path}' already exists, do you want to overwrite it? (yes|no)", false).IsYes())
{
return;
}
@@ -150,7 +150,7 @@ private void OnDeleteAddressCommand(UInt160 address)
{
if (NoWallet()) return;
- if (ReadUserInput($"Warning: Irrevocable operation!\nAre you sure to delete account {address.ToAddress(NeoSystem.Settings.AddressVersion)}? (no|yes)").IsYes())
+ if (ConsoleHelper.ReadUserInput($"Warning: Irrevocable operation!\nAre you sure to delete account {address.ToAddress(NeoSystem.Settings.AddressVersion)}? (no|yes)").IsYes())
{
if (CurrentWallet!.DeleteAccount(address))
{
@@ -181,7 +181,7 @@ private void OnExportKeyCommand(string? path = null, UInt160? scriptHash = null)
ConsoleHelper.Error($"File '{path}' already exists");
return;
}
- string password = ReadUserInput("password", true);
+ string password = ConsoleHelper.ReadUserInput("password", true);
if (password.Length == 0)
{
ConsoleHelper.Info("Cancelled");
@@ -213,13 +213,13 @@ private void OnExportKeyCommand(string? path = null, UInt160? scriptHash = null)
[ConsoleCommand("create wallet", Category = "Wallet Commands")]
private void OnCreateWalletCommand(string path, string? wifOrFile = null)
{
- string password = ReadUserInput("password", true);
+ string password = ConsoleHelper.ReadUserInput("password", true);
if (password.Length == 0)
{
ConsoleHelper.Info("Cancelled");
return;
}
- string password2 = ReadUserInput("repeat password", true);
+ string password2 = ConsoleHelper.ReadUserInput("repeat password", true);
if (password != password2)
{
ConsoleHelper.Error("Two passwords not match.");
@@ -287,7 +287,7 @@ private void OnImportKeyCommand(string wifOrFile)
if (wifOrFile.Length > 1024 * 1024)
{
- if (!ReadUserInput($"The file '{fileInfo.FullName}' is too big, do you want to continue? (yes|no)", false).IsYes())
+ if (!ConsoleHelper.ReadUserInput($"The file '{fileInfo.FullName}' is too big, do you want to continue? (yes|no)", false).IsYes())
{
return;
}
@@ -344,7 +344,7 @@ private void OnImportWatchOnlyCommand(string addressOrFile)
if (fileInfo.Length > 1024 * 1024)
{
- if (!ReadUserInput($"The file '{fileInfo.FullName}' is too big, do you want to continue? (yes|no)", false).IsYes())
+ if (!ConsoleHelper.ReadUserInput($"The file '{fileInfo.FullName}' is too big, do you want to continue? (yes|no)", false).IsYes())
{
return;
}
@@ -500,7 +500,7 @@ private void OnSignCommand(JObject jsonObjectToSign)
private void OnSendCommand(UInt160 asset, UInt160 to, string amount, UInt160? from = null, string? data = null, UInt160[]? signerAccounts = null)
{
if (NoWallet()) return;
- string password = ReadUserInput("password", true);
+ string password = ConsoleHelper.ReadUserInput("password", true);
if (password.Length == 0)
{
ConsoleHelper.Info("Cancelled");
@@ -511,6 +511,7 @@ private void OnSendCommand(UInt160 asset, UInt160 to, string amount, UInt160? fr
ConsoleHelper.Error("Incorrect password");
return;
}
+
var snapshot = NeoSystem.StoreView;
Transaction tx;
AssetDescriptor descriptor = new(snapshot, NeoSystem.Settings, asset);
@@ -550,11 +551,11 @@ private void OnSendCommand(UInt160 asset, UInt160 to, string amount, UInt160? fr
return;
}
- ConsoleHelper.Info("Network fee: ",
- $"{new BigDecimal((BigInteger)tx.NetworkFee, NativeContract.GAS.Decimals)}\t",
- "Total fee: ",
- $"{new BigDecimal((BigInteger)(tx.SystemFee + tx.NetworkFee), NativeContract.GAS.Decimals)} GAS");
- if (!ReadUserInput("Relay tx? (no|yes)").IsYes())
+ ConsoleHelper.Info(
+ "Send To: ", $"{to.ToAddress(NeoSystem.Settings.AddressVersion)}\n",
+ "Network fee: ", $"{new BigDecimal((BigInteger)tx.NetworkFee, NativeContract.GAS.Decimals)}\t",
+ "Total fee: ", $"{new BigDecimal((BigInteger)(tx.SystemFee + tx.NetworkFee), NativeContract.GAS.Decimals)} GAS");
+ if (!ConsoleHelper.ReadUserInput("Relay tx? (no|yes)").IsYes())
{
return;
}
@@ -625,7 +626,7 @@ private void OnCancelCommand(UInt256 txid, UInt160? sender = null, UInt160[]? si
{
var snapshot = NeoSystem.StoreView;
AssetDescriptor descriptor = new(snapshot, NeoSystem.Settings, NativeContract.GAS.Hash);
- string extracFee = ReadUserInput("This tx is not in mempool, please input extra fee manually");
+ string extracFee = ConsoleHelper.ReadUserInput("This tx is not in mempool, please input extra fee manually");
if (!BigDecimal.TryParse(extracFee, descriptor.Decimals, out BigDecimal decimalExtraFee) || decimalExtraFee.Sign <= 0)
{
ConsoleHelper.Error("Incorrect Amount Format");
@@ -638,7 +639,7 @@ private void OnCancelCommand(UInt256 txid, UInt160? sender = null, UInt160[]? si
$"{new BigDecimal((BigInteger)tx.NetworkFee, NativeContract.GAS.Decimals)}\t",
"Total fee: ",
$"{new BigDecimal((BigInteger)(tx.SystemFee + tx.NetworkFee), NativeContract.GAS.Decimals)} GAS");
- if (!ReadUserInput("Relay tx? (no|yes)").IsYes())
+ if (!ConsoleHelper.ReadUserInput("Relay tx? (no|yes)").IsYes())
{
return;
}
@@ -667,7 +668,7 @@ private void OnShowGasCommand()
private void OnChangePasswordCommand()
{
if (NoWallet()) return;
- string oldPassword = ReadUserInput("password", true);
+ string oldPassword = ConsoleHelper.ReadUserInput("password", true);
if (oldPassword.Length == 0)
{
ConsoleHelper.Info("Cancelled");
@@ -678,8 +679,8 @@ private void OnChangePasswordCommand()
ConsoleHelper.Error("Incorrect password");
return;
}
- string newPassword = ReadUserInput("New password", true);
- string newPasswordReEntered = ReadUserInput("Re-Enter Password", true);
+ string newPassword = ConsoleHelper.ReadUserInput("New password", true);
+ string newPasswordReEntered = ConsoleHelper.ReadUserInput("Re-Enter Password", true);
if (!newPassword.Equals(newPasswordReEntered))
{
ConsoleHelper.Error("Two passwords entered are inconsistent!");
diff --git a/src/Neo.CLI/CLI/MainService.cs b/src/Neo.CLI/CLI/MainService.cs
index dbccd2b28e..e924d6478b 100644
--- a/src/Neo.CLI/CLI/MainService.cs
+++ b/src/Neo.CLI/CLI/MainService.cs
@@ -96,7 +96,7 @@ public MainService() : base()
Initialize_Logger();
}
- internal static UInt160 StringToAddress(string input, byte version)
+ internal UInt160 StringToAddress(string input, byte version)
{
switch (input.ToLowerInvariant())
{
@@ -104,6 +104,11 @@ internal static UInt160 StringToAddress(string input, byte version)
case "gas": return NativeContract.GAS.Hash;
}
+ if (input.IndexOf('.') > 0 && input.LastIndexOf('.') < input.Length)
+ {
+ return ResolveNeoNameServiceAddress(input);
+ }
+
// Try to parse as UInt160
if (UInt160.TryParse(input, out var addr))
@@ -537,7 +542,7 @@ private void SendTransaction(byte[] script, UInt160? account = null, long gas =
if (engine.State == VMState.FAULT) return;
}
- if (!ReadUserInput("Relay tx(no|yes)").IsYes())
+ if (!ConsoleHelper.ReadUserInput("Relay tx(no|yes)").IsYes())
{
return;
}
@@ -636,5 +641,45 @@ static string GetExceptionMessage(Exception exception)
return exception.Message;
}
+
+ public UInt160 ResolveNeoNameServiceAddress(string domain)
+ {
+ if (Settings.Default.Contracts.NeoNameService == UInt160.Zero)
+ throw new Exception("Neo Name Service (NNS): is disabled on this network.");
+
+ using var sb = new ScriptBuilder();
+ sb.EmitDynamicCall(Settings.Default.Contracts.NeoNameService, "resolve", CallFlags.ReadOnly, domain, 16);
+
+ using var appEng = ApplicationEngine.Run(sb.ToArray(), NeoSystem.StoreView, settings: NeoSystem.Settings);
+ if (appEng.State == VMState.HALT)
+ {
+ var data = appEng.ResultStack.Pop();
+ if (data is ByteString)
+ {
+ try
+ {
+ var addressData = data.GetString();
+ if (UInt160.TryParse(addressData, out var address))
+ return address;
+ else
+ return addressData.ToScriptHash(NeoSystem.Settings.AddressVersion);
+ }
+ catch { }
+ }
+ else if (data is Null)
+ {
+ throw new Exception($"Neo Name Service (NNS): \"{domain}\" domain not found.");
+ }
+ throw new Exception("Neo Name Service (NNS): Record invalid address format.");
+ }
+ else
+ {
+ if (appEng.FaultException is not null)
+ {
+ throw new Exception($"Neo Name Service (NNS): \"{appEng.FaultException.Message}\".");
+ }
+ }
+ throw new Exception($"Neo Name Service (NNS): \"{domain}\" domain not found.");
+ }
}
}
diff --git a/src/Neo.CLI/Neo.CLI.csproj b/src/Neo.CLI/Neo.CLI.csproj
index cd1b75fbc0..8c638d6c2a 100644
--- a/src/Neo.CLI/Neo.CLI.csproj
+++ b/src/Neo.CLI/Neo.CLI.csproj
@@ -1,6 +1,7 @@
+ net7.0
Neo.CLI
neo-cli
Exe
@@ -11,6 +12,10 @@
enable
+
+
+
+
diff --git a/src/Neo.CLI/Settings.cs b/src/Neo.CLI/Settings.cs
index 3c3a8a76d6..54067da747 100644
--- a/src/Neo.CLI/Settings.cs
+++ b/src/Neo.CLI/Settings.cs
@@ -11,6 +11,7 @@
using Microsoft.Extensions.Configuration;
using Neo.Network.P2P;
+using System;
using System.Threading;
namespace Neo
@@ -21,13 +22,14 @@ public class Settings
public StorageSettings Storage { get; }
public P2PSettings P2P { get; }
public UnlockWalletSettings UnlockWallet { get; }
+ public ContractsSettings Contracts { get; }
- static Settings? _default;
+ static Settings? s_default;
static bool UpdateDefault(IConfiguration configuration)
{
var settings = new Settings(configuration.GetSection("ApplicationConfiguration"));
- return null == Interlocked.CompareExchange(ref _default, settings, null);
+ return null == Interlocked.CompareExchange(ref s_default, settings, null);
}
public static bool Initialize(IConfiguration configuration)
@@ -39,22 +41,23 @@ public static Settings Default
{
get
{
- if (_default == null)
+ if (s_default == null)
{
- IConfigurationRoot config = new ConfigurationBuilder().AddJsonFile("config.json", optional: true).Build();
+ var config = new ConfigurationBuilder().AddJsonFile("config.json", optional: true).Build();
Initialize(config);
}
- return _default!;
+ return s_default!;
}
}
public Settings(IConfigurationSection section)
{
- this.Logger = new LoggerSettings(section.GetSection("Logger"));
- this.Storage = new StorageSettings(section.GetSection("Storage"));
- this.P2P = new P2PSettings(section.GetSection("P2P"));
- this.UnlockWallet = new UnlockWalletSettings(section.GetSection("UnlockWallet"));
+ Contracts = new(section.GetSection(nameof(Contracts)));
+ Logger = new(section.GetSection(nameof(Logger)));
+ Storage = new(section.GetSection(nameof(Storage)));
+ P2P = new(section.GetSection(nameof(P2P)));
+ UnlockWallet = new(section.GetSection(nameof(UnlockWallet)));
}
}
@@ -66,21 +69,21 @@ public class LoggerSettings
public LoggerSettings(IConfigurationSection section)
{
- this.Path = section.GetValue("Path", "Logs")!;
- this.ConsoleOutput = section.GetValue("ConsoleOutput", false);
- this.Active = section.GetValue("Active", false);
+ Path = section.GetValue(nameof(Path), "Logs")!;
+ ConsoleOutput = section.GetValue(nameof(ConsoleOutput), false);
+ Active = section.GetValue(nameof(Active), false);
}
}
public class StorageSettings
{
- public string Engine { get; }
- public string Path { get; }
+ public string Engine { get; } = string.Empty;
+ public string Path { get; } = string.Empty;
public StorageSettings(IConfigurationSection section)
{
- this.Engine = section.GetValue("Engine", "LevelDBStore")!;
- this.Path = section.GetValue("Path", "Data_LevelDB_{0}")!;
+ Engine = section.GetValue(nameof(Engine), "LevelDBStore")!;
+ Path = section.GetValue(nameof(Path), "Data_LevelDB_{0}")!;
}
}
@@ -93,26 +96,44 @@ public class P2PSettings
public P2PSettings(IConfigurationSection section)
{
- this.Port = ushort.Parse(section.GetValue("Port", "10333"));
- this.MinDesiredConnections = section.GetValue("MinDesiredConnections", Peer.DefaultMinDesiredConnections);
- this.MaxConnections = section.GetValue("MaxConnections", Peer.DefaultMaxConnections);
- this.MaxConnectionsPerAddress = section.GetValue("MaxConnectionsPerAddress", 3);
+ Port = section.GetValue(nameof(Port), 10333);
+ MinDesiredConnections = section.GetValue(nameof(MinDesiredConnections), Peer.DefaultMinDesiredConnections);
+ MaxConnections = section.GetValue(nameof(MaxConnections), Peer.DefaultMaxConnections);
+ MaxConnectionsPerAddress = section.GetValue(nameof(MaxConnectionsPerAddress), 3);
}
}
public class UnlockWalletSettings
{
- public string? Path { get; }
- public string? Password { get; }
- public bool IsActive { get; }
+ public string Path { get; } = string.Empty;
+ public string Password { get; } = string.Empty;
+ public bool IsActive { get; } = false;
public UnlockWalletSettings(IConfigurationSection section)
{
if (section.Exists())
{
- this.Path = section.GetValue("Path", "");
- this.Password = section.GetValue("Password", "");
- this.IsActive = bool.Parse(section.GetValue("IsActive", "false")!);
+ Path = section.GetValue(nameof(Path), string.Empty)!;
+ Password = section.GetValue(nameof(Password), string.Empty)!;
+ IsActive = section.GetValue(nameof(IsActive), false);
+ }
+ }
+ }
+
+ public class ContractsSettings
+ {
+ public UInt160 NeoNameService { get; } = UInt160.Zero;
+
+ public ContractsSettings(IConfigurationSection section)
+ {
+ if (section.Exists())
+ {
+ if (UInt160.TryParse(section.GetValue(nameof(NeoNameService), string.Empty), out var hash))
+ {
+ NeoNameService = hash;
+ }
+ else
+ throw new ArgumentException("Neo Name Service (NNS): NeoNameService hash is invalid. Check your config.json.", nameof(NeoNameService));
}
}
}
diff --git a/src/Neo.CLI/config.fs.mainnet.json b/src/Neo.CLI/config.fs.mainnet.json
index 57bf751406..248a2a8956 100644
--- a/src/Neo.CLI/config.fs.mainnet.json
+++ b/src/Neo.CLI/config.fs.mainnet.json
@@ -19,6 +19,9 @@
"Path": "",
"Password": "",
"IsActive": false
+ },
+ "Contracts": {
+ "NeoNameService": "0x7061fbd31562664b58f422c3dee4acfd70dba8af"
}
},
"ProtocolConfiguration": {
diff --git a/src/Neo.CLI/config.fs.testnet.json b/src/Neo.CLI/config.fs.testnet.json
index a7a930c28b..88a03f7413 100644
--- a/src/Neo.CLI/config.fs.testnet.json
+++ b/src/Neo.CLI/config.fs.testnet.json
@@ -19,6 +19,9 @@
"Path": "",
"Password": "",
"IsActive": false
+ },
+ "Contracts": {
+ "NeoNameService": "0xfb08ccf30ab534a871b7b092a49fe70c154ed678"
}
},
"ProtocolConfiguration": {
diff --git a/src/Neo.CLI/config.json b/src/Neo.CLI/config.json
index 897e33afab..5ff5dbb2bb 100644
--- a/src/Neo.CLI/config.json
+++ b/src/Neo.CLI/config.json
@@ -19,6 +19,9 @@
"Path": "",
"Password": "",
"IsActive": false
+ },
+ "Contracts": {
+ "NeoNameService": "0x50ac1c37690cc2cfc594472833cf57505d5f46de"
}
},
"ProtocolConfiguration": {
diff --git a/src/Neo.CLI/config.mainnet.json b/src/Neo.CLI/config.mainnet.json
index 897e33afab..5ff5dbb2bb 100644
--- a/src/Neo.CLI/config.mainnet.json
+++ b/src/Neo.CLI/config.mainnet.json
@@ -19,6 +19,9 @@
"Path": "",
"Password": "",
"IsActive": false
+ },
+ "Contracts": {
+ "NeoNameService": "0x50ac1c37690cc2cfc594472833cf57505d5f46de"
}
},
"ProtocolConfiguration": {
diff --git a/src/Neo.CLI/config.testnet.json b/src/Neo.CLI/config.testnet.json
index e3bcb7ce95..c6d771ef3a 100644
--- a/src/Neo.CLI/config.testnet.json
+++ b/src/Neo.CLI/config.testnet.json
@@ -19,6 +19,9 @@
"Path": "",
"Password": "",
"IsActive": false
+ },
+ "Contracts": {
+ "NeoNameService": "0x50ac1c37690cc2cfc594472833cf57505d5f46de"
}
},
"ProtocolConfiguration": {
diff --git a/src/Neo.ConsoleService/ConsoleHelper.cs b/src/Neo.ConsoleService/ConsoleHelper.cs
index 19bfcd155a..78f170a3af 100644
--- a/src/Neo.ConsoleService/ConsoleHelper.cs
+++ b/src/Neo.ConsoleService/ConsoleHelper.cs
@@ -10,6 +10,8 @@
// modifications are permitted.
using System;
+using System.Security;
+using System.Text;
namespace Neo.ConsoleService
{
@@ -19,6 +21,8 @@ public static class ConsoleHelper
private static readonly ConsoleColorSet WarningColor = new(ConsoleColor.Yellow);
private static readonly ConsoleColorSet ErrorColor = new(ConsoleColor.Red);
+ public static bool ReadingPassword { get; private set; } = false;
+
///
/// Info handles message in the format of "[tag]:[message]",
/// avoid using Info if the `tag` is too long
@@ -72,5 +76,88 @@ private static void Log(string tag, ConsoleColorSet colorSet, string msg)
currentColor.Apply();
Console.WriteLine(msg);
}
+
+ public static string ReadUserInput(string prompt, bool password = false)
+ {
+ const string t = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
+ var sb = new StringBuilder();
+
+ if (!string.IsNullOrEmpty(prompt))
+ {
+ Console.Write(prompt + ": ");
+ }
+
+ if (password) ReadingPassword = true;
+ var prevForeground = Console.ForegroundColor;
+ Console.ForegroundColor = ConsoleColor.Yellow;
+
+ if (Console.IsInputRedirected)
+ {
+ // neo-gui Console require it
+ sb.Append(Console.ReadLine());
+ }
+ else
+ {
+ ConsoleKeyInfo key;
+ do
+ {
+ key = Console.ReadKey(true);
+
+ if (t.IndexOf(key.KeyChar) != -1)
+ {
+ sb.Append(key.KeyChar);
+ Console.Write(password ? '*' : key.KeyChar);
+ }
+ else if (key.Key == ConsoleKey.Backspace && sb.Length > 0)
+ {
+ sb.Length--;
+ Console.Write("\b \b");
+ }
+ } while (key.Key != ConsoleKey.Enter);
+ }
+
+ Console.ForegroundColor = prevForeground;
+ if (password) ReadingPassword = false;
+ Console.WriteLine();
+ return sb.ToString();
+ }
+
+ public static SecureString ReadSecureString(string prompt)
+ {
+ const string t = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
+ SecureString securePwd = new SecureString();
+ ConsoleKeyInfo key;
+
+ if (!string.IsNullOrEmpty(prompt))
+ {
+ Console.Write(prompt + ": ");
+ }
+
+ ReadingPassword = true;
+ Console.ForegroundColor = ConsoleColor.Yellow;
+
+ do
+ {
+ key = Console.ReadKey(true);
+ if (t.IndexOf(key.KeyChar) != -1)
+ {
+ securePwd.AppendChar(key.KeyChar);
+ Console.Write('*');
+ }
+ else if (key.Key == ConsoleKey.Backspace && securePwd.Length > 0)
+ {
+ securePwd.RemoveAt(securePwd.Length - 1);
+ Console.Write(key.KeyChar);
+ Console.Write(' ');
+ Console.Write(key.KeyChar);
+ }
+ } while (key.Key != ConsoleKey.Enter);
+
+ Console.ForegroundColor = ConsoleColor.White;
+ ReadingPassword = false;
+ Console.WriteLine();
+ securePwd.MakeReadOnly();
+ return securePwd;
+ }
}
}
diff --git a/src/Neo.ConsoleService/ConsoleServiceBase.cs b/src/Neo.ConsoleService/ConsoleServiceBase.cs
index 5598dcaa45..8b169fb808 100644
--- a/src/Neo.ConsoleService/ConsoleServiceBase.cs
+++ b/src/Neo.ConsoleService/ConsoleServiceBase.cs
@@ -17,9 +17,7 @@
using System.Net;
using System.Reflection;
using System.Runtime.Loader;
-using System.Security;
using System.ServiceProcess;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -33,7 +31,6 @@ public abstract class ConsoleServiceBase
public abstract string ServiceName { get; }
protected bool ShowPrompt { get; set; } = true;
- public bool ReadingPassword { get; set; } = false;
private bool _running;
private readonly CancellationTokenSource _shutdownTokenSource = new();
@@ -91,10 +88,11 @@ private bool OnCommand(string commandLine)
availableCommands.Add((command, arguments.ToArray()));
}
- catch
+ catch (Exception ex)
{
// Skip parse errors
possibleHelp = command.Key;
+ ConsoleHelper.Error($"{ex.InnerException?.Message ?? ex.Message}");
}
}
}
@@ -298,89 +296,6 @@ public virtual void OnStop()
_shutdownAcknowledged.Signal();
}
- public string ReadUserInput(string prompt, bool password = false)
- {
- const string t = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
- var sb = new StringBuilder();
-
- if (!string.IsNullOrEmpty(prompt))
- {
- Console.Write(prompt + ": ");
- }
-
- if (password) ReadingPassword = true;
- var prevForeground = Console.ForegroundColor;
- Console.ForegroundColor = ConsoleColor.Yellow;
-
- if (Console.IsInputRedirected)
- {
- // neo-gui Console require it
- sb.Append(Console.ReadLine());
- }
- else
- {
- ConsoleKeyInfo key;
- do
- {
- key = Console.ReadKey(true);
-
- if (t.IndexOf(key.KeyChar) != -1)
- {
- sb.Append(key.KeyChar);
- Console.Write(password ? '*' : key.KeyChar);
- }
- else if (key.Key == ConsoleKey.Backspace && sb.Length > 0)
- {
- sb.Length--;
- Console.Write("\b \b");
- }
- } while (key.Key != ConsoleKey.Enter);
- }
-
- Console.ForegroundColor = prevForeground;
- if (password) ReadingPassword = false;
- Console.WriteLine();
- return sb.ToString();
- }
-
- public SecureString ReadSecureString(string prompt)
- {
- const string t = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
- SecureString securePwd = new SecureString();
- ConsoleKeyInfo key;
-
- if (!string.IsNullOrEmpty(prompt))
- {
- Console.Write(prompt + ": ");
- }
-
- ReadingPassword = true;
- Console.ForegroundColor = ConsoleColor.Yellow;
-
- do
- {
- key = Console.ReadKey(true);
- if (t.IndexOf(key.KeyChar) != -1)
- {
- securePwd.AppendChar(key.KeyChar);
- Console.Write('*');
- }
- else if (key.Key == ConsoleKey.Backspace && securePwd.Length > 0)
- {
- securePwd.RemoveAt(securePwd.Length - 1);
- Console.Write(key.KeyChar);
- Console.Write(' ');
- Console.Write(key.KeyChar);
- }
- } while (key.Key != ConsoleKey.Enter);
-
- Console.ForegroundColor = ConsoleColor.White;
- ReadingPassword = false;
- Console.WriteLine();
- securePwd.MakeReadOnly();
- return securePwd;
- }
-
private void TriggerGracefulShutdown()
{
if (!_running) return;
@@ -575,8 +490,10 @@ public void Run(string[] args)
}
else
{
- Debug.Assert(OperatingSystem.IsWindows());
+ Debug.Assert(Environment.OSVersion.Platform == PlatformID.Win32NT);
+#pragma warning disable CA1416
ServiceBase.Run(new ServiceProxy(this));
+#pragma warning restore CA1416
}
}
diff --git a/src/Neo.ConsoleService/Neo.ConsoleService.csproj b/src/Neo.ConsoleService/Neo.ConsoleService.csproj
index 5c2fd653ad..0139d2a0f5 100644
--- a/src/Neo.ConsoleService/Neo.ConsoleService.csproj
+++ b/src/Neo.ConsoleService/Neo.ConsoleService.csproj
@@ -1,13 +1,14 @@
+ netstandard2.1;net7.0
Neo.ConsoleService
enable
-
+
diff --git a/src/Neo.Cryptography.BLS12_381/Bls12.Adder.cs b/src/Neo.Cryptography.BLS12_381/Bls12.Adder.cs
new file mode 100644
index 0000000000..3981f19a1b
--- /dev/null
+++ b/src/Neo.Cryptography.BLS12_381/Bls12.Adder.cs
@@ -0,0 +1,71 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// Bls12.Adder.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using System.Runtime.CompilerServices;
+using static Neo.Cryptography.BLS12_381.MillerLoopUtility;
+
+namespace Neo.Cryptography.BLS12_381;
+
+partial class Bls12
+{
+ class Adder : IMillerLoopDriver
+ {
+ public G2Projective Curve;
+ public readonly G2Affine Base;
+ public readonly G1Affine P;
+
+ public Adder(in G1Affine p, in G2Affine q)
+ {
+ Curve = new(q);
+ Base = q;
+ P = p;
+ }
+
+ Fp12 IMillerLoopDriver.DoublingStep(in Fp12 f)
+ {
+ var coeffs = DoublingStep(ref Curve);
+ return Ell(in f, in coeffs, in P);
+ }
+
+ Fp12 IMillerLoopDriver.AdditionStep(in Fp12 f)
+ {
+ var coeffs = AdditionStep(ref Curve, in Base);
+ return Ell(in f, in coeffs, in P);
+ }
+
+ #region IMillerLoopDriver
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Fp12 Square(in Fp12 f) => f.Square();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Fp12 Conjugate(in Fp12 f) => f.Conjugate();
+
+ public static Fp12 One
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => Fp12.One;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ Fp12 IMillerLoopDriver.Square(in Fp12 f) => Adder.Square(f);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ Fp12 IMillerLoopDriver.Conjugate(in Fp12 f) => Adder.Conjugate(f);
+ Fp12 IMillerLoopDriver.One
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => Adder.One;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Neo.Cryptography.BLS12_381/Bls12.cs b/src/Neo.Cryptography.BLS12_381/Bls12.cs
new file mode 100644
index 0000000000..10022ded4d
--- /dev/null
+++ b/src/Neo.Cryptography.BLS12_381/Bls12.cs
@@ -0,0 +1,31 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// Bls12.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using static Neo.Cryptography.BLS12_381.ConstantTimeUtility;
+using static Neo.Cryptography.BLS12_381.MillerLoopUtility;
+
+namespace Neo.Cryptography.BLS12_381;
+
+public static partial class Bls12
+{
+ public static Gt Pairing(in G1Affine p, in G2Affine q)
+ {
+ var either_identity = p.IsIdentity | q.IsIdentity;
+ var p2 = ConditionalSelect(in p, in G1Affine.Generator, either_identity);
+ var q2 = ConditionalSelect(in q, in G2Affine.Generator, either_identity);
+
+ var adder = new Adder(p2, q2);
+
+ var tmp = MillerLoop(adder);
+ var tmp2 = new MillerLoopResult(ConditionalSelect(in tmp, in Fp12.One, either_identity));
+ return tmp2.FinalExponentiation();
+ }
+}
diff --git a/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs b/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs
new file mode 100644
index 0000000000..70517edeb1
--- /dev/null
+++ b/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs
@@ -0,0 +1,42 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// ConstantTimeUtility.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Neo.Cryptography.BLS12_381;
+
+public static class ConstantTimeUtility
+{
+ public static bool ConstantTimeEq(in T a, in T b) where T : unmanaged
+ {
+ ReadOnlySpan a_bytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in a), 1));
+ ReadOnlySpan b_bytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in b), 1));
+ ReadOnlySpan a_u64 = MemoryMarshal.Cast(a_bytes);
+ ReadOnlySpan b_u64 = MemoryMarshal.Cast(b_bytes);
+ ulong f = 0;
+ for (int i = 0; i < a_u64.Length; i++)
+ f |= a_u64[i] ^ b_u64[i];
+ for (int i = a_u64.Length * sizeof(ulong); i < a_bytes.Length; i++)
+ f |= (ulong)a_bytes[i] ^ a_bytes[i];
+ return f == 0;
+ }
+
+ public static T ConditionalSelect(in T a, in T b, bool choice) where T : unmanaged
+ {
+ return choice ? b : a;
+ }
+
+ public static void ConditionalAssign(this ref T self, in T other, bool choice) where T : unmanaged
+ {
+ self = ConditionalSelect(in self, in other, choice);
+ }
+}
diff --git a/src/Neo.Cryptography.BLS12_381/Constants.cs b/src/Neo.Cryptography.BLS12_381/Constants.cs
new file mode 100644
index 0000000000..457b4d720b
--- /dev/null
+++ b/src/Neo.Cryptography.BLS12_381/Constants.cs
@@ -0,0 +1,18 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// Constants.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+namespace Neo.Cryptography.BLS12_381;
+
+static class Constants
+{
+ public const ulong BLS_X = 0xd201_0000_0001_0000;
+ public const bool BLS_X_IS_NEGATIVE = true;
+}
diff --git a/src/Neo.Cryptography.BLS12_381/Fp.cs b/src/Neo.Cryptography.BLS12_381/Fp.cs
new file mode 100644
index 0000000000..4b30b4fa5c
--- /dev/null
+++ b/src/Neo.Cryptography.BLS12_381/Fp.cs
@@ -0,0 +1,491 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// Fp.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using System.Buffers.Binary;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using static Neo.Cryptography.BLS12_381.ConstantTimeUtility;
+using static Neo.Cryptography.BLS12_381.FpConstants;
+using static Neo.Cryptography.BLS12_381.MathUtility;
+
+namespace Neo.Cryptography.BLS12_381;
+
+[StructLayout(LayoutKind.Explicit, Size = Size)]
+public readonly struct Fp : IEquatable, INumber
+{
+ public const int Size = 48;
+ public const int SizeL = Size / sizeof(ulong);
+
+ private static readonly Fp _zero = new();
+
+ public static ref readonly Fp Zero => ref _zero;
+ public static ref readonly Fp One => ref R;
+
+ public bool IsZero => this == Zero;
+
+ public static Fp FromBytes(ReadOnlySpan data)
+ {
+ if (data.Length != Size)
+ throw new FormatException($"The argument `{nameof(data)}` must contain {Size} bytes.");
+
+ Span tmp = stackalloc ulong[SizeL];
+ BinaryPrimitives.TryReadUInt64BigEndian(data[0..8], out tmp[5]);
+ BinaryPrimitives.TryReadUInt64BigEndian(data[8..16], out tmp[4]);
+ BinaryPrimitives.TryReadUInt64BigEndian(data[16..24], out tmp[3]);
+ BinaryPrimitives.TryReadUInt64BigEndian(data[24..32], out tmp[2]);
+ BinaryPrimitives.TryReadUInt64BigEndian(data[32..40], out tmp[1]);
+ BinaryPrimitives.TryReadUInt64BigEndian(data[40..48], out tmp[0]);
+ ReadOnlySpan span = MemoryMarshal.Cast(tmp);
+
+ try
+ {
+ return span[0] * R2;
+ }
+ finally
+ {
+ ulong borrow;
+ (_, borrow) = Sbb(tmp[0], MODULUS[0], 0);
+ (_, borrow) = Sbb(tmp[1], MODULUS[1], borrow);
+ (_, borrow) = Sbb(tmp[2], MODULUS[2], borrow);
+ (_, borrow) = Sbb(tmp[3], MODULUS[3], borrow);
+ (_, borrow) = Sbb(tmp[4], MODULUS[4], borrow);
+ (_, borrow) = Sbb(tmp[5], MODULUS[5], borrow);
+ if (borrow == 0)
+ {
+ // If the element is smaller than MODULUS then the subtraction will underflow.
+ // Otherwise, throws.
+ // Why not throw before return?
+ // Because we want to run the method in a constant time.
+ throw new FormatException();
+ }
+ }
+ }
+
+ internal static Fp FromRawUnchecked(ulong[] values)
+ {
+ if (values.Length != SizeL)
+ throw new FormatException($"The argument `{nameof(values)}` must contain {SizeL} entries.");
+
+ return MemoryMarshal.Cast(values)[0];
+ }
+
+ public static Fp Random(RandomNumberGenerator rng)
+ {
+ Span buffer = stackalloc byte[Size * 2];
+ rng.GetBytes(buffer);
+ Span d = MemoryMarshal.Cast(buffer);
+ return d[0] * R2 + d[1] * R3;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ReadOnlySpan GetSpan()
+ {
+ return MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in this), 1));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private Span GetSpanU64()
+ {
+ return MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in this), 1));
+ }
+
+ public static bool operator ==(in Fp left, in Fp right)
+ {
+ return ConstantTimeEq(in left, in right);
+ }
+
+ public static bool operator !=(in Fp left, in Fp right)
+ {
+ return !(left == right);
+ }
+
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ if (obj is not Fp other) return false;
+ return this == other;
+ }
+
+ public bool Equals(Fp other)
+ {
+ return this == other;
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+
+ public byte[] ToArray()
+ {
+ byte[] result = new byte[Size];
+ TryWrite(result);
+ return result;
+ }
+
+ public bool TryWrite(Span buffer)
+ {
+ if (buffer.Length < Size) return false;
+
+ ReadOnlySpan u64 = GetSpanU64();
+ Fp tmp = MontgomeryReduce(u64[0], u64[1], u64[2], u64[3], u64[4], u64[5], 0, 0, 0, 0, 0, 0);
+ u64 = tmp.GetSpanU64();
+
+ BinaryPrimitives.WriteUInt64BigEndian(buffer[0..8], u64[5]);
+ BinaryPrimitives.WriteUInt64BigEndian(buffer[8..16], u64[4]);
+ BinaryPrimitives.WriteUInt64BigEndian(buffer[16..24], u64[3]);
+ BinaryPrimitives.WriteUInt64BigEndian(buffer[24..32], u64[2]);
+ BinaryPrimitives.WriteUInt64BigEndian(buffer[32..40], u64[1]);
+ BinaryPrimitives.WriteUInt64BigEndian(buffer[40..48], u64[0]);
+
+ return true;
+ }
+
+ public override string ToString()
+ {
+ var output = string.Empty;
+ foreach (var b in ToArray())
+ output += b.ToString("x2");
+
+ return "0x" + output;
+ }
+
+ public bool LexicographicallyLargest()
+ {
+ ReadOnlySpan s = GetSpanU64();
+ Fp tmp = MontgomeryReduce(s[0], s[1], s[2], s[3], s[4], s[5], 0, 0, 0, 0, 0, 0);
+ ReadOnlySpan t = tmp.GetSpanU64();
+ ulong borrow;
+
+ (_, borrow) = Sbb(t[0], 0xdcff_7fff_ffff_d556, 0);
+ (_, borrow) = Sbb(t[1], 0x0f55_ffff_58a9_ffff, borrow);
+ (_, borrow) = Sbb(t[2], 0xb398_6950_7b58_7b12, borrow);
+ (_, borrow) = Sbb(t[3], 0xb23b_a5c2_79c2_895f, borrow);
+ (_, borrow) = Sbb(t[4], 0x258d_d3db_21a5_d66b, borrow);
+ (_, borrow) = Sbb(t[5], 0x0d00_88f5_1cbf_f34d, borrow);
+
+ return borrow == 0;
+ }
+
+ public Fp Sqrt()
+ {
+ // We use Shank's method, as p = 3 (mod 4). This means
+ // we only need to exponentiate by (p + 1) / 4. This only
+ // works for elements that are actually quadratic residue,
+ // so we check that we got the correct result at the end.
+ Fp result = this.PowVartime(P_1_4);
+ if (result.Square() != this) throw new ArithmeticException();
+ return result;
+ }
+
+ public Fp Invert()
+ {
+ if (!TryInvert(out Fp result))
+ throw new DivideByZeroException();
+ return result;
+ }
+
+ public bool TryInvert(out Fp result)
+ {
+ // Exponentiate by p - 2
+ result = this.PowVartime(P_2);
+
+ // Why not return before Pow() if IsZero?
+ // Because we want to run the method in a constant time.
+ return !IsZero;
+ }
+
+ private Fp SubtractP()
+ {
+ Fp result;
+ ReadOnlySpan s = GetSpanU64();
+ Span r = result.GetSpanU64();
+ ulong borrow;
+
+ (r[0], borrow) = Sbb(s[0], MODULUS[0], 0);
+ (r[1], borrow) = Sbb(s[1], MODULUS[1], borrow);
+ (r[2], borrow) = Sbb(s[2], MODULUS[2], borrow);
+ (r[3], borrow) = Sbb(s[3], MODULUS[3], borrow);
+ (r[4], borrow) = Sbb(s[4], MODULUS[4], borrow);
+ (r[5], borrow) = Sbb(s[5], MODULUS[5], borrow);
+
+ borrow = borrow == 0 ? ulong.MinValue : ulong.MaxValue;
+ r[0] = (s[0] & borrow) | (r[0] & ~borrow);
+ r[1] = (s[1] & borrow) | (r[1] & ~borrow);
+ r[2] = (s[2] & borrow) | (r[2] & ~borrow);
+ r[3] = (s[3] & borrow) | (r[3] & ~borrow);
+ r[4] = (s[4] & borrow) | (r[4] & ~borrow);
+ r[5] = (s[5] & borrow) | (r[5] & ~borrow);
+
+ return result;
+ }
+
+ public static Fp operator +(in Fp a, in Fp b)
+ {
+ Fp result;
+ ReadOnlySpan s = a.GetSpanU64(), r = b.GetSpanU64();
+ Span d = result.GetSpanU64();
+
+ ulong carry = 0;
+ (d[0], carry) = Adc(s[0], r[0], carry);
+ (d[1], carry) = Adc(s[1], r[1], carry);
+ (d[2], carry) = Adc(s[2], r[2], carry);
+ (d[3], carry) = Adc(s[3], r[3], carry);
+ (d[4], carry) = Adc(s[4], r[4], carry);
+ (d[5], _) = Adc(s[5], r[5], carry);
+
+ return result.SubtractP();
+ }
+
+ public static Fp operator -(in Fp a)
+ {
+ Fp result;
+ ReadOnlySpan self = a.GetSpanU64();
+ Span d = result.GetSpanU64();
+
+ ulong borrow = 0;
+ (d[0], borrow) = Sbb(MODULUS[0], self[0], borrow);
+ (d[1], borrow) = Sbb(MODULUS[1], self[1], borrow);
+ (d[2], borrow) = Sbb(MODULUS[2], self[2], borrow);
+ (d[3], borrow) = Sbb(MODULUS[3], self[3], borrow);
+ (d[4], borrow) = Sbb(MODULUS[4], self[4], borrow);
+ (d[5], _) = Sbb(MODULUS[5], self[5], borrow);
+
+ ulong mask = a.IsZero ? ulong.MinValue : ulong.MaxValue;
+ d[0] &= mask;
+ d[1] &= mask;
+ d[2] &= mask;
+ d[3] &= mask;
+ d[4] &= mask;
+ d[5] &= mask;
+
+ return result;
+ }
+
+ public static Fp operator -(in Fp a, in Fp b)
+ {
+ return -b + a;
+ }
+
+ public static Fp SumOfProducts(ReadOnlySpan a, ReadOnlySpan b)
+ {
+ int length = a.Length;
+ if (length != b.Length)
+ throw new ArgumentException("The lengths of the two arrays must be the same.");
+
+ Fp result;
+ ReadOnlySpan au = MemoryMarshal.Cast(a);
+ ReadOnlySpan bu = MemoryMarshal.Cast(b);
+ Span u = result.GetSpanU64();
+
+ for (int j = 0; j < 6; j++)
+ {
+ ulong carry;
+
+ var (t0, t1, t2, t3, t4, t5, t6) = (u[0], u[1], u[2], u[3], u[4], u[5], 0ul);
+ for (int i = 0; i < length; i++)
+ {
+ (t0, carry) = Mac(t0, au[i * SizeL + j], bu[i * SizeL + 0], 0);
+ (t1, carry) = Mac(t1, au[i * SizeL + j], bu[i * SizeL + 1], carry);
+ (t2, carry) = Mac(t2, au[i * SizeL + j], bu[i * SizeL + 2], carry);
+ (t3, carry) = Mac(t3, au[i * SizeL + j], bu[i * SizeL + 3], carry);
+ (t4, carry) = Mac(t4, au[i * SizeL + j], bu[i * SizeL + 4], carry);
+ (t5, carry) = Mac(t5, au[i * SizeL + j], bu[i * SizeL + 5], carry);
+ (t6, _) = Adc(t6, 0, carry);
+ }
+
+ ulong k = unchecked(t0 * INV);
+ (_, carry) = Mac(t0, k, MODULUS[0], 0);
+ (u[0], carry) = Mac(t1, k, MODULUS[1], carry);
+ (u[1], carry) = Mac(t2, k, MODULUS[2], carry);
+ (u[2], carry) = Mac(t3, k, MODULUS[3], carry);
+ (u[3], carry) = Mac(t4, k, MODULUS[4], carry);
+ (u[4], carry) = Mac(t5, k, MODULUS[5], carry);
+ (u[5], _) = Adc(t6, 0, carry);
+ }
+
+ return result.SubtractP();
+ }
+
+ private static Fp MontgomeryReduce(ulong r0, ulong r1, ulong r2, ulong r3, ulong r4, ulong r5, ulong r6, ulong r7, ulong r8, ulong r9, ulong r10, ulong r11)
+ {
+ ulong carry, carry2;
+
+ ulong k = unchecked(r0 * INV);
+ (_, carry) = Mac(r0, k, MODULUS[0], 0);
+ (r1, carry) = Mac(r1, k, MODULUS[1], carry);
+ (r2, carry) = Mac(r2, k, MODULUS[2], carry);
+ (r3, carry) = Mac(r3, k, MODULUS[3], carry);
+ (r4, carry) = Mac(r4, k, MODULUS[4], carry);
+ (r5, carry) = Mac(r5, k, MODULUS[5], carry);
+ (r6, carry2) = Adc(r6, 0, carry);
+
+ k = unchecked(r1 * INV);
+ (_, carry) = Mac(r1, k, MODULUS[0], 0);
+ (r2, carry) = Mac(r2, k, MODULUS[1], carry);
+ (r3, carry) = Mac(r3, k, MODULUS[2], carry);
+ (r4, carry) = Mac(r4, k, MODULUS[3], carry);
+ (r5, carry) = Mac(r5, k, MODULUS[4], carry);
+ (r6, carry) = Mac(r6, k, MODULUS[5], carry);
+ (r7, carry2) = Adc(r7, carry2, carry);
+
+ k = unchecked(r2 * INV);
+ (_, carry) = Mac(r2, k, MODULUS[0], 0);
+ (r3, carry) = Mac(r3, k, MODULUS[1], carry);
+ (r4, carry) = Mac(r4, k, MODULUS[2], carry);
+ (r5, carry) = Mac(r5, k, MODULUS[3], carry);
+ (r6, carry) = Mac(r6, k, MODULUS[4], carry);
+ (r7, carry) = Mac(r7, k, MODULUS[5], carry);
+ (r8, carry2) = Adc(r8, carry2, carry);
+
+ k = unchecked(r3 * INV);
+ (_, carry) = Mac(r3, k, MODULUS[0], 0);
+ (r4, carry) = Mac(r4, k, MODULUS[1], carry);
+ (r5, carry) = Mac(r5, k, MODULUS[2], carry);
+ (r6, carry) = Mac(r6, k, MODULUS[3], carry);
+ (r7, carry) = Mac(r7, k, MODULUS[4], carry);
+ (r8, carry) = Mac(r8, k, MODULUS[5], carry);
+ (r9, carry2) = Adc(r9, carry2, carry);
+
+ k = unchecked(r4 * INV);
+ (_, carry) = Mac(r4, k, MODULUS[0], 0);
+ (r5, carry) = Mac(r5, k, MODULUS[1], carry);
+ (r6, carry) = Mac(r6, k, MODULUS[2], carry);
+ (r7, carry) = Mac(r7, k, MODULUS[3], carry);
+ (r8, carry) = Mac(r8, k, MODULUS[4], carry);
+ (r9, carry) = Mac(r9, k, MODULUS[5], carry);
+ (r10, carry2) = Adc(r10, carry2, carry);
+
+ k = unchecked(r5 * INV);
+ (_, carry) = Mac(r5, k, MODULUS[0], 0);
+ (r6, carry) = Mac(r6, k, MODULUS[1], carry);
+ (r7, carry) = Mac(r7, k, MODULUS[2], carry);
+ (r8, carry) = Mac(r8, k, MODULUS[3], carry);
+ (r9, carry) = Mac(r9, k, MODULUS[4], carry);
+ (r10, carry) = Mac(r10, k, MODULUS[5], carry);
+ (r11, _) = Adc(r11, carry2, carry);
+
+ ReadOnlySpan tmp = stackalloc[] { r6, r7, r8, r9, r10, r11 };
+ return MemoryMarshal.Cast(tmp)[0].SubtractP();
+ }
+
+ public static Fp operator *(in Fp a, in Fp b)
+ {
+ ReadOnlySpan s = a.GetSpanU64(), r = b.GetSpanU64();
+ Span t = stackalloc ulong[SizeL * 2];
+ ulong carry;
+
+ (t[0], carry) = Mac(0, s[0], r[0], 0);
+ (t[1], carry) = Mac(0, s[0], r[1], carry);
+ (t[2], carry) = Mac(0, s[0], r[2], carry);
+ (t[3], carry) = Mac(0, s[0], r[3], carry);
+ (t[4], carry) = Mac(0, s[0], r[4], carry);
+ (t[5], t[6]) = Mac(0, s[0], r[5], carry);
+
+ (t[1], carry) = Mac(t[1], s[1], r[0], 0);
+ (t[2], carry) = Mac(t[2], s[1], r[1], carry);
+ (t[3], carry) = Mac(t[3], s[1], r[2], carry);
+ (t[4], carry) = Mac(t[4], s[1], r[3], carry);
+ (t[5], carry) = Mac(t[5], s[1], r[4], carry);
+ (t[6], t[7]) = Mac(t[6], s[1], r[5], carry);
+
+ (t[2], carry) = Mac(t[2], s[2], r[0], 0);
+ (t[3], carry) = Mac(t[3], s[2], r[1], carry);
+ (t[4], carry) = Mac(t[4], s[2], r[2], carry);
+ (t[5], carry) = Mac(t[5], s[2], r[3], carry);
+ (t[6], carry) = Mac(t[6], s[2], r[4], carry);
+ (t[7], t[8]) = Mac(t[7], s[2], r[5], carry);
+ (t[3], carry) = Mac(t[3], s[3], r[0], 0);
+ (t[4], carry) = Mac(t[4], s[3], r[1], carry);
+ (t[5], carry) = Mac(t[5], s[3], r[2], carry);
+ (t[6], carry) = Mac(t[6], s[3], r[3], carry);
+ (t[7], carry) = Mac(t[7], s[3], r[4], carry);
+ (t[8], t[9]) = Mac(t[8], s[3], r[5], carry);
+ (t[4], carry) = Mac(t[4], s[4], r[0], 0);
+ (t[5], carry) = Mac(t[5], s[4], r[1], carry);
+ (t[6], carry) = Mac(t[6], s[4], r[2], carry);
+ (t[7], carry) = Mac(t[7], s[4], r[3], carry);
+ (t[8], carry) = Mac(t[8], s[4], r[4], carry);
+ (t[9], t[10]) = Mac(t[9], s[4], r[5], carry);
+ (t[5], carry) = Mac(t[5], s[5], r[0], 0);
+ (t[6], carry) = Mac(t[6], s[5], r[1], carry);
+ (t[7], carry) = Mac(t[7], s[5], r[2], carry);
+ (t[8], carry) = Mac(t[8], s[5], r[3], carry);
+ (t[9], carry) = Mac(t[9], s[5], r[4], carry);
+ (t[10], t[11]) = Mac(t[10], s[5], r[5], carry);
+
+ return MontgomeryReduce(t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8], t[9], t[10], t[11]);
+ }
+
+ public Fp Square()
+ {
+ ReadOnlySpan self = GetSpanU64();
+ ulong t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11;
+ ulong carry;
+
+ (t1, carry) = Mac(0, self[0], self[1], 0);
+ (t2, carry) = Mac(0, self[0], self[2], carry);
+ (t3, carry) = Mac(0, self[0], self[3], carry);
+ (t4, carry) = Mac(0, self[0], self[4], carry);
+ (t5, t6) = Mac(0, self[0], self[5], carry);
+
+ (t3, carry) = Mac(t3, self[1], self[2], 0);
+ (t4, carry) = Mac(t4, self[1], self[3], carry);
+ (t5, carry) = Mac(t5, self[1], self[4], carry);
+ (t6, t7) = Mac(t6, self[1], self[5], carry);
+
+ (t5, carry) = Mac(t5, self[2], self[3], 0);
+ (t6, carry) = Mac(t6, self[2], self[4], carry);
+ (t7, t8) = Mac(t7, self[2], self[5], carry);
+
+ (t7, carry) = Mac(t7, self[3], self[4], 0);
+ (t8, t9) = Mac(t8, self[3], self[5], carry);
+
+ (t9, t10) = Mac(t9, self[4], self[5], 0);
+
+ t11 = t10 >> 63;
+ t10 = (t10 << 1) | (t9 >> 63);
+ t9 = (t9 << 1) | (t8 >> 63);
+ t8 = (t8 << 1) | (t7 >> 63);
+ t7 = (t7 << 1) | (t6 >> 63);
+ t6 = (t6 << 1) | (t5 >> 63);
+ t5 = (t5 << 1) | (t4 >> 63);
+ t4 = (t4 << 1) | (t3 >> 63);
+ t3 = (t3 << 1) | (t2 >> 63);
+ t2 = (t2 << 1) | (t1 >> 63);
+ t1 <<= 1;
+
+ (t0, carry) = Mac(0, self[0], self[0], 0);
+ (t1, carry) = Adc(t1, carry, 0);
+ (t2, carry) = Mac(t2, self[1], self[1], carry);
+ (t3, carry) = Adc(t3, carry, 0);
+ (t4, carry) = Mac(t4, self[2], self[2], carry);
+ (t5, carry) = Adc(t5, carry, 0);
+ (t6, carry) = Mac(t6, self[3], self[3], carry);
+ (t7, carry) = Adc(t7, carry, 0);
+ (t8, carry) = Mac(t8, self[4], self[4], carry);
+ (t9, carry) = Adc(t9, carry, 0);
+ (t10, carry) = Mac(t10, self[5], self[5], carry);
+ (t11, _) = Adc(t11, carry, 0);
+
+ return MontgomeryReduce(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11);
+ }
+
+ #region Instance math methods
+
+ public Fp Negate() => -this;
+ public Fp Multiply(in Fp value) => this * value;
+ public Fp Sum(in Fp value) => this + value;
+ public Fp Subtract(in Fp value) => this - value;
+
+ #endregion
+}
diff --git a/src/Neo.Cryptography.BLS12_381/Fp12.cs b/src/Neo.Cryptography.BLS12_381/Fp12.cs
new file mode 100644
index 0000000000..2ed7ee988a
--- /dev/null
+++ b/src/Neo.Cryptography.BLS12_381/Fp12.cs
@@ -0,0 +1,218 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// Fp12.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+
+namespace Neo.Cryptography.BLS12_381;
+
+[StructLayout(LayoutKind.Explicit, Size = Size)]
+public readonly struct Fp12 : IEquatable, INumber
+{
+ [FieldOffset(0)]
+ public readonly Fp6 C0;
+ [FieldOffset(Fp6.Size)]
+ public readonly Fp6 C1;
+
+ public const int Size = Fp6.Size * 2;
+
+ private static readonly Fp12 _zero = new();
+ private static readonly Fp12 _one = new(in Fp6.One);
+
+ public static ref readonly Fp12 Zero => ref _zero;
+ public static ref readonly Fp12 One => ref _one;
+
+ public bool IsZero => C0.IsZero & C1.IsZero;
+
+ public Fp12(in Fp f)
+ : this(new Fp6(in f), in Fp6.Zero)
+ {
+ }
+
+ public Fp12(in Fp2 f)
+ : this(new Fp6(in f), in Fp6.Zero)
+ {
+ }
+
+ public Fp12(in Fp6 f)
+ : this(in f, in Fp6.Zero)
+ {
+ }
+
+ public Fp12(in Fp6 c0, in Fp6 c1)
+ {
+ C0 = c0;
+ C1 = c1;
+ }
+
+ public static Fp12 FromBytes(ReadOnlySpan data)
+ {
+ if (data.Length != Size)
+ throw new FormatException($"The argument `{nameof(data)}` must contain {Size} bytes.");
+ Fp6 c0 = Fp6.FromBytes(data[Fp6.Size..]);
+ Fp6 c1 = Fp6.FromBytes(data[..Fp6.Size]);
+ return new(in c0, in c1);
+ }
+
+ public static bool operator ==(in Fp12 a, in Fp12 b)
+ {
+ return a.C0 == b.C0 & a.C1 == b.C1;
+ }
+
+ public static bool operator !=(in Fp12 a, in Fp12 b)
+ {
+ return !(a == b);
+ }
+
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ if (obj is not Fp12 other) return false;
+ return this == other;
+ }
+
+ public bool Equals(Fp12 other)
+ {
+ return this == other;
+ }
+
+ public override int GetHashCode()
+ {
+ return C0.GetHashCode() ^ C1.GetHashCode();
+ }
+
+ public byte[] ToArray()
+ {
+ byte[] result = new byte[Size];
+ TryWrite(result);
+ return result;
+ }
+
+ public bool TryWrite(Span buffer)
+ {
+ if (buffer.Length < Size) return false;
+ C0.TryWrite(buffer[Fp6.Size..Size]);
+ C1.TryWrite(buffer[0..Fp6.Size]);
+ return true;
+ }
+
+ public static Fp12 Random(RandomNumberGenerator rng)
+ {
+ return new(Fp6.Random(rng), Fp6.Random(rng));
+ }
+
+ internal Fp12 MulBy_014(in Fp2 c0, in Fp2 c1, in Fp2 c4)
+ {
+ var aa = C0.MulBy_01(in c0, in c1);
+ var bb = C1.MulBy_1(in c4);
+ var o = c1 + c4;
+ var _c1 = C1 + C0;
+ _c1 = _c1.MulBy_01(in c0, in o);
+ _c1 = _c1 - aa - bb;
+ var _c0 = bb;
+ _c0 = _c0.MulByNonresidue();
+ _c0 += aa;
+
+ return new Fp12(in _c0, in _c1);
+ }
+
+ public Fp12 Conjugate()
+ {
+ return new Fp12(in C0, -C1);
+ }
+
+ public Fp12 FrobeniusMap()
+ {
+ var c0 = C0.FrobeniusMap();
+ var c1 = C1.FrobeniusMap();
+
+ // c1 = c1 * (u + 1)^((p - 1) / 6)
+ c1 *= new Fp6(new Fp2(
+ Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x0708_9552_b319_d465,
+ 0xc669_5f92_b50a_8313,
+ 0x97e8_3ccc_d117_228f,
+ 0xa35b_aeca_b2dc_29ee,
+ 0x1ce3_93ea_5daa_ce4d,
+ 0x08f2_220f_b0fb_66eb
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xb2f6_6aad_4ce5_d646,
+ 0x5842_a06b_fc49_7cec,
+ 0xcf48_95d4_2599_d394,
+ 0xc11b_9cba_40a8_e8d0,
+ 0x2e38_13cb_e5a0_de89,
+ 0x110e_efda_8884_7faf
+ })));
+
+ return new Fp12(in c0, in c1);
+ }
+
+ public Fp12 Square()
+ {
+ var ab = C0 * C1;
+ var c0c1 = C0 + C1;
+ var c0 = C1.MulByNonresidue();
+ c0 += C0;
+ c0 *= c0c1;
+ c0 -= ab;
+ var c1 = ab + ab;
+ c0 -= ab.MulByNonresidue();
+
+ return new Fp12(in c0, in c1);
+ }
+
+ public Fp12 Invert()
+ {
+ Fp6 t = (C0.Square() - C1.Square().MulByNonresidue()).Invert();
+ return new Fp12(C0 * t, C1 * -t);
+ }
+
+ public static Fp12 operator -(in Fp12 a)
+ {
+ return new Fp12(-a.C0, -a.C1);
+ }
+
+ public static Fp12 operator +(in Fp12 a, in Fp12 b)
+ {
+ return new Fp12(a.C0 + b.C0, a.C1 + b.C1);
+ }
+
+ public static Fp12 operator -(in Fp12 a, in Fp12 b)
+ {
+ return new Fp12(a.C0 - b.C0, a.C1 - b.C1);
+ }
+
+ public static Fp12 operator *(in Fp12 a, in Fp12 b)
+ {
+ var aa = a.C0 * b.C0;
+ var bb = a.C1 * b.C1;
+ var o = b.C0 + b.C1;
+ var c1 = a.C1 + a.C0;
+ c1 *= o;
+ c1 -= aa;
+ c1 -= bb;
+ var c0 = bb.MulByNonresidue();
+ c0 += aa;
+
+ return new Fp12(in c0, in c1);
+ }
+
+ #region Instance math methods
+
+ public Fp12 Negate() => -this;
+ public Fp12 Multiply(in Fp12 value) => this * value;
+ public Fp12 Sum(in Fp12 value) => this + value;
+ public Fp12 Subtract(in Fp12 value) => this - value;
+
+ #endregion
+}
diff --git a/src/Neo.Cryptography.BLS12_381/Fp2.cs b/src/Neo.Cryptography.BLS12_381/Fp2.cs
new file mode 100644
index 0000000000..4ca3830970
--- /dev/null
+++ b/src/Neo.Cryptography.BLS12_381/Fp2.cs
@@ -0,0 +1,274 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// Fp2.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using static Neo.Cryptography.BLS12_381.ConstantTimeUtility;
+
+namespace Neo.Cryptography.BLS12_381;
+
+[StructLayout(LayoutKind.Explicit, Size = Size)]
+public readonly struct Fp2 : IEquatable, INumber
+{
+ [FieldOffset(0)]
+ public readonly Fp C0;
+ [FieldOffset(Fp.Size)]
+ public readonly Fp C1;
+
+ public const int Size = Fp.Size * 2;
+
+ private static readonly Fp2 _zero = new();
+ private static readonly Fp2 _one = new(in Fp.One);
+
+ public static ref readonly Fp2 Zero => ref _zero;
+ public static ref readonly Fp2 One => ref _one;
+
+ public bool IsZero => C0.IsZero & C1.IsZero;
+
+ public Fp2(in Fp f)
+ : this(in f, in Fp.Zero)
+ {
+ }
+
+ public Fp2(in Fp c0, in Fp c1)
+ {
+ C0 = c0;
+ C1 = c1;
+ }
+
+ public static Fp2 FromBytes(ReadOnlySpan data)
+ {
+ if (data.Length != Size)
+ throw new FormatException($"The argument `{nameof(data)}` must contain {Size} bytes.");
+ Fp c0 = Fp.FromBytes(data[Fp.Size..]);
+ Fp c1 = Fp.FromBytes(data[..Fp.Size]);
+ return new(in c0, in c1);
+ }
+
+ public static Fp2 Random(RandomNumberGenerator rng)
+ {
+ return new(Fp.Random(rng), Fp.Random(rng));
+ }
+
+ public static bool operator ==(in Fp2 a, in Fp2 b)
+ {
+ return a.C0 == b.C0 & a.C1 == b.C1;
+ }
+
+ public static bool operator !=(in Fp2 a, in Fp2 b)
+ {
+ return !(a == b);
+ }
+
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ if (obj is not Fp2 other) return false;
+ return this == other;
+ }
+
+ public bool Equals(Fp2 other)
+ {
+ return this == other;
+ }
+
+ public override int GetHashCode()
+ {
+ return C0.GetHashCode() ^ C1.GetHashCode();
+ }
+
+ public byte[] ToArray()
+ {
+ byte[] result = new byte[Size];
+ TryWrite(result);
+ return result;
+ }
+
+ public bool TryWrite(Span buffer)
+ {
+ if (buffer.Length < Size) return false;
+ C0.TryWrite(buffer[Fp.Size..Size]);
+ C1.TryWrite(buffer[0..Fp.Size]);
+ return true;
+ }
+
+ public Fp2 FrobeniusMap()
+ {
+ // This is always just a conjugation. If you're curious why, here's
+ // an article about it: https://alicebob.cryptoland.net/the-frobenius-endomorphism-with-finite-fields/
+ return Conjugate();
+ }
+
+ public Fp2 Conjugate()
+ {
+ return new(in C0, -C1);
+ }
+
+ public Fp2 MulByNonresidue()
+ {
+ // Multiply a + bu by u + 1, getting
+ // au + a + bu^2 + bu
+ // and because u^2 = -1, we get
+ // (a - b) + (a + b)u
+
+ return new(C0 - C1, C0 + C1);
+ }
+
+ public bool LexicographicallyLargest()
+ {
+ // If this element's c1 coefficient is lexicographically largest
+ // then it is lexicographically largest. Otherwise, in the event
+ // the c1 coefficient is zero and the c0 coefficient is
+ // lexicographically largest, then this element is lexicographically
+ // largest.
+
+ return C1.LexicographicallyLargest() | (C1.IsZero & C0.LexicographicallyLargest());
+ }
+
+ public Fp2 Square()
+ {
+ // Complex squaring:
+ //
+ // v0 = c0 * c1
+ // c0' = (c0 + c1) * (c0 + \beta*c1) - v0 - \beta * v0
+ // c1' = 2 * v0
+ //
+ // In BLS12-381's F_{p^2}, our \beta is -1 so we
+ // can modify this formula:
+ //
+ // c0' = (c0 + c1) * (c0 - c1)
+ // c1' = 2 * c0 * c1
+
+ var a = C0 + C1;
+ var b = C0 - C1;
+ var c = C0 + C0;
+
+ return new(a * b, c * C1);
+ }
+
+ public static Fp2 operator *(in Fp2 a, in Fp2 b)
+ {
+ // F_{p^2} x F_{p^2} multiplication implemented with operand scanning (schoolbook)
+ // computes the result as:
+ //
+ // a·b = (a_0 b_0 + a_1 b_1 β) + (a_0 b_1 + a_1 b_0)i
+ //
+ // In BLS12-381's F_{p^2}, our β is -1, so the resulting F_{p^2} element is:
+ //
+ // c_0 = a_0 b_0 - a_1 b_1
+ // c_1 = a_0 b_1 + a_1 b_0
+ //
+ // Each of these is a "sum of products", which we can compute efficiently.
+
+ return new(
+ Fp.SumOfProducts(stackalloc[] { a.C0, -a.C1 }, stackalloc[] { b.C0, b.C1 }),
+ Fp.SumOfProducts(stackalloc[] { a.C0, a.C1 }, stackalloc[] { b.C1, b.C0 })
+ );
+ }
+
+ public static Fp2 operator +(in Fp2 a, in Fp2 b)
+ {
+ return new(a.C0 + b.C0, a.C1 + b.C1);
+ }
+
+ public static Fp2 operator -(in Fp2 a, in Fp2 b)
+ {
+ return new(a.C0 - b.C0, a.C1 - b.C1);
+ }
+
+ public static Fp2 operator -(in Fp2 a)
+ {
+ return new(-a.C0, -a.C1);
+ }
+
+ public Fp2 Sqrt()
+ {
+ // Algorithm 9, https://eprint.iacr.org/2012/685.pdf
+ // with constant time modifications.
+
+ // a1 = self^((p - 3) / 4)
+ var a1 = this.PowVartime(new ulong[]
+ {
+ 0xee7f_bfff_ffff_eaaa,
+ 0x07aa_ffff_ac54_ffff,
+ 0xd9cc_34a8_3dac_3d89,
+ 0xd91d_d2e1_3ce1_44af,
+ 0x92c6_e9ed_90d2_eb35,
+ 0x0680_447a_8e5f_f9a6
+ });
+
+ // alpha = a1^2 * self = self^((p - 3) / 2 + 1) = self^((p - 1) / 2)
+ var alpha = a1.Square() * this;
+
+ // x0 = self^((p + 1) / 4)
+ var x0 = a1 * this;
+
+ // (1 + alpha)^((q - 1) // 2) * x0
+ var sqrt = (alpha + One).PowVartime(new ulong[] {
+ 0xdcff_7fff_ffff_d555,
+ 0x0f55_ffff_58a9_ffff,
+ 0xb398_6950_7b58_7b12,
+ 0xb23b_a5c2_79c2_895f,
+ 0x258d_d3db_21a5_d66b,
+ 0x0d00_88f5_1cbf_f34d,
+ }) * x0;
+
+ // In the event that alpha = -1, the element is order p - 1 and so
+ // we're just trying to get the square of an element of the subfield
+ // Fp. This is given by x0 * u, since u = sqrt(-1). Since the element
+ // x0 = a + bu has b = 0, the solution is therefore au.
+ sqrt = ConditionalSelect(in sqrt, new(-x0.C1, in x0.C0), alpha == -One);
+
+ sqrt = ConditionalSelect(in sqrt, in Zero, IsZero);
+
+ // Only return the result if it's really the square root (and so
+ // self is actually quadratic nonresidue)
+ if (sqrt.Square() != this) throw new ArithmeticException();
+ return sqrt;
+ }
+
+ public Fp2 Invert()
+ {
+ if (!TryInvert(out Fp2 result))
+ throw new DivideByZeroException();
+ return result;
+ }
+
+ public bool TryInvert(out Fp2 result)
+ {
+ // We wish to find the multiplicative inverse of a nonzero
+ // element a + bu in Fp2. We leverage an identity
+ //
+ // (a + bu)(a - bu) = a^2 + b^2
+ //
+ // which holds because u^2 = -1. This can be rewritten as
+ //
+ // (a + bu)(a - bu)/(a^2 + b^2) = 1
+ //
+ // because a^2 + b^2 = 0 has no nonzero solutions for (a, b).
+ // This gives that (a - bu)/(a^2 + b^2) is the inverse
+ // of (a + bu). Importantly, this can be computing using
+ // only a single inversion in Fp.
+
+ bool s = (C0.Square() + C1.Square()).TryInvert(out Fp t);
+ result = new Fp2(C0 * t, C1 * -t);
+ return s;
+ }
+
+ #region Instance math methods
+
+ public Fp2 Negate() => -this;
+ public Fp2 Multiply(in Fp2 value) => this * value;
+ public Fp2 Sum(in Fp2 value) => this + value;
+ public Fp2 Subtract(in Fp2 value) => this - value;
+
+ #endregion
+}
diff --git a/src/Neo.Cryptography.BLS12_381/Fp6.cs b/src/Neo.Cryptography.BLS12_381/Fp6.cs
new file mode 100644
index 0000000000..540bfc8a36
--- /dev/null
+++ b/src/Neo.Cryptography.BLS12_381/Fp6.cs
@@ -0,0 +1,308 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// Fp6.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+
+namespace Neo.Cryptography.BLS12_381;
+
+[StructLayout(LayoutKind.Explicit, Size = Size)]
+public readonly struct Fp6 : IEquatable, INumber
+{
+ [FieldOffset(0)]
+ public readonly Fp2 C0;
+ [FieldOffset(Fp2.Size)]
+ public readonly Fp2 C1;
+ [FieldOffset(Fp2.Size * 2)]
+ public readonly Fp2 C2;
+
+ public const int Size = Fp2.Size * 3;
+
+ private static readonly Fp6 _zero = new();
+ private static readonly Fp6 _one = new(in Fp2.One);
+
+ public static ref readonly Fp6 Zero => ref _zero;
+ public static ref readonly Fp6 One => ref _one;
+
+ public bool IsZero => C0.IsZero & C1.IsZero & C2.IsZero;
+
+ public Fp6(in Fp f)
+ : this(new Fp2(in f), in Fp2.Zero, in Fp2.Zero)
+ {
+ }
+
+ public Fp6(in Fp2 f)
+ : this(in f, in Fp2.Zero, in Fp2.Zero)
+ {
+ }
+
+ public Fp6(in Fp2 c0, in Fp2 c1, in Fp2 c2)
+ {
+ C0 = c0;
+ C1 = c1;
+ C2 = c2;
+ }
+
+ public static Fp6 FromBytes(ReadOnlySpan data)
+ {
+ if (data.Length != Size)
+ throw new FormatException($"The argument `{nameof(data)}` must contain {Size} bytes.");
+ Fp2 c0 = Fp2.FromBytes(data[(Fp2.Size * 2)..]);
+ Fp2 c1 = Fp2.FromBytes(data[Fp2.Size..(Fp2.Size * 2)]);
+ Fp2 c2 = Fp2.FromBytes(data[..Fp2.Size]);
+ return new(in c0, in c1, in c2);
+ }
+
+ public static bool operator ==(in Fp6 a, in Fp6 b)
+ {
+ return a.C0 == b.C0 & a.C1 == b.C1 & a.C2 == b.C2;
+ }
+
+ public static bool operator !=(in Fp6 a, in Fp6 b)
+ {
+ return !(a == b);
+ }
+
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ if (obj is not Fp6 other) return false;
+ return this == other;
+ }
+
+ public bool Equals(Fp6 other)
+ {
+ return this == other;
+ }
+
+ public override int GetHashCode()
+ {
+ return C0.GetHashCode() ^ C1.GetHashCode() ^ C2.GetHashCode();
+ }
+
+ public byte[] ToArray()
+ {
+ byte[] result = new byte[Size];
+ TryWrite(result);
+ return result;
+ }
+
+ public bool TryWrite(Span buffer)
+ {
+ if (buffer.Length < Size) return false;
+ C0.TryWrite(buffer[(Fp2.Size * 2)..Size]);
+ C1.TryWrite(buffer[Fp2.Size..(Fp2.Size * 2)]);
+ C2.TryWrite(buffer[0..Fp2.Size]);
+ return true;
+ }
+
+ public static Fp6 Random(RandomNumberGenerator rng)
+ {
+ return new(Fp2.Random(rng), Fp2.Random(rng), Fp2.Random(rng));
+ }
+
+ internal Fp6 MulBy_1(in Fp2 c1)
+ {
+ var b_b = C1 * c1;
+
+ var t1 = (C1 + C2) * c1 - b_b;
+ t1 = t1.MulByNonresidue();
+
+ var t2 = (C0 + C1) * c1 - b_b;
+
+ return new Fp6(in t1, in t2, in b_b);
+ }
+
+ internal Fp6 MulBy_01(in Fp2 c0, in Fp2 c1)
+ {
+ var a_a = C0 * c0;
+ var b_b = C1 * c1;
+
+ var t1 = (C1 + C2) * c1 - b_b;
+ t1 = t1.MulByNonresidue() + a_a;
+
+ var t2 = (c0 + c1) * (C0 + C1) - a_a - b_b;
+
+ var t3 = (C0 + C2) * c0 - a_a + b_b;
+
+ return new Fp6(in t1, in t2, in t3);
+ }
+
+ public Fp6 MulByNonresidue()
+ {
+ // Given a + bv + cv^2, this produces
+ // av + bv^2 + cv^3
+ // but because v^3 = u + 1, we have
+ // c(u + 1) + av + v^2
+
+ return new Fp6(C2.MulByNonresidue(), in C0, in C1);
+ }
+
+ public Fp6 FrobeniusMap()
+ {
+ var c0 = C0.FrobeniusMap();
+ var c1 = C1.FrobeniusMap();
+ var c2 = C2.FrobeniusMap();
+
+ // c1 = c1 * (u + 1)^((p - 1) / 3)
+ c1 *= new Fp2(in Fp.Zero, Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xcd03_c9e4_8671_f071,
+ 0x5dab_2246_1fcd_a5d2,
+ 0x5870_42af_d385_1b95,
+ 0x8eb6_0ebe_01ba_cb9e,
+ 0x03f9_7d6e_83d0_50d2,
+ 0x18f0_2065_5463_8741
+ }));
+
+ // c2 = c2 * (u + 1)^((2p - 2) / 3)
+ c2 *= new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x890d_c9e4_8675_45c3,
+ 0x2af3_2253_3285_a5d5,
+ 0x5088_0866_309b_7e2c,
+ 0xa20d_1b8c_7e88_1024,
+ 0x14e4_f04f_e2db_9068,
+ 0x14e5_6d3f_1564_853a
+ }), in Fp.Zero);
+
+ return new Fp6(c0, c1, c2);
+ }
+
+ public Fp6 Square()
+ {
+ var s0 = C0.Square();
+ var ab = C0 * C1;
+ var s1 = ab + ab;
+ var s2 = (C0 - C1 + C2).Square();
+ var bc = C1 * C2;
+ var s3 = bc + bc;
+ var s4 = C2.Square();
+
+ return new Fp6(
+ s3.MulByNonresidue() + s0,
+ s4.MulByNonresidue() + s1,
+ s1 + s2 + s3 - s0 - s4
+ );
+ }
+
+ public Fp6 Invert()
+ {
+ var c0 = (C1 * C2).MulByNonresidue();
+ c0 = C0.Square() - c0;
+
+ var c1 = C2.Square().MulByNonresidue();
+ c1 -= C0 * C1;
+
+ var c2 = C1.Square();
+ c2 -= C0 * C2;
+
+ var t = (C1 * c2 + C2 * c1).MulByNonresidue();
+ t += C0 * c0;
+
+ t = t.Invert();
+ return new Fp6(t * c0, t * c1, t * c2);
+ }
+
+ public static Fp6 operator -(in Fp6 a)
+ {
+ return new Fp6(-a.C0, -a.C1, -a.C2);
+ }
+
+ public static Fp6 operator +(in Fp6 a, in Fp6 b)
+ {
+ return new Fp6(a.C0 + b.C0, a.C1 + b.C1, a.C2 + b.C2);
+ }
+
+ public static Fp6 operator -(in Fp6 a, in Fp6 b)
+ {
+ return new Fp6(a.C0 - b.C0, a.C1 - b.C1, a.C2 - b.C2);
+ }
+
+ public static Fp6 operator *(in Fp6 a, in Fp6 b)
+ {
+ // The intuition for this algorithm is that we can look at F_p^6 as a direct
+ // extension of F_p^2, and express the overall operations down to the base field
+ // F_p instead of only over F_p^2. This enables us to interleave multiplications
+ // and reductions, ensuring that we don't require double-width intermediate
+ // representations (with around twice as many limbs as F_p elements).
+
+ // We want to express the multiplication c = a x b, where a = (a_0, a_1, a_2) is
+ // an element of F_p^6, and a_i = (a_i,0, a_i,1) is an element of F_p^2. The fully
+ // expanded multiplication is given by (2022-376 §5):
+ //
+ // c_0,0 = a_0,0 b_0,0 - a_0,1 b_0,1 + a_1,0 b_2,0 - a_1,1 b_2,1 + a_2,0 b_1,0 - a_2,1 b_1,1
+ // - a_1,0 b_2,1 - a_1,1 b_2,0 - a_2,0 b_1,1 - a_2,1 b_1,0.
+ // = a_0,0 b_0,0 - a_0,1 b_0,1 + a_1,0 (b_2,0 - b_2,1) - a_1,1 (b_2,0 + b_2,1)
+ // + a_2,0 (b_1,0 - b_1,1) - a_2,1 (b_1,0 + b_1,1).
+ //
+ // c_0,1 = a_0,0 b_0,1 + a_0,1 b_0,0 + a_1,0 b_2,1 + a_1,1 b_2,0 + a_2,0 b_1,1 + a_2,1 b_1,0
+ // + a_1,0 b_2,0 - a_1,1 b_2,1 + a_2,0 b_1,0 - a_2,1 b_1,1.
+ // = a_0,0 b_0,1 + a_0,1 b_0,0 + a_1,0(b_2,0 + b_2,1) + a_1,1(b_2,0 - b_2,1)
+ // + a_2,0(b_1,0 + b_1,1) + a_2,1(b_1,0 - b_1,1).
+ //
+ // c_1,0 = a_0,0 b_1,0 - a_0,1 b_1,1 + a_1,0 b_0,0 - a_1,1 b_0,1 + a_2,0 b_2,0 - a_2,1 b_2,1
+ // - a_2,0 b_2,1 - a_2,1 b_2,0.
+ // = a_0,0 b_1,0 - a_0,1 b_1,1 + a_1,0 b_0,0 - a_1,1 b_0,1 + a_2,0(b_2,0 - b_2,1)
+ // - a_2,1(b_2,0 + b_2,1).
+ //
+ // c_1,1 = a_0,0 b_1,1 + a_0,1 b_1,0 + a_1,0 b_0,1 + a_1,1 b_0,0 + a_2,0 b_2,1 + a_2,1 b_2,0
+ // + a_2,0 b_2,0 - a_2,1 b_2,1
+ // = a_0,0 b_1,1 + a_0,1 b_1,0 + a_1,0 b_0,1 + a_1,1 b_0,0 + a_2,0(b_2,0 + b_2,1)
+ // + a_2,1(b_2,0 - b_2,1).
+ //
+ // c_2,0 = a_0,0 b_2,0 - a_0,1 b_2,1 + a_1,0 b_1,0 - a_1,1 b_1,1 + a_2,0 b_0,0 - a_2,1 b_0,1.
+ // c_2,1 = a_0,0 b_2,1 + a_0,1 b_2,0 + a_1,0 b_1,1 + a_1,1 b_1,0 + a_2,0 b_0,1 + a_2,1 b_0,0.
+ //
+ // Each of these is a "sum of products", which we can compute efficiently.
+
+ var b10_p_b11 = b.C1.C0 + b.C1.C1;
+ var b10_m_b11 = b.C1.C0 - b.C1.C1;
+ var b20_p_b21 = b.C2.C0 + b.C2.C1;
+ var b20_m_b21 = b.C2.C0 - b.C2.C1;
+
+ return new Fp6(new Fp2(
+ Fp.SumOfProducts(
+ stackalloc[] { a.C0.C0, -a.C0.C1, a.C1.C0, -a.C1.C1, a.C2.C0, -a.C2.C1 },
+ stackalloc[] { b.C0.C0, b.C0.C1, b20_m_b21, b20_p_b21, b10_m_b11, b10_p_b11 }
+ ),
+ Fp.SumOfProducts(
+ stackalloc[] { a.C0.C0, a.C0.C1, a.C1.C0, a.C1.C1, a.C2.C0, a.C2.C1 },
+ stackalloc[] { b.C0.C1, b.C0.C0, b20_p_b21, b20_m_b21, b10_p_b11, b10_m_b11 }
+ )), new Fp2(
+ Fp.SumOfProducts(
+ stackalloc[] { a.C0.C0, -a.C0.C1, a.C1.C0, -a.C1.C1, a.C2.C0, -a.C2.C1 },
+ stackalloc[] { b.C1.C0, b.C1.C1, b.C0.C0, b.C0.C1, b20_m_b21, b20_p_b21 }
+ ),
+ Fp.SumOfProducts(
+ stackalloc[] { a.C0.C0, a.C0.C1, a.C1.C0, a.C1.C1, a.C2.C0, a.C2.C1 },
+ stackalloc[] { b.C1.C1, b.C1.C0, b.C0.C1, b.C0.C0, b20_p_b21, b20_m_b21 }
+ )), new Fp2(
+ Fp.SumOfProducts(
+ stackalloc[] { a.C0.C0, -a.C0.C1, a.C1.C0, -a.C1.C1, a.C2.C0, -a.C2.C1 },
+ stackalloc[] { b.C2.C0, b.C2.C1, b.C1.C0, b.C1.C1, b.C0.C0, b.C0.C1 }
+ ),
+ Fp.SumOfProducts(
+ stackalloc[] { a.C0.C0, a.C0.C1, a.C1.C0, a.C1.C1, a.C2.C0, a.C2.C1 },
+ stackalloc[] { b.C2.C1, b.C2.C0, b.C1.C1, b.C1.C0, b.C0.C1, b.C0.C0 }
+ ))
+ );
+ }
+
+ #region Instance math methods
+
+ public Fp6 Negate() => -this;
+ public Fp6 Multiply(in Fp6 value) => this * value;
+ public Fp6 Sum(in Fp6 value) => this + value;
+ public Fp6 Subtract(in Fp6 value) => this - value;
+
+ #endregion
+}
diff --git a/src/Neo.Cryptography.BLS12_381/FpConstants.cs b/src/Neo.Cryptography.BLS12_381/FpConstants.cs
new file mode 100644
index 0000000000..dd9f7ece9f
--- /dev/null
+++ b/src/Neo.Cryptography.BLS12_381/FpConstants.cs
@@ -0,0 +1,84 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// FpConstants.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+namespace Neo.Cryptography.BLS12_381;
+
+static class FpConstants
+{
+ // p = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787
+ public static readonly ulong[] MODULUS =
+ {
+ 0xb9fe_ffff_ffff_aaab,
+ 0x1eab_fffe_b153_ffff,
+ 0x6730_d2a0_f6b0_f624,
+ 0x6477_4b84_f385_12bf,
+ 0x4b1b_a7b6_434b_acd7,
+ 0x1a01_11ea_397f_e69a
+ };
+
+ // p - 2
+ public static readonly ulong[] P_2 =
+ {
+ 0xb9fe_ffff_ffff_aaa9,
+ 0x1eab_fffe_b153_ffff,
+ 0x6730_d2a0_f6b0_f624,
+ 0x6477_4b84_f385_12bf,
+ 0x4b1b_a7b6_434b_acd7,
+ 0x1a01_11ea_397f_e69a
+ };
+
+ // (p + 1) / 4
+ public static readonly ulong[] P_1_4 =
+ {
+ 0xee7f_bfff_ffff_eaab,
+ 0x07aa_ffff_ac54_ffff,
+ 0xd9cc_34a8_3dac_3d89,
+ 0xd91d_d2e1_3ce1_44af,
+ 0x92c6_e9ed_90d2_eb35,
+ 0x0680_447a_8e5f_f9a6
+ };
+
+ // INV = -(p^{-1} mod 2^64) mod 2^64
+ public const ulong INV = 0x89f3_fffc_fffc_fffd;
+
+ // R = 2^384 mod p
+ public static readonly Fp R = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x7609_0000_0002_fffd,
+ 0xebf4_000b_c40c_0002,
+ 0x5f48_9857_53c7_58ba,
+ 0x77ce_5853_7052_5745,
+ 0x5c07_1a97_a256_ec6d,
+ 0x15f6_5ec3_fa80_e493
+ });
+
+ // R2 = 2^(384*2) mod p
+ public static readonly Fp R2 = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xf4df_1f34_1c34_1746,
+ 0x0a76_e6a6_09d1_04f1,
+ 0x8de5_476c_4c95_b6d5,
+ 0x67eb_88a9_939d_83c0,
+ 0x9a79_3e85_b519_952d,
+ 0x1198_8fe5_92ca_e3aa
+ });
+
+ // R3 = 2^(384*3) mod p
+ public static readonly Fp R3 = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xed48_ac6b_d94c_a1e0,
+ 0x315f_831e_03a7_adf8,
+ 0x9a53_352a_615e_29dd,
+ 0x34c0_4e5e_921e_1761,
+ 0x2512_d435_6572_4728,
+ 0x0aa6_3460_9175_5d4d
+ });
+}
diff --git a/src/Neo.Cryptography.BLS12_381/G1Affine.cs b/src/Neo.Cryptography.BLS12_381/G1Affine.cs
new file mode 100644
index 0000000000..cbed66bc8f
--- /dev/null
+++ b/src/Neo.Cryptography.BLS12_381/G1Affine.cs
@@ -0,0 +1,181 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// G1Affine.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using System.Diagnostics.CodeAnalysis;
+using static Neo.Cryptography.BLS12_381.ConstantTimeUtility;
+using static Neo.Cryptography.BLS12_381.G1Constants;
+
+namespace Neo.Cryptography.BLS12_381;
+
+public readonly struct G1Affine : IEquatable
+{
+ public readonly Fp X;
+ public readonly Fp Y;
+ public readonly bool Infinity;
+
+ public static readonly G1Affine Identity = new(in Fp.Zero, in Fp.One, true);
+ public static readonly G1Affine Generator = new(in GeneratorX, in GeneratorY, false);
+
+ public bool IsIdentity => Infinity;
+ public bool IsTorsionFree => -new G1Projective(this).MulByX().MulByX() == new G1Projective(Endomorphism());
+ public bool IsOnCurve => ((Y.Square() - (X.Square() * X)) == B) | Infinity;
+
+ public G1Affine(in Fp x, in Fp y)
+ : this(in x, in y, false)
+ {
+ }
+
+ private G1Affine(in Fp x, in Fp y, bool infinity)
+ {
+ X = x;
+ Y = y;
+ Infinity = infinity;
+ }
+
+ public G1Affine(in G1Projective p)
+ {
+ bool s = p.Z.TryInvert(out Fp zinv);
+
+ zinv = ConditionalSelect(in Fp.Zero, in zinv, s);
+ Fp x = p.X * zinv;
+ Fp y = p.Y * zinv;
+
+ G1Affine tmp = new(in x, in y, false);
+ this = ConditionalSelect(in tmp, in Identity, !s);
+ }
+
+ public static G1Affine FromUncompressed(ReadOnlySpan data)
+ {
+ return FromBytes(data, false, true);
+ }
+
+ public static G1Affine FromCompressed(ReadOnlySpan data)
+ {
+ return FromBytes(data, true, true);
+ }
+
+ private static G1Affine FromBytes(ReadOnlySpan data, bool compressed, bool check)
+ {
+ bool compression_flag_set = (data[0] & 0x80) != 0;
+ bool infinity_flag_set = (data[0] & 0x40) != 0;
+ bool sort_flag_set = (data[0] & 0x20) != 0;
+ byte[] tmp = data[0..48].ToArray();
+ tmp[0] &= 0b0001_1111;
+ Fp x = Fp.FromBytes(tmp);
+ if (compressed)
+ {
+ Fp y = ((x.Square() * x) + B).Sqrt();
+ y = ConditionalSelect(in y, -y, y.LexicographicallyLargest() ^ sort_flag_set);
+ G1Affine result = new(in x, in y, infinity_flag_set);
+ result = ConditionalSelect(in result, in Identity, infinity_flag_set);
+ if (check)
+ {
+ bool _checked = (!infinity_flag_set | (infinity_flag_set & !sort_flag_set & x.IsZero))
+ & compression_flag_set;
+ _checked &= result.IsTorsionFree;
+ if (!_checked) throw new FormatException();
+ }
+ return result;
+ }
+ else
+ {
+ Fp y = Fp.FromBytes(data[48..96]);
+ G1Affine result = ConditionalSelect(new(in x, in y, infinity_flag_set), in Identity, infinity_flag_set);
+ if (check)
+ {
+ bool _checked = (!infinity_flag_set | (infinity_flag_set & x.IsZero & y.IsZero))
+ & !compression_flag_set
+ & !sort_flag_set;
+ _checked &= result.IsOnCurve & result.IsTorsionFree;
+ if (!_checked) throw new FormatException();
+ }
+ return result;
+ }
+ }
+
+ public static bool operator ==(in G1Affine a, in G1Affine b)
+ {
+ return (a.Infinity & b.Infinity) | (!a.Infinity & !b.Infinity & a.X == b.X & a.Y == b.Y);
+ }
+
+ public static bool operator !=(in G1Affine a, in G1Affine b)
+ {
+ return !(a == b);
+ }
+
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ if (obj is not G1Affine other) return false;
+ return this == other;
+ }
+
+ public bool Equals(G1Affine other)
+ {
+ return this == other;
+ }
+
+ public override int GetHashCode()
+ {
+ if (Infinity) return Infinity.GetHashCode();
+ return X.GetHashCode() ^ Y.GetHashCode();
+ }
+
+ public static G1Affine operator -(in G1Affine p)
+ {
+ return new G1Affine(in p.X, ConditionalSelect(-p.Y, in Fp.One, p.Infinity), p.Infinity);
+ }
+
+ public byte[] ToCompressed()
+ {
+ byte[] res = ConditionalSelect(in X, in Fp.Zero, Infinity).ToArray();
+
+ // This point is in compressed form, so we set the most significant bit.
+ res[0] |= 0x80;
+
+ // Is this point at infinity? If so, set the second-most significant bit.
+ res[0] |= ConditionalSelect((byte)0, (byte)0x40, Infinity);
+
+ // Is the y-coordinate the lexicographically largest of the two associated with the
+ // x-coordinate? If so, set the third-most significant bit so long as this is not
+ // the point at infinity.
+ res[0] |= ConditionalSelect((byte)0, (byte)0x20, !Infinity & Y.LexicographicallyLargest());
+
+ return res;
+ }
+
+ public byte[] ToUncompressed()
+ {
+ byte[] res = new byte[96];
+
+ ConditionalSelect(in X, in Fp.Zero, Infinity).TryWrite(res.AsSpan(0..48));
+ ConditionalSelect(in Y, in Fp.Zero, Infinity).TryWrite(res.AsSpan(48..96));
+
+ // Is this point at infinity? If so, set the second-most significant bit.
+ res[0] |= ConditionalSelect((byte)0, (byte)0x40, Infinity);
+
+ return res;
+ }
+
+ public G1Projective ToCurve()
+ {
+ return new(this);
+ }
+
+ private G1Affine Endomorphism()
+ {
+ return new(X * BETA, in Y, Infinity);
+ }
+
+ public static G1Projective operator *(in G1Affine a, in Scalar b)
+ {
+ return new G1Projective(in a) * b.ToArray();
+ }
+}
diff --git a/src/Neo.Cryptography.BLS12_381/G1Constants.cs b/src/Neo.Cryptography.BLS12_381/G1Constants.cs
new file mode 100644
index 0000000000..1c07bb0900
--- /dev/null
+++ b/src/Neo.Cryptography.BLS12_381/G1Constants.cs
@@ -0,0 +1,55 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// G1Constants.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+namespace Neo.Cryptography.BLS12_381;
+
+static class G1Constants
+{
+ public static readonly Fp GeneratorX = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x5cb3_8790_fd53_0c16,
+ 0x7817_fc67_9976_fff5,
+ 0x154f_95c7_143b_a1c1,
+ 0xf0ae_6acd_f3d0_e747,
+ 0xedce_6ecc_21db_f440,
+ 0x1201_7741_9e0b_fb75
+ });
+
+ public static readonly Fp GeneratorY = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xbaac_93d5_0ce7_2271,
+ 0x8c22_631a_7918_fd8e,
+ 0xdd59_5f13_5707_25ce,
+ 0x51ac_5829_5040_5194,
+ 0x0e1c_8c3f_ad00_59c0,
+ 0x0bbc_3efc_5008_a26a
+ });
+
+ public static readonly Fp B = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xaa27_0000_000c_fff3,
+ 0x53cc_0032_fc34_000a,
+ 0x478f_e97a_6b0a_807f,
+ 0xb1d3_7ebe_e6ba_24d7,
+ 0x8ec9_733b_bf78_ab2f,
+ 0x09d6_4551_3d83_de7e
+ });
+
+ public static readonly Fp BETA = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x30f1_361b_798a_64e8,
+ 0xf3b8_ddab_7ece_5a2a,
+ 0x16a8_ca3a_c615_77f7,
+ 0xc26a_2ff8_74fd_029b,
+ 0x3636_b766_6070_1c6e,
+ 0x051b_a4ab_241b_6160
+ });
+}
diff --git a/src/Neo.Cryptography.BLS12_381/G1Projective.cs b/src/Neo.Cryptography.BLS12_381/G1Projective.cs
new file mode 100644
index 0000000000..95600af135
--- /dev/null
+++ b/src/Neo.Cryptography.BLS12_381/G1Projective.cs
@@ -0,0 +1,294 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// G1Projective.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+using static Neo.Cryptography.BLS12_381.Constants;
+using static Neo.Cryptography.BLS12_381.ConstantTimeUtility;
+using static Neo.Cryptography.BLS12_381.G1Constants;
+
+namespace Neo.Cryptography.BLS12_381;
+
+[StructLayout(LayoutKind.Explicit, Size = Fp.Size * 3)]
+public readonly struct G1Projective : IEquatable
+{
+ [FieldOffset(0)]
+ public readonly Fp X;
+ [FieldOffset(Fp.Size)]
+ public readonly Fp Y;
+ [FieldOffset(Fp.Size * 2)]
+ public readonly Fp Z;
+
+ public static readonly G1Projective Identity = new(in Fp.Zero, in Fp.One, in Fp.Zero);
+ public static readonly G1Projective Generator = new(in GeneratorX, in GeneratorY, in Fp.One);
+
+ public bool IsIdentity => Z.IsZero;
+ public bool IsOnCurve => ((Y.Square() * Z) == (X.Square() * X + Z.Square() * Z * B)) | Z.IsZero;
+
+ public G1Projective(in Fp x, in Fp y, in Fp z)
+ {
+ X = x;
+ Y = y;
+ Z = z;
+ }
+
+ public G1Projective(in G1Affine p)
+ : this(in p.X, in p.Y, ConditionalSelect(in Fp.One, in Fp.Zero, p.Infinity))
+ {
+ }
+
+ public static bool operator ==(in G1Projective a, in G1Projective b)
+ {
+ // Is (xz, yz, z) equal to (x'z', y'z', z') when converted to affine?
+
+ Fp x1 = a.X * b.Z;
+ Fp x2 = b.X * a.Z;
+
+ Fp y1 = a.Y * b.Z;
+ Fp y2 = b.Y * a.Z;
+
+ bool self_is_zero = a.Z.IsZero;
+ bool other_is_zero = b.Z.IsZero;
+
+ // Both point at infinity. Or neither point at infinity, coordinates are the same.
+ return (self_is_zero & other_is_zero) | ((!self_is_zero) & (!other_is_zero) & x1 == x2 & y1 == y2);
+ }
+
+ public static bool operator !=(in G1Projective a, in G1Projective b)
+ {
+ return !(a == b);
+ }
+
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ if (obj is not G1Projective other) return false;
+ return this == other;
+ }
+
+ public bool Equals(G1Projective other)
+ {
+ return this == other;
+ }
+
+ public override int GetHashCode()
+ {
+ return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode();
+ }
+
+ public static G1Projective operator -(in G1Projective p)
+ {
+ return new G1Projective(in p.X, -p.Y, in p.Z);
+ }
+
+ private static Fp MulBy3B(in Fp a)
+ {
+ Fp b = a + a;
+ b += b;
+ return b + b + b;
+ }
+
+ public G1Projective Double()
+ {
+ Fp t0 = Y.Square();
+ Fp z3 = t0 + t0;
+ z3 += z3;
+ z3 += z3;
+ Fp t1 = Y * Z;
+ Fp t2 = Z.Square();
+ t2 = MulBy3B(in t2);
+ Fp x3 = t2 * z3;
+ Fp y3 = t0 + t2;
+ z3 = t1 * z3;
+ t1 = t2 + t2;
+ t2 = t1 + t2;
+ t0 -= t2;
+ y3 = t0 * y3;
+ y3 = x3 + y3;
+ t1 = X * Y;
+ x3 = t0 * t1;
+ x3 += x3;
+
+ G1Projective tmp = new(in x3, in y3, in z3);
+ return ConditionalSelect(in tmp, in Identity, IsIdentity);
+ }
+
+ public static G1Projective operator +(in G1Projective a, in G1Projective b)
+ {
+ Fp t0 = a.X * b.X;
+ Fp t1 = a.Y * b.Y;
+ Fp t2 = a.Z * b.Z;
+ Fp t3 = a.X + a.Y;
+ Fp t4 = b.X + b.Y;
+ t3 *= t4;
+ t4 = t0 + t1;
+ t3 -= t4;
+ t4 = a.Y + a.Z;
+ Fp x3 = b.Y + b.Z;
+ t4 *= x3;
+ x3 = t1 + t2;
+ t4 -= x3;
+ x3 = a.X + a.Z;
+ Fp y3 = b.X + b.Z;
+ x3 *= y3;
+ y3 = t0 + t2;
+ y3 = x3 - y3;
+ x3 = t0 + t0;
+ t0 = x3 + t0;
+ t2 = MulBy3B(in t2);
+ Fp z3 = t1 + t2;
+ t1 -= t2;
+ y3 = MulBy3B(in y3);
+ x3 = t4 * y3;
+ t2 = t3 * t1;
+ x3 = t2 - x3;
+ y3 *= t0;
+ t1 *= z3;
+ y3 = t1 + y3;
+ t0 *= t3;
+ z3 *= t4;
+ z3 += t0;
+
+ return new G1Projective(in x3, in y3, in z3);
+ }
+
+ public static G1Projective operator +(in G1Projective a, in G1Affine b)
+ {
+ Fp t0 = a.X * b.X;
+ Fp t1 = a.Y * b.Y;
+ Fp t3 = b.X + b.Y;
+ Fp t4 = a.X + a.Y;
+ t3 *= t4;
+ t4 = t0 + t1;
+ t3 -= t4;
+ t4 = b.Y * a.Z;
+ t4 += a.Y;
+ Fp y3 = b.X * a.Z;
+ y3 += a.X;
+ Fp x3 = t0 + t0;
+ t0 = x3 + t0;
+ Fp t2 = MulBy3B(in a.Z);
+ Fp z3 = t1 + t2;
+ t1 -= t2;
+ y3 = MulBy3B(in y3);
+ x3 = t4 * y3;
+ t2 = t3 * t1;
+ x3 = t2 - x3;
+ y3 *= t0;
+ t1 *= z3;
+ y3 = t1 + y3;
+ t0 *= t3;
+ z3 *= t4;
+ z3 += t0;
+
+ G1Projective tmp = new(in x3, in y3, in z3);
+ return ConditionalSelect(in tmp, in a, b.IsIdentity);
+ }
+
+ public static G1Projective operator +(in G1Affine a, in G1Projective b)
+ {
+ return b + a;
+ }
+
+ public static G1Projective operator -(in G1Projective a, in G1Projective b)
+ {
+ return a + -b;
+ }
+
+ public static G1Projective operator -(in G1Projective a, in G1Affine b)
+ {
+ return a + -b;
+ }
+
+ public static G1Projective operator -(in G1Affine a, in G1Projective b)
+ {
+ return -b + a;
+ }
+
+ public static G1Projective operator *(in G1Projective a, byte[] b)
+ {
+ int length = b.Length;
+ if (length != 32)
+ throw new ArgumentException($"The argument {nameof(b)} must be 32 bytes.");
+
+ G1Projective acc = Identity;
+
+ foreach (bool bit in b
+ .SelectMany(p => Enumerable.Range(0, 8).Select(q => ((p >> q) & 1) == 1))
+ .Reverse()
+ .Skip(1))
+ {
+ acc = acc.Double();
+ acc = ConditionalSelect(in acc, acc + a, bit);
+ }
+
+ return acc;
+ }
+
+ public static G1Projective operator *(in G1Projective a, in Scalar b)
+ {
+ return a * b.ToArray();
+ }
+
+ internal G1Projective MulByX()
+ {
+ G1Projective xself = Identity;
+
+ ulong x = BLS_X >> 1;
+ G1Projective tmp = this;
+ while (x > 0)
+ {
+ tmp = tmp.Double();
+
+ if (x % 2 == 1)
+ {
+ xself += tmp;
+ }
+ x >>= 1;
+ }
+
+ if (BLS_X_IS_NEGATIVE)
+ {
+ xself = -xself;
+ }
+ return xself;
+ }
+
+ public G1Projective ClearCofactor()
+ {
+ return this - MulByX();
+ }
+
+ public static void BatchNormalize(ReadOnlySpan p, Span q)
+ {
+ int length = p.Length;
+ if (length != q.Length)
+ throw new ArgumentException($"{nameof(p)} and {nameof(q)} must have the same length.");
+
+ Span x = stackalloc Fp[length];
+ Fp acc = Fp.One;
+ for (int i = 0; i < length; i++)
+ {
+ x[i] = acc;
+ acc = ConditionalSelect(acc * p[i].Z, in acc, p[i].IsIdentity);
+ }
+
+ acc = acc.Invert();
+
+ for (int i = length - 1; i >= 0; i--)
+ {
+ bool skip = p[i].IsIdentity;
+ Fp tmp = x[i] * acc;
+ acc = ConditionalSelect(acc * p[i].Z, in acc, skip);
+ G1Affine qi = new(p[i].X * tmp, p[i].Y * tmp);
+ q[i] = ConditionalSelect(in qi, in G1Affine.Identity, skip);
+ }
+ }
+}
diff --git a/src/Neo.Cryptography.BLS12_381/G2Affine.cs b/src/Neo.Cryptography.BLS12_381/G2Affine.cs
new file mode 100644
index 0000000000..97256d02c7
--- /dev/null
+++ b/src/Neo.Cryptography.BLS12_381/G2Affine.cs
@@ -0,0 +1,225 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// G2Affine.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using System.Diagnostics.CodeAnalysis;
+using static Neo.Cryptography.BLS12_381.ConstantTimeUtility;
+using static Neo.Cryptography.BLS12_381.G2Constants;
+
+namespace Neo.Cryptography.BLS12_381;
+
+public readonly struct G2Affine : IEquatable
+{
+ public readonly Fp2 X;
+ public readonly Fp2 Y;
+ public readonly bool Infinity;
+
+ public static readonly G2Affine Identity = new(in Fp2.Zero, in Fp2.One, true);
+ public static readonly G2Affine Generator = new(in GeneratorX, in GeneratorY, false);
+
+ public bool IsIdentity => Infinity;
+ public bool IsTorsionFree
+ {
+ get
+ {
+ // Algorithm from Section 4 of https://eprint.iacr.org/2021/1130
+ // Updated proof of correctness in https://eprint.iacr.org/2022/352
+ //
+ // Check that psi(P) == [x] P
+ var p = new G2Projective(this);
+ return p.Psi() == p.MulByX();
+ }
+ }
+ public bool IsOnCurve => ((Y.Square() - X.Square() * X) == B) | Infinity; // y^2 - x^3 ?= 4(u + 1)
+
+ public G2Affine(in Fp2 x, in Fp2 y)
+ : this(in x, in y, false)
+ {
+ }
+
+ private G2Affine(in Fp2 x, in Fp2 y, bool infinity)
+ {
+ X = x;
+ Y = y;
+ Infinity = infinity;
+ }
+
+ public G2Affine(in G2Projective p)
+ {
+ bool s = p.Z.TryInvert(out Fp2 zinv);
+
+ zinv = ConditionalSelect(in Fp2.Zero, in zinv, s);
+ Fp2 x = p.X * zinv;
+ Fp2 y = p.Y * zinv;
+
+ G2Affine tmp = new(in x, in y, false);
+ this = ConditionalSelect(in tmp, in Identity, !s);
+ }
+
+ public static bool operator ==(in G2Affine a, in G2Affine b)
+ {
+ // The only cases in which two points are equal are
+ // 1. infinity is set on both
+ // 2. infinity is not set on both, and their coordinates are equal
+
+ return (a.Infinity & b.Infinity) | (!a.Infinity & !b.Infinity & a.X == b.X & a.Y == b.Y);
+ }
+
+ public static bool operator !=(in G2Affine a, in G2Affine b)
+ {
+ return !(a == b);
+ }
+
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ if (obj is not G2Affine other) return false;
+ return this == other;
+ }
+
+ public bool Equals(G2Affine other)
+ {
+ return this == other;
+ }
+
+ public override int GetHashCode()
+ {
+ if (Infinity) return Infinity.GetHashCode();
+ return X.GetHashCode() ^ Y.GetHashCode();
+ }
+
+ public static G2Affine operator -(in G2Affine a)
+ {
+ return new G2Affine(
+ in a.X,
+ ConditionalSelect(-a.Y, in Fp2.One, a.Infinity),
+ a.Infinity
+ );
+ }
+
+ public static G2Projective operator *(in G2Affine a, in Scalar b)
+ {
+ return new G2Projective(a) * b.ToArray();
+ }
+
+ public byte[] ToCompressed()
+ {
+ // Strictly speaking, self.x is zero already when self.infinity is true, but
+ // to guard against implementation mistakes we do not assume this.
+ var x = ConditionalSelect(in X, in Fp2.Zero, Infinity);
+
+ var res = new byte[96];
+
+ x.C1.TryWrite(res.AsSpan(0..48));
+ x.C0.TryWrite(res.AsSpan(48..96));
+
+ // This point is in compressed form, so we set the most significant bit.
+ res[0] |= 0x80;
+
+ // Is this point at infinity? If so, set the second-most significant bit.
+ res[0] |= ConditionalSelect((byte)0, (byte)0x40, Infinity);
+
+ // Is the y-coordinate the lexicographically largest of the two associated with the
+ // x-coordinate? If so, set the third-most significant bit so long as this is not
+ // the point at infinity.
+ res[0] |= ConditionalSelect((byte)0, (byte)0x20, !Infinity & Y.LexicographicallyLargest());
+
+ return res;
+ }
+
+ public byte[] ToUncompressed()
+ {
+ var res = new byte[192];
+
+ var x = ConditionalSelect(in X, in Fp2.Zero, Infinity);
+ var y = ConditionalSelect(in Y, in Fp2.Zero, Infinity);
+
+ x.C1.TryWrite(res.AsSpan(0..48));
+ x.C0.TryWrite(res.AsSpan(48..96));
+ y.C1.TryWrite(res.AsSpan(96..144));
+ y.C0.TryWrite(res.AsSpan(144..192));
+
+ // Is this point at infinity? If so, set the second-most significant bit.
+ res[0] |= ConditionalSelect((byte)0, (byte)0x40, Infinity);
+
+ return res;
+ }
+
+ public static G2Affine FromUncompressed(ReadOnlySpan bytes)
+ {
+ return FromBytes(bytes, false, true);
+ }
+
+ public static G2Affine FromCompressed(ReadOnlySpan bytes)
+ {
+ return FromBytes(bytes, true, true);
+ }
+
+ private static G2Affine FromBytes(ReadOnlySpan bytes, bool compressed, bool check)
+ {
+ // Obtain the three flags from the start of the byte sequence
+ bool compression_flag_set = (bytes[0] & 0x80) != 0;
+ bool infinity_flag_set = (bytes[0] & 0x40) != 0;
+ bool sort_flag_set = (bytes[0] & 0x20) != 0;
+
+ // Attempt to obtain the x-coordinate
+ var tmp = bytes[0..48].ToArray();
+ tmp[0] &= 0b0001_1111;
+ var xc1 = Fp.FromBytes(tmp);
+ var xc0 = Fp.FromBytes(bytes[48..96]);
+ var x = new Fp2(in xc0, in xc1);
+
+ if (compressed)
+ {
+ // Recover a y-coordinate given x by y = sqrt(x^3 + 4)
+ var y = ((x.Square() * x) + B).Sqrt();
+ y = ConditionalSelect(in y, -y, y.LexicographicallyLargest() ^ sort_flag_set);
+ G2Affine result = new(in x, in y, infinity_flag_set);
+ result = ConditionalSelect(in result, in Identity, infinity_flag_set);
+ if (check)
+ {
+ bool _checked = (!infinity_flag_set | (infinity_flag_set & !sort_flag_set & x.IsZero))
+ & compression_flag_set;
+ _checked &= result.IsTorsionFree;
+ if (!_checked) throw new FormatException();
+ }
+ return result;
+ }
+ else
+ {
+ // Attempt to obtain the y-coordinate
+ var yc1 = Fp.FromBytes(bytes[96..144]);
+ var yc0 = Fp.FromBytes(bytes[144..192]);
+ var y = new Fp2(in yc0, in yc1);
+
+ // Create a point representing this value
+ var p = ConditionalSelect(new G2Affine(in x, in y, infinity_flag_set), in Identity, infinity_flag_set);
+
+ if (check)
+ {
+ bool _checked =
+ // If the infinity flag is set, the x and y coordinates should have been zero.
+ ((!infinity_flag_set) | (infinity_flag_set & x.IsZero & y.IsZero)) &
+ // The compression flag should not have been set, as this is an uncompressed element
+ (!compression_flag_set) &
+ // The sort flag should not have been set, as this is an uncompressed element
+ (!sort_flag_set);
+ _checked &= p.IsOnCurve & p.IsTorsionFree;
+ if (!_checked) throw new FormatException();
+ }
+
+ return p;
+ }
+ }
+
+ public G2Projective ToCurve()
+ {
+ return new(this);
+ }
+}
diff --git a/src/Neo.Cryptography.BLS12_381/G2Constants.cs b/src/Neo.Cryptography.BLS12_381/G2Constants.cs
new file mode 100644
index 0000000000..045ad43a93
--- /dev/null
+++ b/src/Neo.Cryptography.BLS12_381/G2Constants.cs
@@ -0,0 +1,112 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// G2Constants.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+namespace Neo.Cryptography.BLS12_381;
+
+static class G2Constants
+{
+ public static readonly Fp2 GeneratorX = new(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xf5f2_8fa2_0294_0a10,
+ 0xb3f5_fb26_87b4_961a,
+ 0xa1a8_93b5_3e2a_e580,
+ 0x9894_999d_1a3c_aee9,
+ 0x6f67_b763_1863_366b,
+ 0x0581_9192_4350_bcd7
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xa5a9_c075_9e23_f606,
+ 0xaaa0_c59d_bccd_60c3,
+ 0x3bb1_7e18_e286_7806,
+ 0x1b1a_b6cc_8541_b367,
+ 0xc2b6_ed0e_f215_8547,
+ 0x1192_2a09_7360_edf3
+ }));
+
+ public static readonly Fp2 GeneratorY = new(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x4c73_0af8_6049_4c4a,
+ 0x597c_fa1f_5e36_9c5a,
+ 0xe7e6_856c_aa0a_635a,
+ 0xbbef_b5e9_6e0d_495f,
+ 0x07d3_a975_f0ef_25a2,
+ 0x0083_fd8e_7e80_dae5
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xadc0_fc92_df64_b05d,
+ 0x18aa_270a_2b14_61dc,
+ 0x86ad_ac6a_3be4_eba0,
+ 0x7949_5c4e_c93d_a33a,
+ 0xe717_5850_a43c_caed,
+ 0x0b2b_c2a1_63de_1bf2
+ }));
+
+ public static readonly Fp2 B = new(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xaa27_0000_000c_fff3,
+ 0x53cc_0032_fc34_000a,
+ 0x478f_e97a_6b0a_807f,
+ 0xb1d3_7ebe_e6ba_24d7,
+ 0x8ec9_733b_bf78_ab2f,
+ 0x09d6_4551_3d83_de7e
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xaa27_0000_000c_fff3,
+ 0x53cc_0032_fc34_000a,
+ 0x478f_e97a_6b0a_807f,
+ 0xb1d3_7ebe_e6ba_24d7,
+ 0x8ec9_733b_bf78_ab2f,
+ 0x09d6_4551_3d83_de7e
+ }));
+
+ public static readonly Fp2 B3 = B + B + B;
+
+ // 1 / ((u+1) ^ ((q-1)/3))
+ public static readonly Fp2 PsiCoeffX = new(in Fp.Zero, Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x890dc9e4867545c3,
+ 0x2af322533285a5d5,
+ 0x50880866309b7e2c,
+ 0xa20d1b8c7e881024,
+ 0x14e4f04fe2db9068,
+ 0x14e56d3f1564853a
+ }));
+
+ // 1 / ((u+1) ^ (p-1)/2)
+ public static readonly Fp2 PsiCoeffY = new(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x3e2f585da55c9ad1,
+ 0x4294213d86c18183,
+ 0x382844c88b623732,
+ 0x92ad2afd19103e18,
+ 0x1d794e4fac7cf0b9,
+ 0x0bd592fc7d825ec8
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x7bcfa7a25aa30fda,
+ 0xdc17dec12a927e7c,
+ 0x2f088dd86b4ebef1,
+ 0xd1ca2087da74d4a7,
+ 0x2da2596696cebc1d,
+ 0x0e2b7eedbbfd87d2
+ }));
+
+ // 1 / 2 ^ ((q-1)/3)
+ public static readonly Fp2 Psi2CoeffX = new(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xcd03c9e48671f071,
+ 0x5dab22461fcda5d2,
+ 0x587042afd3851b95,
+ 0x8eb60ebe01bacb9e,
+ 0x03f97d6e83d050d2,
+ 0x18f0206554638741
+ }), in Fp.Zero);
+}
diff --git a/src/Neo.Cryptography.BLS12_381/G2Prepared.Adder.cs b/src/Neo.Cryptography.BLS12_381/G2Prepared.Adder.cs
new file mode 100644
index 0000000000..c28b8889cb
--- /dev/null
+++ b/src/Neo.Cryptography.BLS12_381/G2Prepared.Adder.cs
@@ -0,0 +1,74 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// G2Prepared.Adder.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using System.Runtime.CompilerServices;
+using static Neo.Cryptography.BLS12_381.MillerLoopUtility;
+
+namespace Neo.Cryptography.BLS12_381;
+
+partial class G2Prepared
+{
+ class Adder : IMillerLoopDriver
+
+
+
+
diff --git a/src/Neo.Json/JArray.cs b/src/Neo.Json/JArray.cs
index 30ac7c83a2..903f29941b 100644
--- a/src/Neo.Json/JArray.cs
+++ b/src/Neo.Json/JArray.cs
@@ -123,7 +123,7 @@ internal override void Write(Utf8JsonWriter writer)
writer.WriteEndArray();
}
- public override JArray Clone()
+ public override JToken Clone()
{
var cloned = new JArray();
diff --git a/src/Neo.Json/JBoolean.cs b/src/Neo.Json/JBoolean.cs
index 0ca4901a86..05cb36a89d 100644
--- a/src/Neo.Json/JBoolean.cs
+++ b/src/Neo.Json/JBoolean.cs
@@ -63,7 +63,7 @@ internal override void Write(Utf8JsonWriter writer)
writer.WriteBooleanValue(Value);
}
- public override JBoolean Clone()
+ public override JToken Clone()
{
return this;
}
diff --git a/src/Neo.Json/JNumber.cs b/src/Neo.Json/JNumber.cs
index 6c73a01b37..479303cfb5 100644
--- a/src/Neo.Json/JNumber.cs
+++ b/src/Neo.Json/JNumber.cs
@@ -109,7 +109,7 @@ internal override void Write(Utf8JsonWriter writer)
writer.WriteNumberValue(Value);
}
- public override JNumber Clone()
+ public override JToken Clone()
{
return this;
}
diff --git a/src/Neo.Json/JObject.cs b/src/Neo.Json/JObject.cs
index adc6755b5e..c5666e9112 100644
--- a/src/Neo.Json/JObject.cs
+++ b/src/Neo.Json/JObject.cs
@@ -76,7 +76,7 @@ internal override void Write(Utf8JsonWriter writer)
/// Creates a copy of the current JSON object.
///
/// A copy of the current JSON object.
- public override JObject Clone()
+ public override JToken Clone()
{
var cloned = new JObject();
diff --git a/src/Neo.Json/JString.cs b/src/Neo.Json/JString.cs
index f6c5d155f3..c9466c26a6 100644
--- a/src/Neo.Json/JString.cs
+++ b/src/Neo.Json/JString.cs
@@ -70,7 +70,7 @@ public override T AsEnum(T defaultValue = default, bool ignoreCase = false)
public override T GetEnum(bool ignoreCase = false)
{
T result = Enum.Parse(Value, ignoreCase);
- if (!Enum.IsDefined(result)) throw new InvalidCastException();
+ if (!Enum.IsDefined(typeof(T), result)) throw new InvalidCastException();
return result;
}
@@ -79,7 +79,7 @@ internal override void Write(Utf8JsonWriter writer)
writer.WriteStringValue(Value);
}
- public override JString Clone()
+ public override JToken Clone()
{
return this;
}
diff --git a/src/Neo.Json/Neo.Json.csproj b/src/Neo.Json/Neo.Json.csproj
index 94943d4402..d2ce18e430 100644
--- a/src/Neo.Json/Neo.Json.csproj
+++ b/src/Neo.Json/Neo.Json.csproj
@@ -1,9 +1,14 @@
-
-
-
- enable
- enable
- NEO;JSON
-
-
-
+
+
+
+ netstandard2.1;net7.0
+ enable
+ enable
+ NEO;JSON
+
+
+
+
+
+
+
diff --git a/src/Neo.Json/OrderedDictionary.cs b/src/Neo.Json/OrderedDictionary.cs
index 244121f304..0913bda44e 100644
--- a/src/Neo.Json/OrderedDictionary.cs
+++ b/src/Neo.Json/OrderedDictionary.cs
@@ -90,6 +90,8 @@ public bool Remove(TKey key)
return collection.Remove(key);
}
+#pragma warning disable CS8767
+
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
if (collection.TryGetValue(key, out var entry))
@@ -101,6 +103,8 @@ public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
return false;
}
+#pragma warning restore CS8767
+
void ICollection>.Add(KeyValuePair item)
{
Add(item.Key, item.Value);
diff --git a/src/Neo.VM/Neo.VM.csproj b/src/Neo.VM/Neo.VM.csproj
index add3762a3e..5e7e071b22 100644
--- a/src/Neo.VM/Neo.VM.csproj
+++ b/src/Neo.VM/Neo.VM.csproj
@@ -2,7 +2,6 @@
netstandard2.1;net7.0
- 9.0
true
enable
diff --git a/src/Neo/ContainsTransactionType.cs b/src/Neo/ContainsTransactionType.cs
new file mode 100644
index 0000000000..cb91eb303f
--- /dev/null
+++ b/src/Neo/ContainsTransactionType.cs
@@ -0,0 +1,20 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// ContainsTransactionType.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+namespace Neo
+{
+ public enum ContainsTransactionType
+ {
+ NotExist,
+ ExistsInPool,
+ ExistsInLedger
+ }
+}
diff --git a/src/Neo/Ledger/Blockchain.cs b/src/Neo/Ledger/Blockchain.cs
index d9fc3e1b3f..ff9c8e29d8 100644
--- a/src/Neo/Ledger/Blockchain.cs
+++ b/src/Neo/Ledger/Blockchain.cs
@@ -339,7 +339,11 @@ private VerifyResult OnNewExtensiblePayload(ExtensiblePayload payload)
private VerifyResult OnNewTransaction(Transaction transaction)
{
- if (system.ContainsTransaction(transaction.Hash)) return VerifyResult.AlreadyExists;
+ switch (system.ContainsTransaction(transaction.Hash))
+ {
+ case ContainsTransactionType.ExistsInPool: return VerifyResult.AlreadyInPool;
+ case ContainsTransactionType.ExistsInLedger: return VerifyResult.AlreadyExists;
+ }
if (system.ContainsConflictHash(transaction.Hash, transaction.Signers.Select(s => s.Account))) return VerifyResult.HasConflicts;
return system.MemPool.TryAdd(transaction, system.StoreView);
}
@@ -393,11 +397,22 @@ protected override void OnReceive(object message)
private void OnTransaction(Transaction tx)
{
- if (system.ContainsTransaction(tx.Hash))
- SendRelayResult(tx, VerifyResult.AlreadyExists);
- else if (system.ContainsConflictHash(tx.Hash, tx.Signers.Select(s => s.Account)))
- SendRelayResult(tx, VerifyResult.HasConflicts);
- else system.TxRouter.Forward(new TransactionRouter.Preverify(tx, true));
+ switch (system.ContainsTransaction(tx.Hash))
+ {
+ case ContainsTransactionType.ExistsInPool:
+ SendRelayResult(tx, VerifyResult.AlreadyInPool);
+ break;
+ case ContainsTransactionType.ExistsInLedger:
+ SendRelayResult(tx, VerifyResult.AlreadyExists);
+ break;
+ default:
+ {
+ if (system.ContainsConflictHash(tx.Hash, tx.Signers.Select(s => s.Account)))
+ SendRelayResult(tx, VerifyResult.HasConflicts);
+ else system.TxRouter.Forward(new TransactionRouter.Preverify(tx, true));
+ break;
+ }
+ }
}
private void Persist(Block block)
@@ -409,7 +424,12 @@ private void Persist(Block block)
using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.OnPersist, null, snapshot, block, system.Settings, 0))
{
engine.LoadScript(onPersistScript);
- if (engine.Execute() != VMState.HALT) throw new InvalidOperationException();
+ if (engine.Execute() != VMState.HALT)
+ {
+ if (engine.FaultException != null)
+ throw engine.FaultException;
+ throw new InvalidOperationException();
+ }
ApplicationExecuted application_executed = new(engine);
Context.System.EventStream.Publish(application_executed);
all_application_executed.Add(application_executed);
@@ -438,7 +458,12 @@ private void Persist(Block block)
using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.PostPersist, null, snapshot, block, system.Settings, 0))
{
engine.LoadScript(postPersistScript);
- if (engine.Execute() != VMState.HALT) throw new InvalidOperationException();
+ if (engine.Execute() != VMState.HALT)
+ {
+ if (engine.FaultException != null)
+ throw engine.FaultException;
+ throw new InvalidOperationException();
+ }
ApplicationExecuted application_executed = new(engine);
Context.System.EventStream.Publish(application_executed);
all_application_executed.Add(application_executed);
diff --git a/src/Neo/Ledger/MemoryPool.cs b/src/Neo/Ledger/MemoryPool.cs
index 8e4ec64b22..23eb711e87 100644
--- a/src/Neo/Ledger/MemoryPool.cs
+++ b/src/Neo/Ledger/MemoryPool.cs
@@ -293,7 +293,7 @@ internal VerifyResult TryAdd(Transaction tx, DataCache snapshot)
{
var poolItem = new PoolItem(tx);
- if (_unsortedTransactions.ContainsKey(tx.Hash)) return VerifyResult.AlreadyExists;
+ if (_unsortedTransactions.ContainsKey(tx.Hash)) return VerifyResult.AlreadyInPool;
List removedTransactions = null;
_txRwLock.EnterWriteLock();
diff --git a/src/Neo/Ledger/VerifyResult.cs b/src/Neo/Ledger/VerifyResult.cs
index b218288efa..7a242b4c60 100644
--- a/src/Neo/Ledger/VerifyResult.cs
+++ b/src/Neo/Ledger/VerifyResult.cs
@@ -28,6 +28,11 @@ public enum VerifyResult : byte
///
AlreadyExists,
+ ///
+ /// Indicates that an with the same hash already exists in the memory pool.
+ ///
+ AlreadyInPool,
+
///
/// Indicates that the is full and the transaction cannot be verified.
///
diff --git a/src/Neo/Neo.csproj b/src/Neo/Neo.csproj
index 2d3a8a04cc..de628a66de 100644
--- a/src/Neo/Neo.csproj
+++ b/src/Neo/Neo.csproj
@@ -1,22 +1,23 @@
+ net7.0
true
NEO;AntShares;Blockchain;Smart Contract
-
-
-
-
-
+
+
+
+
+
diff --git a/src/Neo/NeoSystem.cs b/src/Neo/NeoSystem.cs
index 09c78e3e9c..3a148d612d 100644
--- a/src/Neo/NeoSystem.cs
+++ b/src/Neo/NeoSystem.cs
@@ -274,10 +274,11 @@ public SnapshotCache GetSnapshot()
///
/// The hash of the transaction
/// if the transaction exists; otherwise, .
- public bool ContainsTransaction(UInt256 hash)
+ public ContainsTransactionType ContainsTransaction(UInt256 hash)
{
- if (MemPool.ContainsKey(hash)) return true;
- return NativeContract.Ledger.ContainsTransaction(StoreView, hash);
+ if (MemPool.ContainsKey(hash)) return ContainsTransactionType.ExistsInPool;
+ return NativeContract.Ledger.ContainsTransaction(StoreView, hash) ?
+ ContainsTransactionType.ExistsInLedger : ContainsTransactionType.NotExist;
}
///
diff --git a/src/Neo/Network/P2P/Payloads/Header.cs b/src/Neo/Network/P2P/Payloads/Header.cs
index 9519c1432c..9e1d77fb8a 100644
--- a/src/Neo/Network/P2P/Payloads/Header.cs
+++ b/src/Neo/Network/P2P/Payloads/Header.cs
@@ -243,6 +243,7 @@ internal bool Verify(ProtocolSettings settings, DataCache snapshot)
TrimmedBlock prev = NativeContract.Ledger.GetTrimmedBlock(snapshot, prevHash);
if (prev is null) return false;
if (prev.Index + 1 != index) return false;
+ if (prev.Hash != prevHash) return false;
if (prev.Header.timestamp >= timestamp) return false;
if (!this.VerifyWitnesses(settings, snapshot, 3_00000000L)) return false;
return true;
diff --git a/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs b/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs
index afd37e2d17..4606723cd0 100644
--- a/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs
+++ b/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs
@@ -319,7 +319,7 @@ private void OnInventoryReceived(IInventory inventory)
switch (inventory)
{
case Transaction transaction:
- if (!(system.ContainsTransaction(transaction.Hash) || system.ContainsConflictHash(transaction.Hash, transaction.Signers.Select(s => s.Account))))
+ if (!(system.ContainsTransaction(transaction.Hash) != ContainsTransactionType.NotExist || system.ContainsConflictHash(transaction.Hash, transaction.Signers.Select(s => s.Account))))
system.TxRouter.Tell(new TransactionRouter.Preverify(transaction, true));
break;
case Block block:
diff --git a/src/README.md b/src/README.md
index 58a2cf66b5..c3738b18b9 100644
--- a/src/README.md
+++ b/src/README.md
@@ -1,25 +1,25 @@
-## Overview
-This repository contain main classes of the [Neo](https://www.neo.org) blockchain.
+## Overview
+This repository contain main classes of the [Neo](https://www.neo.org) blockchain.
Visit the [documentation](https://docs.neo.org/docs/en-us/index.html) to get started.
-## Related projects
-Code references are provided for all platform building blocks. That includes the base library, the VM, a command line application and the compiler.
-
-- [neo:](https://github.com/neo-project/neo/) Neo core library, contains base classes, including ledger, p2p and IO modules.
-- [neo-modules:](https://github.com/neo-project/neo-modules/) Neo modules include additional tools and plugins to be used with Neo.
-- [neo-devpack-dotnet:](https://github.com/neo-project/neo-devpack-dotnet/) These are the official tools used to convert a C# smart-contract into a *neo executable file*.
-
-## Opening a new issue
-Please feel free to create new issues to suggest features or ask questions.
-
-- [Feature request](https://github.com/neo-project/neo/issues/new?assignees=&labels=discussion&template=feature-or-enhancement-request.md&title=)
-- [Bug report](https://github.com/neo-project/neo/issues/new?assignees=&labels=&template=bug_report.md&title=)
-- [Questions](https://github.com/neo-project/neo/issues/new?assignees=&labels=question&template=questions.md&title=)
-
-If you found a security issue, please refer to our [security policy](https://github.com/neo-project/neo/security/policy).
-
-## Bounty program
-You can be rewarded by finding security issues. Please refer to our [bounty program page](https://neo.org/bounty) for more information.
-
-## License
+## Related projects
+Code references are provided for all platform building blocks. That includes the base library, the VM, a command line application and the compiler.
+
+- [neo:](https://github.com/neo-project/neo/) Neo core library, contains base classes, including ledger, p2p and IO modules.
+- [neo-modules:](https://github.com/neo-project/neo-modules/) Neo modules include additional tools and plugins to be used with Neo.
+- [neo-devpack-dotnet:](https://github.com/neo-project/neo-devpack-dotnet/) These are the official tools used to convert a C# smart-contract into a *neo executable file*.
+
+## Opening a new issue
+Please feel free to create new issues to suggest features or ask questions.
+
+- [Feature request](https://github.com/neo-project/neo/issues/new?assignees=&labels=discussion&template=feature-or-enhancement-request.md&title=)
+- [Bug report](https://github.com/neo-project/neo/issues/new?assignees=&labels=&template=bug_report.md&title=)
+- [Questions](https://github.com/neo-project/neo/issues/new?assignees=&labels=question&template=questions.md&title=)
+
+If you found a security issue, please refer to our [security policy](https://github.com/neo-project/neo/security/policy).
+
+## Bounty program
+You can be rewarded by finding security issues. Please refer to our [bounty program page](https://neo.org/bounty) for more information.
+
+## License
The NEO project is licensed under the [MIT license](http://www.opensource.org/licenses/mit-license.php).
diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props
index 875c10b42b..83fe17740a 100644
--- a/tests/Directory.Build.props
+++ b/tests/Directory.Build.props
@@ -8,7 +8,7 @@
-
+
diff --git a/tests/Neo.ConsoleService.Tests/Neo.ConsoleService.Tests.csproj b/tests/Neo.ConsoleService.Tests/Neo.ConsoleService.Tests.csproj
index b4b29127f6..0b12c476c8 100644
--- a/tests/Neo.ConsoleService.Tests/Neo.ConsoleService.Tests.csproj
+++ b/tests/Neo.ConsoleService.Tests/Neo.ConsoleService.Tests.csproj
@@ -1,10 +1,13 @@
+ net7.0
neo_cli.Tests
+ false
+
diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/Neo.Cryptography.BLS12_381.Tests.csproj b/tests/Neo.Cryptography.BLS12_381.Tests/Neo.Cryptography.BLS12_381.Tests.csproj
new file mode 100644
index 0000000000..a56621448c
--- /dev/null
+++ b/tests/Neo.Cryptography.BLS12_381.Tests/Neo.Cryptography.BLS12_381.Tests.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net7.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp.cs
new file mode 100644
index 0000000000..bc9cd480f2
--- /dev/null
+++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp.cs
@@ -0,0 +1,353 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// UT_Fp.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using System.Collections;
+using System.Runtime.InteropServices;
+using static Neo.Cryptography.BLS12_381.ConstantTimeUtility;
+
+namespace Neo.Cryptography.BLS12_381.Tests;
+
+[TestClass]
+public class UT_Fp
+{
+ [TestMethod]
+ public void TestSize()
+ {
+ Assert.AreEqual(Fp.Size, Marshal.SizeOf());
+ }
+
+ [TestMethod]
+ public void TestEquality()
+ {
+ static bool IsEqual(in Fp a, in Fp b)
+ {
+ bool eq = StructuralComparisons.StructuralEqualityComparer.Equals(a, b);
+ bool ct_eq = a == b;
+ Assert.AreEqual(eq, ct_eq);
+ return eq;
+ }
+
+ Assert.IsTrue(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 })));
+
+ Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 7, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 })));
+ Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 7, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 })));
+ Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 2, 7, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 })));
+ Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 7, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 })));
+ Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 7, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 })));
+ Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 7 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 })));
+ }
+
+ [TestMethod]
+ public void TestConditionalSelection()
+ {
+ Fp a = Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 });
+ Fp b = Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 });
+
+ Assert.AreEqual(a, ConditionalSelect(in a, in b, false));
+ Assert.AreEqual(b, ConditionalSelect(in a, in b, true));
+ }
+
+ [TestMethod]
+ public void TestSquaring()
+ {
+ Fp a = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xd215_d276_8e83_191b,
+ 0x5085_d80f_8fb2_8261,
+ 0xce9a_032d_df39_3a56,
+ 0x3e9c_4fff_2ca0_c4bb,
+ 0x6436_b6f7_f4d9_5dfb,
+ 0x1060_6628_ad4a_4d90
+ });
+ Fp b = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x33d9_c42a_3cb3_e235,
+ 0xdad1_1a09_4c4c_d455,
+ 0xa2f1_44bd_729a_aeba,
+ 0xd415_0932_be9f_feac,
+ 0xe27b_c7c4_7d44_ee50,
+ 0x14b6_a78d_3ec7_a560
+ });
+
+ Assert.AreEqual(b, a.Square());
+ }
+
+ [TestMethod]
+ public void TestMultiplication()
+ {
+ Fp a = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x0397_a383_2017_0cd4,
+ 0x734c_1b2c_9e76_1d30,
+ 0x5ed2_55ad_9a48_beb5,
+ 0x095a_3c6b_22a7_fcfc,
+ 0x2294_ce75_d4e2_6a27,
+ 0x1333_8bd8_7001_1ebb
+ });
+ Fp b = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xb9c3_c7c5_b119_6af7,
+ 0x2580_e208_6ce3_35c1,
+ 0xf49a_ed3d_8a57_ef42,
+ 0x41f2_81e4_9846_e878,
+ 0xe076_2346_c384_52ce,
+ 0x0652_e893_26e5_7dc0
+ });
+ Fp c = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xf96e_f3d7_11ab_5355,
+ 0xe8d4_59ea_00f1_48dd,
+ 0x53f7_354a_5f00_fa78,
+ 0x9e34_a4f3_125c_5f83,
+ 0x3fbe_0c47_ca74_c19e,
+ 0x01b0_6a8b_bd4a_dfe4
+ });
+
+ Assert.AreEqual(c, a * b);
+ }
+
+ [TestMethod]
+ public void TestAddition()
+ {
+ Fp a = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x5360_bb59_7867_8032,
+ 0x7dd2_75ae_799e_128e,
+ 0x5c5b_5071_ce4f_4dcf,
+ 0xcdb2_1f93_078d_bb3e,
+ 0xc323_65c5_e73f_474a,
+ 0x115a_2a54_89ba_be5b
+ });
+ Fp b = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x9fd2_8773_3d23_dda0,
+ 0xb16b_f2af_738b_3554,
+ 0x3e57_a75b_d3cc_6d1d,
+ 0x900b_c0bd_627f_d6d6,
+ 0xd319_a080_efb2_45fe,
+ 0x15fd_caa4_e4bb_2091
+ });
+ Fp c = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x3934_42cc_b58b_b327,
+ 0x1092_685f_3bd5_47e3,
+ 0x3382_252c_ab6a_c4c9,
+ 0xf946_94cb_7688_7f55,
+ 0x4b21_5e90_93a5_e071,
+ 0x0d56_e30f_34f5_f853
+ });
+
+ Assert.AreEqual(c, a + b);
+ }
+
+ [TestMethod]
+ public void TestSubtraction()
+ {
+ Fp a = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x5360_bb59_7867_8032,
+ 0x7dd2_75ae_799e_128e,
+ 0x5c5b_5071_ce4f_4dcf,
+ 0xcdb2_1f93_078d_bb3e,
+ 0xc323_65c5_e73f_474a,
+ 0x115a_2a54_89ba_be5b
+ });
+ Fp b = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x9fd2_8773_3d23_dda0,
+ 0xb16b_f2af_738b_3554,
+ 0x3e57_a75b_d3cc_6d1d,
+ 0x900b_c0bd_627f_d6d6,
+ 0xd319_a080_efb2_45fe,
+ 0x15fd_caa4_e4bb_2091
+ });
+ Fp c = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x6d8d_33e6_3b43_4d3d,
+ 0xeb12_82fd_b766_dd39,
+ 0x8534_7bb6_f133_d6d5,
+ 0xa21d_aa5a_9892_f727,
+ 0x3b25_6cfb_3ad8_ae23,
+ 0x155d_7199_de7f_8464
+ });
+
+ Assert.AreEqual(c, a - b);
+ }
+
+ [TestMethod]
+ public void TestNegation()
+ {
+ Fp a = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x5360_bb59_7867_8032,
+ 0x7dd2_75ae_799e_128e,
+ 0x5c5b_5071_ce4f_4dcf,
+ 0xcdb2_1f93_078d_bb3e,
+ 0xc323_65c5_e73f_474a,
+ 0x115a_2a54_89ba_be5b
+ });
+ Fp b = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x669e_44a6_8798_2a79,
+ 0xa0d9_8a50_37b5_ed71,
+ 0x0ad5_822f_2861_a854,
+ 0x96c5_2bf1_ebf7_5781,
+ 0x87f8_41f0_5c0c_658c,
+ 0x08a6_e795_afc5_283e
+ });
+
+ Assert.AreEqual(b, -a);
+ }
+
+ [TestMethod]
+ public void TestToString()
+ {
+ Fp a = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x5360_bb59_7867_8032,
+ 0x7dd2_75ae_799e_128e,
+ 0x5c5b_5071_ce4f_4dcf,
+ 0xcdb2_1f93_078d_bb3e,
+ 0xc323_65c5_e73f_474a,
+ 0x115a_2a54_89ba_be5b
+ });
+
+ Assert.AreEqual("0x104bf052ad3bc99bcb176c24a06a6c3aad4eaf2308fc4d282e106c84a757d061052630515305e59bdddf8111bfdeb704", a.ToString());
+ }
+
+ [TestMethod]
+ public void TestConstructor()
+ {
+ Fp a = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xdc90_6d9b_e3f9_5dc8,
+ 0x8755_caf7_4596_91a1,
+ 0xcff1_a7f4_e958_3ab3,
+ 0x9b43_821f_849e_2284,
+ 0xf575_54f3_a297_4f3f,
+ 0x085d_bea8_4ed4_7f79
+ });
+
+ for (int i = 0; i < 100; i++)
+ {
+ a = a.Square();
+ byte[] tmp = a.ToArray();
+ Fp b = Fp.FromBytes(tmp);
+
+ Assert.AreEqual(b, a);
+ }
+
+ Assert.AreEqual(-Fp.One, Fp.FromBytes(new byte[]
+ {
+ 26, 1, 17, 234, 57, 127, 230, 154, 75, 27, 167, 182, 67, 75, 172, 215, 100, 119, 75,
+ 132, 243, 133, 18, 191, 103, 48, 210, 160, 246, 176, 246, 36, 30, 171, 255, 254, 177,
+ 83, 255, 255, 185, 254, 255, 255, 255, 255, 170, 170
+ }));
+
+ Assert.ThrowsException(() => Fp.FromBytes(new byte[]
+ {
+ 27, 1, 17, 234, 57, 127, 230, 154, 75, 27, 167, 182, 67, 75, 172, 215, 100, 119, 75,
+ 132, 243, 133, 18, 191, 103, 48, 210, 160, 246, 176, 246, 36, 30, 171, 255, 254, 177,
+ 83, 255, 255, 185, 254, 255, 255, 255, 255, 170, 170
+ }));
+
+ Assert.ThrowsException(() => Fp.FromBytes(Enumerable.Repeat(0xff, 48).ToArray()));
+ }
+
+ [TestMethod]
+ public void TestSqrt()
+ {
+ // a = 4
+ Fp a = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xaa27_0000_000c_fff3,
+ 0x53cc_0032_fc34_000a,
+ 0x478f_e97a_6b0a_807f,
+ 0xb1d3_7ebe_e6ba_24d7,
+ 0x8ec9_733b_bf78_ab2f,
+ 0x09d6_4551_3d83_de7e
+ });
+
+ // b = 2
+ Fp b = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x3213_0000_0006_554f,
+ 0xb93c_0018_d6c4_0005,
+ 0x5760_5e0d_b0dd_bb51,
+ 0x8b25_6521_ed1f_9bcb,
+ 0x6cf2_8d79_0162_2c03,
+ 0x11eb_ab9d_bb81_e28c
+ });
+
+ // sqrt(4) = -2
+ Assert.AreEqual(b, -a.Sqrt());
+ }
+
+ [TestMethod]
+ public void TestInversion()
+ {
+ Fp a = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x43b4_3a50_78ac_2076,
+ 0x1ce0_7630_46f8_962b,
+ 0x724a_5276_486d_735c,
+ 0x6f05_c2a6_282d_48fd,
+ 0x2095_bd5b_b4ca_9331,
+ 0x03b3_5b38_94b0_f7da
+ });
+ Fp b = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x69ec_d704_0952_148f,
+ 0x985c_cc20_2219_0f55,
+ 0xe19b_ba36_a9ad_2f41,
+ 0x19bb_16c9_5219_dbd8,
+ 0x14dc_acfd_fb47_8693,
+ 0x115f_f58a_fff9_a8e1
+ });
+
+ Assert.AreEqual(b, a.Invert());
+ Assert.ThrowsException(() => Fp.Zero.Invert());
+ }
+
+ [TestMethod]
+ public void TestLexicographicLargest()
+ {
+ Assert.IsFalse(Fp.Zero.LexicographicallyLargest());
+ Assert.IsFalse(Fp.One.LexicographicallyLargest());
+ Assert.IsFalse(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xa1fa_ffff_fffe_5557,
+ 0x995b_fff9_76a3_fffe,
+ 0x03f4_1d24_d174_ceb4,
+ 0xf654_7998_c199_5dbd,
+ 0x778a_468f_507a_6034,
+ 0x0205_5993_1f7f_8103
+ }).LexicographicallyLargest());
+ Assert.IsTrue(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x1804_0000_0001_5554,
+ 0x8550_0005_3ab0_0001,
+ 0x633c_b57c_253c_276f,
+ 0x6e22_d1ec_31eb_b502,
+ 0xd391_6126_f2d1_4ca2,
+ 0x17fb_b857_1a00_6596
+ }).LexicographicallyLargest());
+ Assert.IsTrue(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x43f5_ffff_fffc_aaae,
+ 0x32b7_fff2_ed47_fffd,
+ 0x07e8_3a49_a2e9_9d69,
+ 0xeca8_f331_8332_bb7a,
+ 0xef14_8d1e_a0f4_c069,
+ 0x040a_b326_3eff_0206
+ }).LexicographicallyLargest());
+ }
+}
diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp12.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp12.cs
new file mode 100644
index 0000000000..238a20c6c6
--- /dev/null
+++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp12.cs
@@ -0,0 +1,347 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// UT_Fp12.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+namespace Neo.Cryptography.BLS12_381.Tests;
+
+[TestClass]
+public class UT_Fp12
+{
+ [TestMethod]
+ public void TestArithmetic()
+ {
+ var a = new Fp12(new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x47f9_cb98_b1b8_2d58,
+ 0x5fe9_11eb_a3aa_1d9d,
+ 0x96bf_1b5f_4dd8_1db3,
+ 0x8100_d27c_c925_9f5b,
+ 0xafa2_0b96_7464_0eab,
+ 0x09bb_cea7_d8d9_497d
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x0303_cb98_b166_2daa,
+ 0xd931_10aa_0a62_1d5a,
+ 0xbfa9_820c_5be4_a468,
+ 0x0ba3_643e_cb05_a348,
+ 0xdc35_34bb_1f1c_25a6,
+ 0x06c3_05bb_19c0_e1c1
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x46f9_cb98_b162_d858,
+ 0x0be9_109c_f7aa_1d57,
+ 0xc791_bc55_fece_41d2,
+ 0xf84c_5770_4e38_5ec2,
+ 0xcb49_c1d9_c010_e60f,
+ 0x0acd_b8e1_58bf_e3c8
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x8aef_cb98_b15f_8306,
+ 0x3ea1_108f_e4f2_1d54,
+ 0xcf79_f69f_a1b7_df3b,
+ 0xe4f5_4aa1_d16b_1a3c,
+ 0xba5e_4ef8_6105_a679,
+ 0x0ed8_6c07_97be_e5cf
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xcee5_cb98_b15c_2db4,
+ 0x7159_1082_d23a_1d51,
+ 0xd762_30e9_44a1_7ca4,
+ 0xd19e_3dd3_549d_d5b6,
+ 0xa972_dc17_01fa_66e3,
+ 0x12e3_1f2d_d6bd_e7d6
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xad2a_cb98_b173_2d9d,
+ 0x2cfd_10dd_0696_1d64,
+ 0x0739_6b86_c6ef_24e8,
+ 0xbd76_e2fd_b1bf_c820,
+ 0x6afe_a7f6_de94_d0d5,
+ 0x1099_4b0c_5744_c040
+ }))), new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x47f9_cb98_b1b8_2d58,
+ 0x5fe9_11eb_a3aa_1d9d,
+ 0x96bf_1b5f_4dd8_1db3,
+ 0x8100_d27c_c925_9f5b,
+ 0xafa2_0b96_7464_0eab,
+ 0x09bb_cea7_d8d9_497d
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x0303_cb98_b166_2daa,
+ 0xd931_10aa_0a62_1d5a,
+ 0xbfa9_820c_5be4_a468,
+ 0x0ba3_643e_cb05_a348,
+ 0xdc35_34bb_1f1c_25a6,
+ 0x06c3_05bb_19c0_e1c1
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x46f9_cb98_b162_d858,
+ 0x0be9_109c_f7aa_1d57,
+ 0xc791_bc55_fece_41d2,
+ 0xf84c_5770_4e38_5ec2,
+ 0xcb49_c1d9_c010_e60f,
+ 0x0acd_b8e1_58bf_e3c8
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x8aef_cb98_b15f_8306,
+ 0x3ea1_108f_e4f2_1d54,
+ 0xcf79_f69f_a1b7_df3b,
+ 0xe4f5_4aa1_d16b_1a3c,
+ 0xba5e_4ef8_6105_a679,
+ 0x0ed8_6c07_97be_e5cf
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xcee5_cb98_b15c_2db4,
+ 0x7159_1082_d23a_1d51,
+ 0xd762_30e9_44a1_7ca4,
+ 0xd19e_3dd3_549d_d5b6,
+ 0xa972_dc17_01fa_66e3,
+ 0x12e3_1f2d_d6bd_e7d6
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xad2a_cb98_b173_2d9d,
+ 0x2cfd_10dd_0696_1d64,
+ 0x0739_6b86_c6ef_24e8,
+ 0xbd76_e2fd_b1bf_c820,
+ 0x6afe_a7f6_de94_d0d5,
+ 0x1099_4b0c_5744_c040
+ }))));
+
+ var b = new Fp12(new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x47f9_cb98_b1b8_2d58,
+ 0x5fe9_11eb_a3aa_1d9d,
+ 0x96bf_1b5f_4dd8_1db3,
+ 0x8100_d272_c925_9f5b,
+ 0xafa2_0b96_7464_0eab,
+ 0x09bb_cea7_d8d9_497d
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x0303_cb98_b166_2daa,
+ 0xd931_10aa_0a62_1d5a,
+ 0xbfa9_820c_5be4_a468,
+ 0x0ba3_643e_cb05_a348,
+ 0xdc35_34bb_1f1c_25a6,
+ 0x06c3_05bb_19c0_e1c1
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x46f9_cb98_b162_d858,
+ 0x0be9_109c_f7aa_1d57,
+ 0xc791_bc55_fece_41d2,
+ 0xf84c_5770_4e38_5ec2,
+ 0xcb49_c1d9_c010_e60f,
+ 0x0acd_b8e1_58bf_e348
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x8aef_cb98_b15f_8306,
+ 0x3ea1_108f_e4f2_1d54,
+ 0xcf79_f69f_a1b7_df3b,
+ 0xe4f5_4aa1_d16b_1a3c,
+ 0xba5e_4ef8_6105_a679,
+ 0x0ed8_6c07_97be_e5cf
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xcee5_cb98_b15c_2db4,
+ 0x7159_1082_d23a_1d51,
+ 0xd762_30e9_44a1_7ca4,
+ 0xd19e_3dd3_549d_d5b6,
+ 0xa972_dc17_01fa_66e3,
+ 0x12e3_1f2d_d6bd_e7d6
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xad2a_cb98_b173_2d9d,
+ 0x2cfd_10dd_0696_1d64,
+ 0x0739_6b86_c6ef_24e8,
+ 0xbd76_e2fd_b1bf_c820,
+ 0x6afe_a7f6_de94_d0d5,
+ 0x1099_4b0c_5744_c040
+ }))), new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x47f9_cb98_b1b8_2d58,
+ 0x5fe9_11eb_a3aa_1d9d,
+ 0x96bf_1b5f_4dd2_1db3,
+ 0x8100_d27c_c925_9f5b,
+ 0xafa2_0b96_7464_0eab,
+ 0x09bb_cea7_d8d9_497d
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x0303_cb98_b166_2daa,
+ 0xd931_10aa_0a62_1d5a,
+ 0xbfa9_820c_5be4_a468,
+ 0x0ba3_643e_cb05_a348,
+ 0xdc35_34bb_1f1c_25a6,
+ 0x06c3_05bb_19c0_e1c1
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x46f9_cb98_b162_d858,
+ 0x0be9_109c_f7aa_1d57,
+ 0xc791_bc55_fece_41d2,
+ 0xf84c_5770_4e38_5ec2,
+ 0xcb49_c1d9_c010_e60f,
+ 0x0acd_b8e1_58bf_e3c8
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x8aef_cb98_b15f_8306,
+ 0x3ea1_108f_e4f2_1d54,
+ 0xcf79_f69f_a117_df3b,
+ 0xe4f5_4aa1_d16b_1a3c,
+ 0xba5e_4ef8_6105_a679,
+ 0x0ed8_6c07_97be_e5cf
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xcee5_cb98_b15c_2db4,
+ 0x7159_1082_d23a_1d51,
+ 0xd762_30e9_44a1_7ca4,
+ 0xd19e_3dd3_549d_d5b6,
+ 0xa972_dc17_01fa_66e3,
+ 0x12e3_1f2d_d6bd_e7d6
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xad2a_cb98_b173_2d9d,
+ 0x2cfd_10dd_0696_1d64,
+ 0x0739_6b86_c6ef_24e8,
+ 0xbd76_e2fd_b1bf_c820,
+ 0x6afe_a7f6_de94_d0d5,
+ 0x1099_4b0c_5744_c040
+ }))));
+
+ var c = new Fp12(new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x47f9_cb98_71b8_2d58,
+ 0x5fe9_11eb_a3aa_1d9d,
+ 0x96bf_1b5f_4dd8_1db3,
+ 0x8100_d27c_c925_9f5b,
+ 0xafa2_0b96_7464_0eab,
+ 0x09bb_cea7_d8d9_497d
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x0303_cb98_b166_2daa,
+ 0xd931_10aa_0a62_1d5a,
+ 0xbfa9_820c_5be4_a468,
+ 0x0ba3_643e_cb05_a348,
+ 0xdc35_34bb_1f1c_25a6,
+ 0x06c3_05bb_19c0_e1c1
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x46f9_cb98_b162_d858,
+ 0x0be9_109c_f7aa_1d57,
+ 0x7791_bc55_fece_41d2,
+ 0xf84c_5770_4e38_5ec2,
+ 0xcb49_c1d9_c010_e60f,
+ 0x0acd_b8e1_58bf_e3c8
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x8aef_cb98_b15f_8306,
+ 0x3ea1_108f_e4f2_1d54,
+ 0xcf79_f69f_a1b7_df3b,
+ 0xe4f5_4aa1_d16b_133c,
+ 0xba5e_4ef8_6105_a679,
+ 0x0ed8_6c07_97be_e5cf
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xcee5_cb98_b15c_2db4,
+ 0x7159_1082_d23a_1d51,
+ 0xd762_40e9_44a1_7ca4,
+ 0xd19e_3dd3_549d_d5b6,
+ 0xa972_dc17_01fa_66e3,
+ 0x12e3_1f2d_d6bd_e7d6
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xad2a_cb98_b173_2d9d,
+ 0x2cfd_10dd_0696_1d64,
+ 0x0739_6b86_c6ef_24e8,
+ 0xbd76_e2fd_b1bf_c820,
+ 0x6afe_a7f6_de94_d0d5,
+ 0x1099_4b0c_1744_c040
+ }))), new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x47f9_cb98_b1b8_2d58,
+ 0x5fe9_11eb_a3aa_1d9d,
+ 0x96bf_1b5f_4dd8_1db3,
+ 0x8100_d27c_c925_9f5b,
+ 0xafa2_0b96_7464_0eab,
+ 0x09bb_cea7_d8d9_497d
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x0303_cb98_b166_2daa,
+ 0xd931_10aa_0a62_1d5a,
+ 0xbfa9_820c_5be4_a468,
+ 0x0ba3_643e_cb05_a348,
+ 0xdc35_34bb_1f1c_25a6,
+ 0x06c3_05bb_19c0_e1c1
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x46f9_cb98_b162_d858,
+ 0x0be9_109c_f7aa_1d57,
+ 0xc791_bc55_fece_41d2,
+ 0xf84c_5770_4e38_5ec2,
+ 0xcb49_c1d3_c010_e60f,
+ 0x0acd_b8e1_58bf_e3c8
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x8aef_cb98_b15f_8306,
+ 0x3ea1_108f_e4f2_1d54,
+ 0xcf79_f69f_a1b7_df3b,
+ 0xe4f5_4aa1_d16b_1a3c,
+ 0xba5e_4ef8_6105_a679,
+ 0x0ed8_6c07_97be_e5cf
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xcee5_cb98_b15c_2db4,
+ 0x7159_1082_d23a_1d51,
+ 0xd762_30e9_44a1_7ca4,
+ 0xd19e_3dd3_549d_d5b6,
+ 0xa972_dc17_01fa_66e3,
+ 0x12e3_1f2d_d6bd_e7d6
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xad2a_cb98_b173_2d9d,
+ 0x2cfd_10dd_0696_1d64,
+ 0x0739_6b86_c6ef_24e8,
+ 0xbd76_e2fd_b1bf_c820,
+ 0x6afe_a7f6_de94_d0d5,
+ 0x1099_4b0c_5744_1040
+ }))));
+
+ // because a and b and c are similar to each other and
+ // I was lazy, this is just some arbitrary way to make
+ // them a little more different
+ a = a.Square().Invert().Square() + c;
+ b = b.Square().Invert().Square() + a;
+ c = c.Square().Invert().Square() + b;
+
+ Assert.AreEqual(a * a, a.Square());
+ Assert.AreEqual(b * b, b.Square());
+ Assert.AreEqual(c * c, c.Square());
+
+ Assert.AreEqual((a + b) * c.Square(), (c * c * a) + (c * c * b));
+
+ Assert.AreEqual(a.Invert() * b.Invert(), (a * b).Invert());
+ Assert.AreEqual(Fp12.One, a.Invert() * a);
+
+ Assert.AreNotEqual(a, a.FrobeniusMap());
+ Assert.AreEqual(
+ a,
+ a.FrobeniusMap()
+ .FrobeniusMap()
+ .FrobeniusMap()
+ .FrobeniusMap()
+ .FrobeniusMap()
+ .FrobeniusMap()
+ .FrobeniusMap()
+ .FrobeniusMap()
+ .FrobeniusMap()
+ .FrobeniusMap()
+ .FrobeniusMap()
+ .FrobeniusMap()
+ );
+ }
+}
diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp2.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp2.cs
new file mode 100644
index 0000000000..8e8781b834
--- /dev/null
+++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp2.cs
@@ -0,0 +1,493 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// UT_Fp2.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using System.Collections;
+using static Neo.Cryptography.BLS12_381.ConstantTimeUtility;
+
+namespace Neo.Cryptography.BLS12_381.Tests;
+
+[TestClass]
+public class UT_Fp2
+{
+ [TestMethod]
+ public void TestConditionalSelection()
+ {
+ var a = new Fp2(
+ Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }),
+ Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 })
+ );
+ var b = new Fp2(
+ Fp.FromRawUnchecked(new ulong[] { 13, 14, 15, 16, 17, 18 }),
+ Fp.FromRawUnchecked(new ulong[] { 19, 20, 21, 22, 23, 24 })
+ );
+
+ Assert.AreEqual(a, ConditionalSelect(in a, in b, false));
+ Assert.AreEqual(b, ConditionalSelect(in a, in b, true));
+ }
+
+ [TestMethod]
+ public void TestEquality()
+ {
+ static bool IsEqual(in Fp2 a, in Fp2 b)
+ {
+ var eq = StructuralComparisons.StructuralEqualityComparer.Equals(a, b);
+ var ct_eq = a == b;
+ Assert.AreEqual(eq, ct_eq);
+ return eq;
+ }
+
+ Assert.IsTrue(IsEqual(
+ new Fp2(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 })),
+ new Fp2(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 }))
+ ));
+
+ Assert.IsFalse(IsEqual(
+ new Fp2(Fp.FromRawUnchecked(new ulong[] { 2, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 })),
+ new Fp2(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 }))
+ ));
+
+ Assert.IsFalse(IsEqual(
+ new Fp2(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 2, 8, 9, 10, 11, 12 })),
+ new Fp2(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 }))
+ ));
+ }
+
+ [TestMethod]
+ public void TestSquaring()
+ {
+ var a = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xc9a2_1831_63ee_70d4,
+ 0xbc37_70a7_196b_5c91,
+ 0xa247_f8c1_304c_5f44,
+ 0xb01f_c2a3_726c_80b5,
+ 0xe1d2_93e5_bbd9_19c9,
+ 0x04b7_8e80_020e_f2ca,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x952e_a446_0462_618f,
+ 0x238d_5edd_f025_c62f,
+ 0xf6c9_4b01_2ea9_2e72,
+ 0x03ce_24ea_c1c9_3808,
+ 0x0559_50f9_45da_483c,
+ 0x010a_768d_0df4_eabc,
+ }));
+ var b = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xa1e0_9175_a4d2_c1fe,
+ 0x8b33_acfc_204e_ff12,
+ 0xe244_15a1_1b45_6e42,
+ 0x61d9_96b1_b6ee_1936,
+ 0x1164_dbe8_667c_853c,
+ 0x0788_557a_cc7d_9c79,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xda6a_87cc_6f48_fa36,
+ 0x0fc7_b488_277c_1903,
+ 0x9445_ac4a_dc44_8187,
+ 0x0261_6d5b_c909_9209,
+ 0xdbed_4677_2db5_8d48,
+ 0x11b9_4d50_76c7_b7b1,
+ }));
+
+ Assert.AreEqual(a.Square(), b);
+ }
+
+ [TestMethod]
+ public void TestMultiplication()
+ {
+ var a = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xc9a2_1831_63ee_70d4,
+ 0xbc37_70a7_196b_5c91,
+ 0xa247_f8c1_304c_5f44,
+ 0xb01f_c2a3_726c_80b5,
+ 0xe1d2_93e5_bbd9_19c9,
+ 0x04b7_8e80_020e_f2ca,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x952e_a446_0462_618f,
+ 0x238d_5edd_f025_c62f,
+ 0xf6c9_4b01_2ea9_2e72,
+ 0x03ce_24ea_c1c9_3808,
+ 0x0559_50f9_45da_483c,
+ 0x010a_768d_0df4_eabc,
+ }));
+ var b = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xa1e0_9175_a4d2_c1fe,
+ 0x8b33_acfc_204e_ff12,
+ 0xe244_15a1_1b45_6e42,
+ 0x61d9_96b1_b6ee_1936,
+ 0x1164_dbe8_667c_853c,
+ 0x0788_557a_cc7d_9c79,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xda6a_87cc_6f48_fa36,
+ 0x0fc7_b488_277c_1903,
+ 0x9445_ac4a_dc44_8187,
+ 0x0261_6d5b_c909_9209,
+ 0xdbed_4677_2db5_8d48,
+ 0x11b9_4d50_76c7_b7b1,
+ }));
+ var c = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xf597_483e_27b4_e0f7,
+ 0x610f_badf_811d_ae5f,
+ 0x8432_af91_7714_327a,
+ 0x6a9a_9603_cf88_f09e,
+ 0xf05a_7bf8_bad0_eb01,
+ 0x0954_9131_c003_ffae,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x963b_02d0_f93d_37cd,
+ 0xc95c_e1cd_b30a_73d4,
+ 0x3087_25fa_3126_f9b8,
+ 0x56da_3c16_7fab_0d50,
+ 0x6b50_86b5_f4b6_d6af,
+ 0x09c3_9f06_2f18_e9f2,
+ }));
+
+ Assert.AreEqual(c, a * b);
+ }
+
+ [TestMethod]
+ public void TestAddition()
+ {
+ var a = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xc9a2_1831_63ee_70d4,
+ 0xbc37_70a7_196b_5c91,
+ 0xa247_f8c1_304c_5f44,
+ 0xb01f_c2a3_726c_80b5,
+ 0xe1d2_93e5_bbd9_19c9,
+ 0x04b7_8e80_020e_f2ca,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x952e_a446_0462_618f,
+ 0x238d_5edd_f025_c62f,
+ 0xf6c9_4b01_2ea9_2e72,
+ 0x03ce_24ea_c1c9_3808,
+ 0x0559_50f9_45da_483c,
+ 0x010a_768d_0df4_eabc,
+ }));
+ var b = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xa1e0_9175_a4d2_c1fe,
+ 0x8b33_acfc_204e_ff12,
+ 0xe244_15a1_1b45_6e42,
+ 0x61d9_96b1_b6ee_1936,
+ 0x1164_dbe8_667c_853c,
+ 0x0788_557a_cc7d_9c79,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xda6a_87cc_6f48_fa36,
+ 0x0fc7_b488_277c_1903,
+ 0x9445_ac4a_dc44_8187,
+ 0x0261_6d5b_c909_9209,
+ 0xdbed_4677_2db5_8d48,
+ 0x11b9_4d50_76c7_b7b1,
+ }));
+ var c = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x6b82_a9a7_08c1_32d2,
+ 0x476b_1da3_39ba_5ba4,
+ 0x848c_0e62_4b91_cd87,
+ 0x11f9_5955_295a_99ec,
+ 0xf337_6fce_2255_9f06,
+ 0x0c3f_e3fa_ce8c_8f43,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x6f99_2c12_73ab_5bc5,
+ 0x3355_1366_17a1_df33,
+ 0x8b0e_f74c_0aed_aff9,
+ 0x062f_9246_8ad2_ca12,
+ 0xe146_9770_738f_d584,
+ 0x12c3_c3dd_84bc_a26d,
+ }));
+
+ Assert.AreEqual(a + b, c);
+ }
+
+ [TestMethod]
+ public void TestSubtraction()
+ {
+ var a = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xc9a2_1831_63ee_70d4,
+ 0xbc37_70a7_196b_5c91,
+ 0xa247_f8c1_304c_5f44,
+ 0xb01f_c2a3_726c_80b5,
+ 0xe1d2_93e5_bbd9_19c9,
+ 0x04b7_8e80_020e_f2ca,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x952e_a446_0462_618f,
+ 0x238d_5edd_f025_c62f,
+ 0xf6c9_4b01_2ea9_2e72,
+ 0x03ce_24ea_c1c9_3808,
+ 0x0559_50f9_45da_483c,
+ 0x010a_768d_0df4_eabc,
+ }));
+ var b = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xa1e0_9175_a4d2_c1fe,
+ 0x8b33_acfc_204e_ff12,
+ 0xe244_15a1_1b45_6e42,
+ 0x61d9_96b1_b6ee_1936,
+ 0x1164_dbe8_667c_853c,
+ 0x0788_557a_cc7d_9c79,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xda6a_87cc_6f48_fa36,
+ 0x0fc7_b488_277c_1903,
+ 0x9445_ac4a_dc44_8187,
+ 0x0261_6d5b_c909_9209,
+ 0xdbed_4677_2db5_8d48,
+ 0x11b9_4d50_76c7_b7b1,
+ }));
+ var c = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xe1c0_86bb_bf1b_5981,
+ 0x4faf_c3a9_aa70_5d7e,
+ 0x2734_b5c1_0bb7_e726,
+ 0xb2bd_7776_af03_7a3e,
+ 0x1b89_5fb3_98a8_4164,
+ 0x1730_4aef_6f11_3cec,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x74c3_1c79_9519_1204,
+ 0x3271_aa54_79fd_ad2b,
+ 0xc9b4_7157_4915_a30f,
+ 0x65e4_0313_ec44_b8be,
+ 0x7487_b238_5b70_67cb,
+ 0x0952_3b26_d0ad_19a4,
+ }));
+
+ Assert.AreEqual(c, a - b);
+ }
+
+ [TestMethod]
+ public void TestNegation()
+ {
+ var a = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xc9a2_1831_63ee_70d4,
+ 0xbc37_70a7_196b_5c91,
+ 0xa247_f8c1_304c_5f44,
+ 0xb01f_c2a3_726c_80b5,
+ 0xe1d2_93e5_bbd9_19c9,
+ 0x04b7_8e80_020e_f2ca,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x952e_a446_0462_618f,
+ 0x238d_5edd_f025_c62f,
+ 0xf6c9_4b01_2ea9_2e72,
+ 0x03ce_24ea_c1c9_3808,
+ 0x0559_50f9_45da_483c,
+ 0x010a_768d_0df4_eabc,
+ }));
+ var b = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xf05c_e7ce_9c11_39d7,
+ 0x6274_8f57_97e8_a36d,
+ 0xc4e8_d9df_c664_96df,
+ 0xb457_88e1_8118_9209,
+ 0x6949_13d0_8772_930d,
+ 0x1549_836a_3770_f3cf,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x24d0_5bb9_fb9d_491c,
+ 0xfb1e_a120_c12e_39d0,
+ 0x7067_879f_c807_c7b1,
+ 0x60a9_269a_31bb_dab6,
+ 0x45c2_56bc_fd71_649b,
+ 0x18f6_9b5d_2b8a_fbde,
+ }));
+
+ Assert.AreEqual(b, -a);
+ }
+
+ [TestMethod]
+ public void TestSqrt()
+ {
+ // a = 1488924004771393321054797166853618474668089414631333405711627789629391903630694737978065425271543178763948256226639*u + 784063022264861764559335808165825052288770346101304131934508881646553551234697082295473567906267937225174620141295
+ var a = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x2bee_d146_27d7_f9e9,
+ 0xb661_4e06_660e_5dce,
+ 0x06c4_cc7c_2f91_d42c,
+ 0x996d_7847_4b7a_63cc,
+ 0xebae_bc4c_820d_574e,
+ 0x1886_5e12_d93f_d845,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x7d82_8664_baf4_f566,
+ 0xd17e_6639_96ec_7339,
+ 0x679e_ad55_cb40_78d0,
+ 0xfe3b_2260_e001_ec28,
+ 0x3059_93d0_43d9_1b68,
+ 0x0626_f03c_0489_b72d,
+ }));
+
+ Assert.AreEqual(a, a.Sqrt().Square());
+
+ // b = 5, which is a generator of the p - 1 order
+ // multiplicative subgroup
+ var b = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x6631_0000_0010_5545,
+ 0x2114_0040_0eec_000d,
+ 0x3fa7_af30_c820_e316,
+ 0xc52a_8b8d_6387_695d,
+ 0x9fb4_e61d_1e83_eac5,
+ 0x005c_b922_afe8_4dc7,
+ }), Fp.Zero);
+
+ Assert.AreEqual(b, b.Sqrt().Square());
+
+ // c = 25, which is a generator of the (p - 1) / 2 order
+ // multiplicative subgroup
+ var c = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x44f6_0000_0051_ffae,
+ 0x86b8_0141_9948_0043,
+ 0xd715_9952_f1f3_794a,
+ 0x755d_6e3d_fe1f_fc12,
+ 0xd36c_d6db_5547_e905,
+ 0x02f8_c8ec_bf18_67bb,
+ }), Fp.Zero);
+
+ Assert.AreEqual(c, c.Sqrt().Square());
+
+ // 2155129644831861015726826462986972654175647013268275306775721078997042729172900466542651176384766902407257452753362*u + 2796889544896299244102912275102369318775038861758288697415827248356648685135290329705805931514906495247464901062529
+ // is nonsquare.
+ Assert.ThrowsException(() =>
+ new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xc5fa_1bc8_fd00_d7f6,
+ 0x3830_ca45_4606_003b,
+ 0x2b28_7f11_04b1_02da,
+ 0xa7fb_30f2_8230_f23e,
+ 0x339c_db9e_e953_dbf0,
+ 0x0d78_ec51_d989_fc57,
+ }), Fp.FromRawUnchecked(new ulong[]{
+ 0x27ec_4898_cf87_f613,
+ 0x9de1_394e_1abb_05a5,
+ 0x0947_f85d_c170_fc14,
+ 0x586f_bc69_6b61_14b7,
+ 0x2b34_75a4_077d_7169,
+ 0x13e1_c895_cc4b_6c22,
+ })).Sqrt());
+ }
+
+ [TestMethod]
+ public void TestInversion()
+ {
+ var a = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x1128_ecad_6754_9455,
+ 0x9e7a_1cff_3a4e_a1a8,
+ 0xeb20_8d51_e08b_cf27,
+ 0xe98a_d408_11f5_fc2b,
+ 0x736c_3a59_232d_511d,
+ 0x10ac_d42d_29cf_cbb6,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xd328_e37c_c2f5_8d41,
+ 0x948d_f085_8a60_5869,
+ 0x6032_f9d5_6f93_a573,
+ 0x2be4_83ef_3fff_dc87,
+ 0x30ef_61f8_8f48_3c2a,
+ 0x1333_f55a_3572_5be0,
+ }));
+
+ var b = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x0581_a133_3d4f_48a6,
+ 0x5824_2f6e_f074_8500,
+ 0x0292_c955_349e_6da5,
+ 0xba37_721d_dd95_fcd0,
+ 0x70d1_6790_3aa5_dfc5,
+ 0x1189_5e11_8b58_a9d5,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x0eda_09d2_d7a8_5d17,
+ 0x8808_e137_a7d1_a2cf,
+ 0x43ae_2625_c1ff_21db,
+ 0xf85a_c9fd_f7a7_4c64,
+ 0x8fcc_dda5_b8da_9738,
+ 0x08e8_4f0c_b32c_d17d,
+ }));
+ Assert.AreEqual(b, a.Invert());
+
+ Assert.ThrowsException(() => Fp2.Zero.Invert());
+ }
+
+ [TestMethod]
+ public void TestLexicographicLargest()
+ {
+ Assert.IsFalse(Fp2.Zero.LexicographicallyLargest());
+ Assert.IsFalse(Fp2.One.LexicographicallyLargest());
+ Assert.IsTrue(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x1128_ecad_6754_9455,
+ 0x9e7a_1cff_3a4e_a1a8,
+ 0xeb20_8d51_e08b_cf27,
+ 0xe98a_d408_11f5_fc2b,
+ 0x736c_3a59_232d_511d,
+ 0x10ac_d42d_29cf_cbb6,
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xd328_e37c_c2f5_8d41,
+ 0x948d_f085_8a60_5869,
+ 0x6032_f9d5_6f93_a573,
+ 0x2be4_83ef_3fff_dc87,
+ 0x30ef_61f8_8f48_3c2a,
+ 0x1333_f55a_3572_5be0,
+ })).LexicographicallyLargest());
+ Assert.IsFalse(new Fp2(-Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x1128_ecad_6754_9455,
+ 0x9e7a_1cff_3a4e_a1a8,
+ 0xeb20_8d51_e08b_cf27,
+ 0xe98a_d408_11f5_fc2b,
+ 0x736c_3a59_232d_511d,
+ 0x10ac_d42d_29cf_cbb6,
+ }), -Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xd328_e37c_c2f5_8d41,
+ 0x948d_f085_8a60_5869,
+ 0x6032_f9d5_6f93_a573,
+ 0x2be4_83ef_3fff_dc87,
+ 0x30ef_61f8_8f48_3c2a,
+ 0x1333_f55a_3572_5be0,
+ })).LexicographicallyLargest());
+ Assert.IsFalse(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x1128_ecad_6754_9455,
+ 0x9e7a_1cff_3a4e_a1a8,
+ 0xeb20_8d51_e08b_cf27,
+ 0xe98a_d408_11f5_fc2b,
+ 0x736c_3a59_232d_511d,
+ 0x10ac_d42d_29cf_cbb6,
+ }), Fp.Zero).LexicographicallyLargest());
+ Assert.IsTrue(new Fp2(-Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x1128_ecad_6754_9455,
+ 0x9e7a_1cff_3a4e_a1a8,
+ 0xeb20_8d51_e08b_cf27,
+ 0xe98a_d408_11f5_fc2b,
+ 0x736c_3a59_232d_511d,
+ 0x10ac_d42d_29cf_cbb6,
+ }), Fp.Zero).LexicographicallyLargest());
+ }
+}
diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp6.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp6.cs
new file mode 100644
index 0000000000..6fcc63fc97
--- /dev/null
+++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp6.cs
@@ -0,0 +1,179 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// UT_Fp6.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+namespace Neo.Cryptography.BLS12_381.Tests;
+
+[TestClass]
+public class UT_Fp6
+{
+ [TestMethod]
+ public void TestArithmetic()
+ {
+ var a = new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x47f9_cb98_b1b8_2d58,
+ 0x5fe9_11eb_a3aa_1d9d,
+ 0x96bf_1b5f_4dd8_1db3,
+ 0x8100_d27c_c925_9f5b,
+ 0xafa2_0b96_7464_0eab,
+ 0x09bb_cea7_d8d9_497d
+ }), c1: Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x0303_cb98_b166_2daa,
+ 0xd931_10aa_0a62_1d5a,
+ 0xbfa9_820c_5be4_a468,
+ 0x0ba3_643e_cb05_a348,
+ 0xdc35_34bb_1f1c_25a6,
+ 0x06c3_05bb_19c0_e1c1
+ })), c1: new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x46f9_cb98_b162_d858,
+ 0x0be9_109c_f7aa_1d57,
+ 0xc791_bc55_fece_41d2,
+ 0xf84c_5770_4e38_5ec2,
+ 0xcb49_c1d9_c010_e60f,
+ 0x0acd_b8e1_58bf_e3c8
+ }), c1: Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x8aef_cb98_b15f_8306,
+ 0x3ea1_108f_e4f2_1d54,
+ 0xcf79_f69f_a1b7_df3b,
+ 0xe4f5_4aa1_d16b_1a3c,
+ 0xba5e_4ef8_6105_a679,
+ 0x0ed8_6c07_97be_e5cf
+ })), c2: new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xcee5_cb98_b15c_2db4,
+ 0x7159_1082_d23a_1d51,
+ 0xd762_30e9_44a1_7ca4,
+ 0xd19e_3dd3_549d_d5b6,
+ 0xa972_dc17_01fa_66e3,
+ 0x12e3_1f2d_d6bd_e7d6
+ }), c1: Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xad2a_cb98_b173_2d9d,
+ 0x2cfd_10dd_0696_1d64,
+ 0x0739_6b86_c6ef_24e8,
+ 0xbd76_e2fd_b1bf_c820,
+ 0x6afe_a7f6_de94_d0d5,
+ 0x1099_4b0c_5744_c040
+ })));
+
+ var b = new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xf120_cb98_b16f_d84b,
+ 0x5fb5_10cf_f3de_1d61,
+ 0x0f21_a5d0_69d8_c251,
+ 0xaa1f_d62f_34f2_839a,
+ 0x5a13_3515_7f89_913f,
+ 0x14a3_fe32_9643_c247
+ }), c1: Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x3516_cb98_b16c_82f9,
+ 0x926d_10c2_e126_1d5f,
+ 0x1709_e01a_0cc2_5fba,
+ 0x96c8_c960_b825_3f14,
+ 0x4927_c234_207e_51a9,
+ 0x18ae_b158_d542_c44e
+ })), c1: new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xbf0d_cb98_b169_82fc,
+ 0xa679_10b7_1d1a_1d5c,
+ 0xb7c1_47c2_b8fb_06ff,
+ 0x1efa_710d_47d2_e7ce,
+ 0xed20_a79c_7e27_653c,
+ 0x02b8_5294_dac1_dfba
+ }), c1: Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x9d52_cb98_b180_82e5,
+ 0x621d_1111_5176_1d6f,
+ 0xe798_8260_3b48_af43,
+ 0x0ad3_1637_a4f4_da37,
+ 0xaeac_737c_5ac1_cf2e,
+ 0x006e_7e73_5b48_b824
+ })), c2: new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xe148_cb98_b17d_2d93,
+ 0x94d5_1104_3ebe_1d6c,
+ 0xef80_bca9_de32_4cac,
+ 0xf77c_0969_2827_95b1,
+ 0x9dc1_009a_fbb6_8f97,
+ 0x0479_3199_9a47_ba2b
+ }), c1: Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x253e_cb98_b179_d841,
+ 0xc78d_10f7_2c06_1d6a,
+ 0xf768_f6f3_811b_ea15,
+ 0xe424_fc9a_ab5a_512b,
+ 0x8cd5_8db9_9cab_5001,
+ 0x0883_e4bf_d946_bc32
+ })));
+
+ var c = new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x6934_cb98_b176_82ef,
+ 0xfa45_10ea_194e_1d67,
+ 0xff51_313d_2405_877e,
+ 0xd0cd_efcc_2e8d_0ca5,
+ 0x7bea_1ad8_3da0_106b,
+ 0x0c8e_97e6_1845_be39
+ }), c1: Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x4779_cb98_b18d_82d8,
+ 0xb5e9_1144_4daa_1d7a,
+ 0x2f28_6bda_a653_2fc2,
+ 0xbca6_94f6_8bae_ff0f,
+ 0x3d75_e6b8_1a3a_7a5d,
+ 0x0a44_c3c4_98cc_96a3
+ })), c1: new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x8b6f_cb98_b18a_2d86,
+ 0xe8a1_1137_3af2_1d77,
+ 0x3710_a624_493c_cd2b,
+ 0xa94f_8828_0ee1_ba89,
+ 0x2c8a_73d6_bb2f_3ac7,
+ 0x0e4f_76ea_d7cb_98aa
+ }), c1: Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xcf65_cb98_b186_d834,
+ 0x1b59_112a_283a_1d74,
+ 0x3ef8_e06d_ec26_6a95,
+ 0x95f8_7b59_9214_7603,
+ 0x1b9f_00f5_5c23_fb31,
+ 0x125a_2a11_16ca_9ab1
+ })), c2: new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x135b_cb98_b183_82e2,
+ 0x4e11_111d_1582_1d72,
+ 0x46e1_1ab7_8f10_07fe,
+ 0x82a1_6e8b_1547_317d,
+ 0x0ab3_8e13_fd18_bb9b,
+ 0x1664_dd37_55c9_9cb8
+ }), c1: Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xce65_cb98_b131_8334,
+ 0xc759_0fdb_7c3a_1d2e,
+ 0x6fcb_8164_9d1c_8eb3,
+ 0x0d44_004d_1727_356a,
+ 0x3746_b738_a7d0_d296,
+ 0x136c_144a_96b1_34fc
+ })));
+
+ Assert.AreEqual(a * a, a.Square());
+ Assert.AreEqual(b * b, b.Square());
+ Assert.AreEqual(c * c, c.Square());
+
+ Assert.AreEqual((a + b) * c.Square(), (c * c * a) + (c * c * b));
+
+ Assert.AreEqual(a.Invert() * b.Invert(), (a * b).Invert());
+ Assert.AreEqual(Fp6.One, a.Invert() * a);
+ }
+}
diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_G1.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_G1.cs
new file mode 100644
index 0000000000..ab1a598b3c
--- /dev/null
+++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_G1.cs
@@ -0,0 +1,603 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// UT_G1.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using static Neo.Cryptography.BLS12_381.Constants;
+using static Neo.Cryptography.BLS12_381.ConstantTimeUtility;
+using static Neo.Cryptography.BLS12_381.G1Constants;
+
+namespace Neo.Cryptography.BLS12_381.Tests;
+
+[TestClass]
+public class UT_G1
+{
+ [TestMethod]
+ public void TestBeta()
+ {
+ Assert.AreEqual(Fp.FromBytes(new byte[]
+ {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x19, 0x67, 0x2f, 0xdf, 0x76,
+ 0xce, 0x51, 0xba, 0x69, 0xc6, 0x07, 0x6a, 0x0f, 0x77, 0xea, 0xdd, 0xb3, 0xa9, 0x3b,
+ 0xe6, 0xf8, 0x96, 0x88, 0xde, 0x17, 0xd8, 0x13, 0x62, 0x0a, 0x00, 0x02, 0x2e, 0x01,
+ 0xff, 0xff, 0xff, 0xfe, 0xff, 0xfe
+ }), BETA);
+ Assert.AreNotEqual(Fp.One, BETA);
+ Assert.AreNotEqual(Fp.One, BETA * BETA);
+ Assert.AreEqual(Fp.One, BETA * BETA * BETA);
+ }
+
+ [TestMethod]
+ public void TestIsOnCurve()
+ {
+ Assert.IsTrue(G1Affine.Identity.IsOnCurve);
+ Assert.IsTrue(G1Affine.Generator.IsOnCurve);
+ Assert.IsTrue(G1Projective.Identity.IsOnCurve);
+ Assert.IsTrue(G1Projective.Generator.IsOnCurve);
+
+ Fp z = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xba7a_fa1f_9a6f_e250,
+ 0xfa0f_5b59_5eaf_e731,
+ 0x3bdc_4776_94c3_06e7,
+ 0x2149_be4b_3949_fa24,
+ 0x64aa_6e06_49b2_078c,
+ 0x12b1_08ac_3364_3c3e
+ });
+
+ var gen = G1Affine.Generator;
+ G1Projective test = new(gen.X * z, gen.Y * z, in z);
+
+ Assert.IsTrue(test.IsOnCurve);
+
+ test = new(in z, in test.Y, in test.Z);
+ Assert.IsFalse(test.IsOnCurve);
+ }
+
+ [TestMethod]
+ public void TestAffinePointEquality()
+ {
+ var a = G1Affine.Generator;
+ var b = G1Affine.Identity;
+
+ Assert.AreEqual(a, a);
+ Assert.AreEqual(b, b);
+ Assert.AreNotEqual(a, b);
+ }
+
+ [TestMethod]
+ public void TestProjectivePointEquality()
+ {
+ var a = G1Projective.Generator;
+ var b = G1Projective.Identity;
+
+ Assert.AreEqual(a, a);
+ Assert.AreEqual(b, b);
+ Assert.AreNotEqual(a, b);
+
+ Fp z = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xba7a_fa1f_9a6f_e250,
+ 0xfa0f_5b59_5eaf_e731,
+ 0x3bdc_4776_94c3_06e7,
+ 0x2149_be4b_3949_fa24,
+ 0x64aa_6e06_49b2_078c,
+ 0x12b1_08ac_3364_3c3e
+ });
+
+ G1Projective c = new(a.X * z, a.Y * z, in z);
+ Assert.IsTrue(c.IsOnCurve);
+
+ Assert.AreEqual(a, c);
+ Assert.AreNotEqual(b, c);
+
+ c = new(in c.X, -c.Y, in c.Z);
+ Assert.IsTrue(c.IsOnCurve);
+
+ Assert.AreNotEqual(a, c);
+ Assert.AreNotEqual(b, c);
+
+ c = new(in z, -c.Y, in c.Z);
+ Assert.IsFalse(c.IsOnCurve);
+ Assert.AreNotEqual(a, b);
+ Assert.AreNotEqual(a, c);
+ Assert.AreNotEqual(b, c);
+ }
+
+ [TestMethod]
+ public void TestConditionallySelectAffine()
+ {
+ var a = G1Affine.Generator;
+ var b = G1Affine.Identity;
+
+ Assert.AreEqual(a, ConditionalSelect(in a, in b, false));
+ Assert.AreEqual(b, ConditionalSelect(in a, in b, true));
+ }
+
+ [TestMethod]
+ public void TestConditionallySelectProjective()
+ {
+ var a = G1Projective.Generator;
+ var b = G1Projective.Identity;
+
+ Assert.AreEqual(a, ConditionalSelect(in a, in b, false));
+ Assert.AreEqual(b, ConditionalSelect(in a, in b, true));
+ }
+
+ [TestMethod]
+ public void TestProjectiveToAffine()
+ {
+ var a = G1Projective.Generator;
+ var b = G1Projective.Identity;
+
+ Assert.IsTrue(new G1Affine(a).IsOnCurve);
+ Assert.IsFalse(new G1Affine(a).IsIdentity);
+ Assert.IsTrue(new G1Affine(b).IsOnCurve);
+ Assert.IsTrue(new G1Affine(b).IsIdentity);
+
+ Fp z = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xba7a_fa1f_9a6f_e250,
+ 0xfa0f_5b59_5eaf_e731,
+ 0x3bdc_4776_94c3_06e7,
+ 0x2149_be4b_3949_fa24,
+ 0x64aa_6e06_49b2_078c,
+ 0x12b1_08ac_3364_3c3e
+ });
+
+ G1Projective c = new(a.X * z, a.Y * z, in z);
+
+ Assert.AreEqual(G1Affine.Generator, new G1Affine(c));
+ }
+
+ [TestMethod]
+ public void TestAffineToProjective()
+ {
+ var a = G1Affine.Generator;
+ var b = G1Affine.Identity;
+
+ Assert.IsTrue(new G1Projective(a).IsOnCurve);
+ Assert.IsFalse(new G1Projective(a).IsIdentity);
+ Assert.IsTrue(new G1Projective(b).IsOnCurve);
+ Assert.IsTrue(new G1Projective(b).IsIdentity);
+ }
+
+ [TestMethod]
+ public void TestDoubling()
+ {
+ {
+ var tmp = G1Projective.Identity.Double();
+ Assert.IsTrue(tmp.IsIdentity);
+ Assert.IsTrue(tmp.IsOnCurve);
+ }
+ {
+ var tmp = G1Projective.Generator.Double();
+ Assert.IsFalse(tmp.IsIdentity);
+ Assert.IsTrue(tmp.IsOnCurve);
+
+ Assert.AreEqual(new G1Affine(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x53e9_78ce_58a9_ba3c,
+ 0x3ea0_583c_4f3d_65f9,
+ 0x4d20_bb47_f001_2960,
+ 0xa54c_664a_e5b2_b5d9,
+ 0x26b5_52a3_9d7e_b21f,
+ 0x0008_895d_26e6_8785
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x7011_0b32_9829_3940,
+ 0xda33_c539_3f1f_6afc,
+ 0xb86e_dfd1_6a5a_a785,
+ 0xaec6_d1c9_e7b1_c895,
+ 0x25cf_c2b5_22d1_1720,
+ 0x0636_1c83_f8d0_9b15
+ })), new G1Affine(tmp));
+ }
+ }
+
+ [TestMethod]
+ public void TestProjectiveAddition()
+ {
+ {
+ var a = G1Projective.Identity;
+ var b = G1Projective.Identity;
+ var c = a + b;
+ Assert.IsTrue(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ }
+ {
+ var a = G1Projective.Identity;
+ var b = G1Projective.Generator;
+
+ Fp z = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xba7a_fa1f_9a6f_e250,
+ 0xfa0f_5b59_5eaf_e731,
+ 0x3bdc_4776_94c3_06e7,
+ 0x2149_be4b_3949_fa24,
+ 0x64aa_6e06_49b2_078c,
+ 0x12b1_08ac_3364_3c3e
+ });
+
+ b = new(b.X * z, b.Y * z, in z);
+ var c = a + b;
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ Assert.AreEqual(G1Projective.Generator, c);
+ }
+ {
+ var a = G1Projective.Identity;
+ var b = G1Projective.Generator;
+
+ Fp z = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xba7a_fa1f_9a6f_e250,
+ 0xfa0f_5b59_5eaf_e731,
+ 0x3bdc_4776_94c3_06e7,
+ 0x2149_be4b_3949_fa24,
+ 0x64aa_6e06_49b2_078c,
+ 0x12b1_08ac_3364_3c3e
+ });
+
+ b = new(b.X * z, b.Y * z, in z);
+ var c = b + a;
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ Assert.AreEqual(G1Projective.Generator, c);
+ }
+ {
+ var a = G1Projective.Generator.Double().Double(); // 4P
+ var b = G1Projective.Generator.Double(); // 2P
+ var c = a + b;
+
+ var d = G1Projective.Generator;
+ for (int i = 0; i < 5; i++)
+ {
+ d += G1Projective.Generator;
+ }
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ Assert.IsFalse(d.IsIdentity);
+ Assert.IsTrue(d.IsOnCurve);
+ Assert.AreEqual(c, d);
+ }
+ {
+ Fp beta = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xcd03_c9e4_8671_f071,
+ 0x5dab_2246_1fcd_a5d2,
+ 0x5870_42af_d385_1b95,
+ 0x8eb6_0ebe_01ba_cb9e,
+ 0x03f9_7d6e_83d0_50d2,
+ 0x18f0_2065_5463_8741
+ });
+ beta = beta.Square();
+ var a = G1Projective.Generator.Double().Double();
+ var b = new G1Projective(a.X * beta, -a.Y, in a.Z);
+ Assert.IsTrue(a.IsOnCurve);
+ Assert.IsTrue(b.IsOnCurve);
+
+ var c = a + b;
+ Assert.AreEqual(new G1Affine(new G1Projective(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x29e1_e987_ef68_f2d0,
+ 0xc5f3_ec53_1db0_3233,
+ 0xacd6_c4b6_ca19_730f,
+ 0x18ad_9e82_7bc2_bab7,
+ 0x46e3_b2c5_785c_c7a9,
+ 0x07e5_71d4_2d22_ddd6
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x94d1_17a7_e5a5_39e7,
+ 0x8e17_ef67_3d4b_5d22,
+ 0x9d74_6aaf_508a_33ea,
+ 0x8c6d_883d_2516_c9a2,
+ 0x0bc3_b8d5_fb04_47f7,
+ 0x07bf_a4c7_210f_4f44,
+ }), in Fp.One)), new G1Affine(c));
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ }
+ }
+
+ [TestMethod]
+ public void TestMixedAddition()
+ {
+ {
+ var a = G1Affine.Identity;
+ var b = G1Projective.Identity;
+ var c = a + b;
+ Assert.IsTrue(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ }
+ {
+ var a = G1Affine.Identity;
+ var b = G1Projective.Generator;
+
+ Fp z = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xba7a_fa1f_9a6f_e250,
+ 0xfa0f_5b59_5eaf_e731,
+ 0x3bdc_4776_94c3_06e7,
+ 0x2149_be4b_3949_fa24,
+ 0x64aa_6e06_49b2_078c,
+ 0x12b1_08ac_3364_3c3e
+ });
+
+ b = new(b.X * z, b.Y * z, in z);
+ var c = a + b;
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ Assert.AreEqual(G1Projective.Generator, c);
+ }
+ {
+ var a = G1Affine.Identity;
+ var b = G1Projective.Generator;
+
+ Fp z = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xba7a_fa1f_9a6f_e250,
+ 0xfa0f_5b59_5eaf_e731,
+ 0x3bdc_4776_94c3_06e7,
+ 0x2149_be4b_3949_fa24,
+ 0x64aa_6e06_49b2_078c,
+ 0x12b1_08ac_3364_3c3e
+ });
+
+ b = new(b.X * z, b.Y * z, in z);
+ var c = b + a;
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ Assert.AreEqual(G1Projective.Generator, c);
+ }
+ {
+ var a = G1Projective.Generator.Double().Double(); // 4P
+ var b = G1Projective.Generator.Double(); // 2P
+ var c = a + b;
+
+ var d = G1Projective.Generator;
+ for (int i = 0; i < 5; i++)
+ {
+ d += G1Affine.Generator;
+ }
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ Assert.IsFalse(d.IsIdentity);
+ Assert.IsTrue(d.IsOnCurve);
+ Assert.AreEqual(c, d);
+ }
+ {
+ Fp beta = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xcd03_c9e4_8671_f071,
+ 0x5dab_2246_1fcd_a5d2,
+ 0x5870_42af_d385_1b95,
+ 0x8eb6_0ebe_01ba_cb9e,
+ 0x03f9_7d6e_83d0_50d2,
+ 0x18f0_2065_5463_8741
+ });
+ beta = beta.Square();
+ var a = G1Projective.Generator.Double().Double();
+ var b = new G1Projective(a.X * beta, -a.Y, in a.Z);
+ var a2 = new G1Affine(a);
+ Assert.IsTrue(a2.IsOnCurve);
+ Assert.IsTrue(b.IsOnCurve);
+
+ var c = a2 + b;
+ Assert.AreEqual(new G1Affine(new G1Projective(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x29e1_e987_ef68_f2d0,
+ 0xc5f3_ec53_1db0_3233,
+ 0xacd6_c4b6_ca19_730f,
+ 0x18ad_9e82_7bc2_bab7,
+ 0x46e3_b2c5_785c_c7a9,
+ 0x07e5_71d4_2d22_ddd6
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x94d1_17a7_e5a5_39e7,
+ 0x8e17_ef67_3d4b_5d22,
+ 0x9d74_6aaf_508a_33ea,
+ 0x8c6d_883d_2516_c9a2,
+ 0x0bc3_b8d5_fb04_47f7,
+ 0x07bf_a4c7_210f_4f44
+ }), Fp.One)), new G1Affine(c));
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ }
+ }
+
+ [TestMethod]
+ public void TestProjectiveNegationAndSubtraction()
+ {
+ var a = G1Projective.Generator.Double();
+ Assert.AreEqual(a + (-a), G1Projective.Identity);
+ Assert.AreEqual(a + (-a), a - a);
+ }
+
+ [TestMethod]
+ public void TestAffineNegationAndSubtraction()
+ {
+ var a = G1Affine.Generator;
+ Assert.AreEqual(G1Projective.Identity, new G1Projective(a) + (-a));
+ Assert.AreEqual(new G1Projective(a) + (-a), new G1Projective(a) - a);
+ }
+
+ [TestMethod]
+ public void TestProjectiveScalarMultiplication()
+ {
+ var g = G1Projective.Generator;
+ var a = Scalar.FromRaw(new ulong[]
+ {
+ 0x2b56_8297_a56d_a71c,
+ 0xd8c3_9ecb_0ef3_75d1,
+ 0x435c_38da_67bf_bf96,
+ 0x8088_a050_26b6_59b2
+ });
+ var b = Scalar.FromRaw(new ulong[]
+ {
+ 0x785f_dd9b_26ef_8b85,
+ 0xc997_f258_3769_5c18,
+ 0x4c8d_bc39_e7b7_56c1,
+ 0x70d9_b6cc_6d87_df20
+ });
+ var c = a * b;
+
+ Assert.AreEqual(g * a * b, g * c);
+ }
+
+ [TestMethod]
+ public void TestAffineScalarMultiplication()
+ {
+ var g = G1Affine.Generator;
+ var a = Scalar.FromRaw(new ulong[]
+ {
+ 0x2b56_8297_a56d_a71c,
+ 0xd8c3_9ecb_0ef3_75d1,
+ 0x435c_38da_67bf_bf96,
+ 0x8088_a050_26b6_59b2
+ });
+ var b = Scalar.FromRaw(new ulong[]
+ {
+ 0x785f_dd9b_26ef_8b85,
+ 0xc997_f258_3769_5c18,
+ 0x4c8d_bc39_e7b7_56c1,
+ 0x70d9_b6cc_6d87_df20
+ });
+ var c = a * b;
+
+ Assert.AreEqual(new G1Affine(g * a) * b, g * c);
+ }
+
+ [TestMethod]
+ public void TestIsTorsionFree()
+ {
+ var a = new G1Affine(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x0aba_f895_b97e_43c8,
+ 0xba4c_6432_eb9b_61b0,
+ 0x1250_6f52_adfe_307f,
+ 0x7502_8c34_3933_6b72,
+ 0x8474_4f05_b8e9_bd71,
+ 0x113d_554f_b095_54f7
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x73e9_0e88_f5cf_01c0,
+ 0x3700_7b65_dd31_97e2,
+ 0x5cf9_a199_2f0d_7c78,
+ 0x4f83_c10b_9eb3_330d,
+ 0xf6a6_3f6f_07f6_0961,
+ 0x0c53_b5b9_7e63_4df3
+ }));
+ Assert.IsFalse(a.IsTorsionFree);
+
+ Assert.IsTrue(G1Affine.Identity.IsTorsionFree);
+ Assert.IsTrue(G1Affine.Generator.IsTorsionFree);
+ }
+
+ [TestMethod]
+ public void TestMulByX()
+ {
+ // multiplying by `x` a point in G1 is the same as multiplying by
+ // the equivalent scalar.
+ var generator = G1Projective.Generator;
+ var x = BLS_X_IS_NEGATIVE ? -new Scalar(BLS_X) : new Scalar(BLS_X);
+ Assert.AreEqual(generator.MulByX(), generator * x);
+
+ var point = G1Projective.Generator * new Scalar(42);
+ Assert.AreEqual(point.MulByX(), point * x);
+ }
+
+ [TestMethod]
+ public void TestClearCofactor()
+ {
+ // the generator (and the identity) are always on the curve,
+ // even after clearing the cofactor
+ var generator = G1Projective.Generator;
+ Assert.IsTrue(generator.ClearCofactor().IsOnCurve);
+ var id = G1Projective.Identity;
+ Assert.IsTrue(id.ClearCofactor().IsOnCurve);
+
+ var z = Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x3d2d1c670671394e,
+ 0x0ee3a800a2f7c1ca,
+ 0x270f4f21da2e5050,
+ 0xe02840a53f1be768,
+ 0x55debeb597512690,
+ 0x08bd25353dc8f791
+ });
+
+ var point = new G1Projective(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x48af5ff540c817f0,
+ 0xd73893acaf379d5a,
+ 0xe6c43584e18e023c,
+ 0x1eda39c30f188b3e,
+ 0xf618c6d3ccc0f8d8,
+ 0x0073542cd671e16c
+ }) * z, Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x57bf8be79461d0ba,
+ 0xfc61459cee3547c3,
+ 0x0d23567df1ef147b,
+ 0x0ee187bcce1d9b64,
+ 0xb0c8cfbe9dc8fdc1,
+ 0x1328661767ef368b
+ }), z.Square() * z);
+
+ Assert.IsTrue(point.IsOnCurve);
+ Assert.IsFalse(new G1Affine(point).IsTorsionFree);
+ var cleared_point = point.ClearCofactor();
+ Assert.IsTrue(cleared_point.IsOnCurve);
+ Assert.IsTrue(new G1Affine(cleared_point).IsTorsionFree);
+
+ // in BLS12-381 the cofactor in G1 can be
+ // cleared multiplying by (1-x)
+ var h_eff = new Scalar(1) + new Scalar(BLS_X);
+ Assert.AreEqual(point.ClearCofactor(), point * h_eff);
+ }
+
+ [TestMethod]
+ public void TestBatchNormalize()
+ {
+ var a = G1Projective.Generator.Double();
+ var b = a.Double();
+ var c = b.Double();
+
+ foreach (bool a_identity in new[] { false, true })
+ {
+ foreach (bool b_identity in new[] { false, true })
+ {
+ foreach (bool c_identity in new[] { false, true })
+ {
+ var v = new[] { a, b, c };
+ if (a_identity)
+ {
+ v[0] = G1Projective.Identity;
+ }
+ if (b_identity)
+ {
+ v[1] = G1Projective.Identity;
+ }
+ if (c_identity)
+ {
+ v[2] = G1Projective.Identity;
+ }
+
+ var t = new G1Affine[3];
+ var expected = new[] { new G1Affine(v[0]), new G1Affine(v[1]), new G1Affine(v[2]) };
+
+ G1Projective.BatchNormalize(v, t);
+
+ CollectionAssert.AreEqual(expected, t);
+ }
+ }
+ }
+ }
+}
diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_G2.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_G2.cs
new file mode 100644
index 0000000000..7b8cadb90e
--- /dev/null
+++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_G2.cs
@@ -0,0 +1,823 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// UT_G2.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using static Neo.Cryptography.BLS12_381.Constants;
+using static Neo.Cryptography.BLS12_381.ConstantTimeUtility;
+
+namespace Neo.Cryptography.BLS12_381.Tests;
+
+[TestClass]
+public class UT_G2
+{
+ [TestMethod]
+ public void TestIsOnCurve()
+ {
+ Assert.IsTrue(G2Affine.Identity.IsOnCurve);
+ Assert.IsTrue(G2Affine.Generator.IsOnCurve);
+ Assert.IsTrue(G2Projective.Identity.IsOnCurve);
+ Assert.IsTrue(G2Projective.Generator.IsOnCurve);
+
+ var z = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xba7a_fa1f_9a6f_e250,
+ 0xfa0f_5b59_5eaf_e731,
+ 0x3bdc_4776_94c3_06e7,
+ 0x2149_be4b_3949_fa24,
+ 0x64aa_6e06_49b2_078c,
+ 0x12b1_08ac_3364_3c3e
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x1253_25df_3d35_b5a8,
+ 0xdc46_9ef5_555d_7fe3,
+ 0x02d7_16d2_4431_06a9,
+ 0x05a1_db59_a6ff_37d0,
+ 0x7cf7_784e_5300_bb8f,
+ 0x16a8_8922_c7a5_e844
+ }));
+
+ var gen = G2Affine.Generator;
+ var test = new G2Projective(gen.X * z, gen.Y * z, z);
+
+ Assert.IsTrue(test.IsOnCurve);
+
+ test = new(in z, in test.Y, in test.Z);
+ Assert.IsFalse(test.IsOnCurve);
+ }
+
+ [TestMethod]
+ public void TestAffinePointEquality()
+ {
+ var a = G2Affine.Generator;
+ var b = G2Affine.Identity;
+
+ Assert.AreEqual(a, a);
+ Assert.AreEqual(b, b);
+ Assert.AreNotEqual(a, b);
+ }
+
+ [TestMethod]
+ public void TestProjectivePointEquality()
+ {
+ var a = G2Projective.Generator;
+ var b = G2Projective.Identity;
+
+ Assert.AreEqual(a, a);
+ Assert.AreEqual(b, b);
+ Assert.AreNotEqual(a, b);
+
+ var z = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xba7a_fa1f_9a6f_e250,
+ 0xfa0f_5b59_5eaf_e731,
+ 0x3bdc_4776_94c3_06e7,
+ 0x2149_be4b_3949_fa24,
+ 0x64aa_6e06_49b2_078c,
+ 0x12b1_08ac_3364_3c3e
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x1253_25df_3d35_b5a8,
+ 0xdc46_9ef5_555d_7fe3,
+ 0x02d7_16d2_4431_06a9,
+ 0x05a1_db59_a6ff_37d0,
+ 0x7cf7_784e_5300_bb8f,
+ 0x16a8_8922_c7a5_e844
+ }));
+
+ var c = new G2Projective(a.X * z, a.Y * z, in z);
+ Assert.IsTrue(c.IsOnCurve);
+
+ Assert.AreEqual(a, c);
+ Assert.AreNotEqual(b, c);
+
+ c = new(in c.X, -c.Y, in c.Z);
+ Assert.IsTrue(c.IsOnCurve);
+
+ Assert.AreNotEqual(a, c);
+ Assert.AreNotEqual(b, c);
+
+ c = new(in z, -c.Y, in c.Z);
+ Assert.IsFalse(c.IsOnCurve);
+ Assert.AreNotEqual(a, b);
+ Assert.AreNotEqual(a, c);
+ Assert.AreNotEqual(b, c);
+ }
+
+ [TestMethod]
+ public void TestConditionallySelectAffine()
+ {
+ var a = G2Affine.Generator;
+ var b = G2Affine.Identity;
+
+ Assert.AreEqual(a, ConditionalSelect(in a, in b, false));
+ Assert.AreEqual(b, ConditionalSelect(in a, in b, true));
+ }
+
+ [TestMethod]
+ public void TestConditionallySelectProjective()
+ {
+ var a = G2Projective.Generator;
+ var b = G2Projective.Identity;
+
+ Assert.AreEqual(a, ConditionalSelect(in a, in b, false));
+ Assert.AreEqual(b, ConditionalSelect(in a, in b, true));
+ }
+
+ [TestMethod]
+ public void TestProjectiveToAffine()
+ {
+ var a = G2Projective.Generator;
+ var b = G2Projective.Identity;
+
+ Assert.IsTrue(new G2Affine(a).IsOnCurve);
+ Assert.IsFalse(new G2Affine(a).IsIdentity);
+ Assert.IsTrue(new G2Affine(b).IsOnCurve);
+ Assert.IsTrue(new G2Affine(b).IsIdentity);
+
+ var z = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xba7a_fa1f_9a6f_e250,
+ 0xfa0f_5b59_5eaf_e731,
+ 0x3bdc_4776_94c3_06e7,
+ 0x2149_be4b_3949_fa24,
+ 0x64aa_6e06_49b2_078c,
+ 0x12b1_08ac_3364_3c3e
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x1253_25df_3d35_b5a8,
+ 0xdc46_9ef5_555d_7fe3,
+ 0x02d7_16d2_4431_06a9,
+ 0x05a1_db59_a6ff_37d0,
+ 0x7cf7_784e_5300_bb8f,
+ 0x16a8_8922_c7a5_e844
+ }));
+
+ var c = new G2Projective(a.X * z, a.Y * z, in z);
+
+ Assert.AreEqual(G2Affine.Generator, new G2Affine(c));
+ }
+
+ [TestMethod]
+ public void TestAffineToProjective()
+ {
+ var a = G2Affine.Generator;
+ var b = G2Affine.Identity;
+
+ Assert.IsTrue(new G2Projective(a).IsOnCurve);
+ Assert.IsFalse(new G2Projective(a).IsIdentity);
+ Assert.IsTrue(new G2Projective(b).IsOnCurve);
+ Assert.IsTrue(new G2Projective(b).IsIdentity);
+ }
+
+ [TestMethod]
+ public void TestDoubling()
+ {
+ {
+ var tmp = G2Projective.Identity.Double();
+ Assert.IsTrue(tmp.IsIdentity);
+ Assert.IsTrue(tmp.IsOnCurve);
+ }
+ {
+ var tmp = G2Projective.Generator.Double();
+ Assert.IsFalse(tmp.IsIdentity);
+ Assert.IsTrue(tmp.IsOnCurve);
+
+ Assert.AreEqual(new G2Affine(tmp), new G2Affine(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xe9d9_e2da_9620_f98b,
+ 0x54f1_1993_46b9_7f36,
+ 0x3db3_b820_376b_ed27,
+ 0xcfdb_31c9_b0b6_4f4c,
+ 0x41d7_c127_8635_4493,
+ 0x0571_0794_c255_c064
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xd6c1_d3ca_6ea0_d06e,
+ 0xda0c_bd90_5595_489f,
+ 0x4f53_52d4_3479_221d,
+ 0x8ade_5d73_6f8c_97e0,
+ 0x48cc_8433_925e_f70e,
+ 0x08d7_ea71_ea91_ef81
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x15ba_26eb_4b0d_186f,
+ 0x0d08_6d64_b7e9_e01e,
+ 0xc8b8_48dd_652f_4c78,
+ 0xeecf_46a6_123b_ae4f,
+ 0x255e_8dd8_b6dc_812a,
+ 0x1641_42af_21dc_f93f
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xf9b4_a1a8_9598_4db4,
+ 0xd417_b114_cccf_f748,
+ 0x6856_301f_c89f_086e,
+ 0x41c7_7787_8931_e3da,
+ 0x3556_b155_066a_2105,
+ 0x00ac_f7d3_25cb_89cf
+ }))));
+ }
+ }
+
+ [TestMethod]
+ public void TestProjectiveAddition()
+ {
+ {
+ var a = G2Projective.Identity;
+ var b = G2Projective.Identity;
+ var c = a + b;
+ Assert.IsTrue(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ }
+ {
+ var a = G2Projective.Identity;
+ var b = G2Projective.Generator;
+ {
+ var z = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xba7a_fa1f_9a6f_e250,
+ 0xfa0f_5b59_5eaf_e731,
+ 0x3bdc_4776_94c3_06e7,
+ 0x2149_be4b_3949_fa24,
+ 0x64aa_6e06_49b2_078c,
+ 0x12b1_08ac_3364_3c3e
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x1253_25df_3d35_b5a8,
+ 0xdc46_9ef5_555d_7fe3,
+ 0x02d7_16d2_4431_06a9,
+ 0x05a1_db59_a6ff_37d0,
+ 0x7cf7_784e_5300_bb8f,
+ 0x16a8_8922_c7a5_e844
+ }));
+
+ b = new G2Projective(b.X * z, b.Y * z, in z);
+ }
+ var c = a + b;
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ Assert.AreEqual(G2Projective.Generator, c);
+ }
+ {
+ var a = G2Projective.Identity;
+ var b = G2Projective.Generator;
+ {
+ var z = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xba7a_fa1f_9a6f_e250,
+ 0xfa0f_5b59_5eaf_e731,
+ 0x3bdc_4776_94c3_06e7,
+ 0x2149_be4b_3949_fa24,
+ 0x64aa_6e06_49b2_078c,
+ 0x12b1_08ac_3364_3c3e
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x1253_25df_3d35_b5a8,
+ 0xdc46_9ef5_555d_7fe3,
+ 0x02d7_16d2_4431_06a9,
+ 0x05a1_db59_a6ff_37d0,
+ 0x7cf7_784e_5300_bb8f,
+ 0x16a8_8922_c7a5_e844
+ }));
+
+ b = new G2Projective(b.X * z, b.Y * z, in z);
+ }
+ var c = b + a;
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ Assert.AreEqual(G2Projective.Generator, c);
+ }
+ {
+ var a = G2Projective.Generator.Double().Double(); // 4P
+ var b = G2Projective.Generator.Double(); // 2P
+ var c = a + b;
+
+ var d = G2Projective.Generator;
+ for (int i = 0; i < 5; i++)
+ {
+ d += G2Projective.Generator;
+ }
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ Assert.IsFalse(d.IsIdentity);
+ Assert.IsTrue(d.IsOnCurve);
+ Assert.AreEqual(c, d);
+ }
+
+ // Degenerate case
+ {
+ var beta = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xcd03_c9e4_8671_f071,
+ 0x5dab_2246_1fcd_a5d2,
+ 0x5870_42af_d385_1b95,
+ 0x8eb6_0ebe_01ba_cb9e,
+ 0x03f9_7d6e_83d0_50d2,
+ 0x18f0_2065_5463_8741
+ }), Fp.Zero);
+ beta = beta.Square();
+ var a = G2Projective.Generator.Double().Double();
+ var b = new G2Projective(a.X * beta, -a.Y, in a.Z);
+ Assert.IsTrue(a.IsOnCurve);
+ Assert.IsTrue(b.IsOnCurve);
+
+ var c = a + b;
+ Assert.AreEqual(
+ new G2Affine(c),
+ new G2Affine(new G2Projective(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x705a_bc79_9ca7_73d3,
+ 0xfe13_2292_c1d4_bf08,
+ 0xf37e_ce3e_07b2_b466,
+ 0x887e_1c43_f447_e301,
+ 0x1e09_70d0_33bc_77e8,
+ 0x1985_c81e_20a6_93f2
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x1d79_b25d_b36a_b924,
+ 0x2394_8e4d_5296_39d3,
+ 0x471b_a7fb_0d00_6297,
+ 0x2c36_d4b4_465d_c4c0,
+ 0x82bb_c3cf_ec67_f538,
+ 0x051d_2728_b67b_f952
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x41b1_bbf6_576c_0abf,
+ 0xb6cc_9371_3f7a_0f9a,
+ 0x6b65_b43e_48f3_f01f,
+ 0xfb7a_4cfc_af81_be4f,
+ 0x3e32_dadc_6ec2_2cb6,
+ 0x0bb0_fc49_d798_07e3
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x7d13_9778_8f5f_2ddf,
+ 0xab29_0714_4ff0_d8e8,
+ 0x5b75_73e0_cdb9_1f92,
+ 0x4cb8_932d_d31d_af28,
+ 0x62bb_fac6_db05_2a54,
+ 0x11f9_5c16_d14c_3bbe
+ })), Fp2.One)));
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ }
+ }
+
+ [TestMethod]
+ public void TestMixedAddition()
+ {
+ {
+ var a = G2Affine.Identity;
+ var b = G2Projective.Identity;
+ var c = a + b;
+ Assert.IsTrue(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ }
+ {
+ var a = G2Affine.Identity;
+ var b = G2Projective.Generator;
+ {
+ var z = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xba7a_fa1f_9a6f_e250,
+ 0xfa0f_5b59_5eaf_e731,
+ 0x3bdc_4776_94c3_06e7,
+ 0x2149_be4b_3949_fa24,
+ 0x64aa_6e06_49b2_078c,
+ 0x12b1_08ac_3364_3c3e
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x1253_25df_3d35_b5a8,
+ 0xdc46_9ef5_555d_7fe3,
+ 0x02d7_16d2_4431_06a9,
+ 0x05a1_db59_a6ff_37d0,
+ 0x7cf7_784e_5300_bb8f,
+ 0x16a8_8922_c7a5_e844
+ }));
+
+ b = new G2Projective(b.X * z, b.Y * z, in z);
+ }
+ var c = a + b;
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ Assert.AreEqual(G2Projective.Generator, c);
+ }
+ {
+ var a = G2Affine.Identity;
+ var b = G2Projective.Generator;
+ {
+ var z = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xba7a_fa1f_9a6f_e250,
+ 0xfa0f_5b59_5eaf_e731,
+ 0x3bdc_4776_94c3_06e7,
+ 0x2149_be4b_3949_fa24,
+ 0x64aa_6e06_49b2_078c,
+ 0x12b1_08ac_3364_3c3e
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x1253_25df_3d35_b5a8,
+ 0xdc46_9ef5_555d_7fe3,
+ 0x02d7_16d2_4431_06a9,
+ 0x05a1_db59_a6ff_37d0,
+ 0x7cf7_784e_5300_bb8f,
+ 0x16a8_8922_c7a5_e844
+ }));
+
+ b = new G2Projective(b.X * z, b.Y * z, in z);
+ }
+ var c = b + a;
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ Assert.AreEqual(G2Projective.Generator, c);
+ }
+ {
+ var a = G2Projective.Generator.Double().Double(); // 4P
+ var b = G2Projective.Generator.Double(); // 2P
+ var c = a + b;
+
+ var d = G2Projective.Generator;
+ for (int i = 0; i < 5; i++)
+ {
+ d += G2Affine.Generator;
+ }
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ Assert.IsFalse(d.IsIdentity);
+ Assert.IsTrue(d.IsOnCurve);
+ Assert.AreEqual(c, d);
+ }
+
+ // Degenerate case
+ {
+ var beta = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xcd03_c9e4_8671_f071,
+ 0x5dab_2246_1fcd_a5d2,
+ 0x5870_42af_d385_1b95,
+ 0x8eb6_0ebe_01ba_cb9e,
+ 0x03f9_7d6e_83d0_50d2,
+ 0x18f0_2065_5463_8741
+ }), Fp.Zero);
+ beta = beta.Square();
+ var _a = G2Projective.Generator.Double().Double();
+ var b = new G2Projective(_a.X * beta, -_a.Y, in _a.Z);
+ var a = new G2Affine(_a);
+ Assert.IsTrue((a.IsOnCurve));
+ Assert.IsTrue((b.IsOnCurve));
+
+ var c = a + b;
+ Assert.AreEqual(new G2Affine(new G2Projective(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x705a_bc79_9ca7_73d3,
+ 0xfe13_2292_c1d4_bf08,
+ 0xf37e_ce3e_07b2_b466,
+ 0x887e_1c43_f447_e301,
+ 0x1e09_70d0_33bc_77e8,
+ 0x1985_c81e_20a6_93f2
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x1d79_b25d_b36a_b924,
+ 0x2394_8e4d_5296_39d3,
+ 0x471b_a7fb_0d00_6297,
+ 0x2c36_d4b4_465d_c4c0,
+ 0x82bb_c3cf_ec67_f538,
+ 0x051d_2728_b67b_f952
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x41b1_bbf6_576c_0abf,
+ 0xb6cc_9371_3f7a_0f9a,
+ 0x6b65_b43e_48f3_f01f,
+ 0xfb7a_4cfc_af81_be4f,
+ 0x3e32_dadc_6ec2_2cb6,
+ 0x0bb0_fc49_d798_07e3
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x7d13_9778_8f5f_2ddf,
+ 0xab29_0714_4ff0_d8e8,
+ 0x5b75_73e0_cdb9_1f92,
+ 0x4cb8_932d_d31d_af28,
+ 0x62bb_fac6_db05_2a54,
+ 0x11f9_5c16_d14c_3bbe
+ })), Fp2.One)), new G2Affine(c));
+ Assert.IsFalse(c.IsIdentity);
+ Assert.IsTrue(c.IsOnCurve);
+ }
+ }
+
+ [TestMethod]
+ public void TestProjectiveNegationAndSubtraction()
+ {
+ var a = G2Projective.Generator.Double();
+ Assert.AreEqual(G2Projective.Identity, a + (-a));
+ Assert.AreEqual(a - a, a + (-a));
+ }
+
+ [TestMethod]
+ public void TestAffineNegationAndSubtraction()
+ {
+ var a = G2Affine.Generator;
+ Assert.AreEqual(G2Projective.Identity, new G2Projective(a) + (-a));
+ Assert.AreEqual(new G2Projective(a) - a, new G2Projective(a) + (-a));
+ }
+
+ [TestMethod]
+ public void TestProjectiveScalarMultiplication()
+ {
+ var g = G2Projective.Generator;
+ var a = Scalar.FromRaw(new ulong[]
+ {
+ 0x2b56_8297_a56d_a71c,
+ 0xd8c3_9ecb_0ef3_75d1,
+ 0x435c_38da_67bf_bf96,
+ 0x8088_a050_26b6_59b2
+ });
+ var b = Scalar.FromRaw(new ulong[]
+ {
+ 0x785f_dd9b_26ef_8b85,
+ 0xc997_f258_3769_5c18,
+ 0x4c8d_bc39_e7b7_56c1,
+ 0x70d9_b6cc_6d87_df20
+ });
+ var c = a * b;
+
+ Assert.AreEqual(g * c, g * a * b);
+ }
+
+ [TestMethod]
+ public void TestAffineScalarMultiplication()
+ {
+ var g = G2Affine.Generator;
+ var a = Scalar.FromRaw(new ulong[]
+ {
+ 0x2b56_8297_a56d_a71c,
+ 0xd8c3_9ecb_0ef3_75d1,
+ 0x435c_38da_67bf_bf96,
+ 0x8088_a050_26b6_59b2
+ });
+ var b = Scalar.FromRaw(new ulong[]
+ {
+ 0x785f_dd9b_26ef_8b85,
+ 0xc997_f258_3769_5c18,
+ 0x4c8d_bc39_e7b7_56c1,
+ 0x70d9_b6cc_6d87_df20
+ });
+ var c = a * b;
+
+ Assert.AreEqual(g * c, new G2Affine(g * a) * b);
+ }
+
+ [TestMethod]
+ public void TestIsTorsionFree()
+ {
+ var a = new G2Affine(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x89f5_50c8_13db_6431,
+ 0xa50b_e8c4_56cd_8a1a,
+ 0xa45b_3741_14ca_e851,
+ 0xbb61_90f5_bf7f_ff63,
+ 0x970c_a02c_3ba8_0bc7,
+ 0x02b8_5d24_e840_fbac
+ }),
+ Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x6888_bc53_d707_16dc,
+ 0x3dea_6b41_1768_2d70,
+ 0xd8f5_f930_500c_a354,
+ 0x6b5e_cb65_56f5_c155,
+ 0xc96b_ef04_3477_8ab0,
+ 0x0508_1505_5150_06ad
+ })), new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x3cf1_ea0d_434b_0f40,
+ 0x1a0d_c610_e603_e333,
+ 0x7f89_9561_60c7_2fa0,
+ 0x25ee_03de_cf64_31c5,
+ 0xeee8_e206_ec0f_e137,
+ 0x0975_92b2_26df_ef28
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x71e8_bb5f_2924_7367,
+ 0xa5fe_049e_2118_31ce,
+ 0x0ce6_b354_502a_3896,
+ 0x93b0_1200_0997_314e,
+ 0x6759_f3b6_aa5b_42ac,
+ 0x1569_44c4_dfe9_2bbb
+ })));
+ Assert.IsFalse(a.IsTorsionFree);
+
+ Assert.IsTrue(G2Affine.Identity.IsTorsionFree);
+ Assert.IsTrue(G2Affine.Generator.IsTorsionFree);
+ }
+
+ [TestMethod]
+ public void TestMulByX()
+ {
+ // multiplying by `x` a point in G2 is the same as multiplying by
+ // the equivalent scalar.
+ var generator = G2Projective.Generator;
+ var x = BLS_X_IS_NEGATIVE ? -new Scalar(BLS_X) : new Scalar(BLS_X);
+ Assert.AreEqual(generator * x, generator.MulByX());
+
+ var point = G2Projective.Generator * new Scalar(42);
+ Assert.AreEqual(point * x, point.MulByX());
+ }
+
+ [TestMethod]
+ public void TestPsi()
+ {
+ var generator = G2Projective.Generator;
+
+ var z = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x0ef2ddffab187c0a,
+ 0x2424522b7d5ecbfc,
+ 0xc6f341a3398054f4,
+ 0x5523ddf409502df0,
+ 0xd55c0b5a88e0dd97,
+ 0x066428d704923e52
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x538bbe0c95b4878d,
+ 0xad04a50379522881,
+ 0x6d5c05bf5c12fb64,
+ 0x4ce4a069a2d34787,
+ 0x59ea6c8d0dffaeaf,
+ 0x0d42a083a75bd6f3
+ }));
+
+ // `point` is a random point in the curve
+ var point = new G2Projective(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xee4c8cb7c047eaf2,
+ 0x44ca22eee036b604,
+ 0x33b3affb2aefe101,
+ 0x15d3e45bbafaeb02,
+ 0x7bfc2154cd7419a4,
+ 0x0a2d0c2b756e5edc
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xfc224361029a8777,
+ 0x4cbf2baab8740924,
+ 0xc5008c6ec6592c89,
+ 0xecc2c57b472a9c2d,
+ 0x8613eafd9d81ffb1,
+ 0x10fe54daa2d3d495
+ })) * z, new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x7de7edc43953b75c,
+ 0x58be1d2de35e87dc,
+ 0x5731d30b0e337b40,
+ 0xbe93b60cfeaae4c9,
+ 0x8b22c203764bedca,
+ 0x01616c8d1033b771
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xea126fe476b5733b,
+ 0x85cee68b5dae1652,
+ 0x98247779f7272b04,
+ 0xa649c8b468c6e808,
+ 0xb5b9a62dff0c4e45,
+ 0x1555b67fc7bbe73d
+ })), z.Square() * z);
+ Assert.IsTrue(point.IsOnCurve);
+
+ // psi2(P) = psi(psi(P))
+ Assert.AreEqual(generator.Psi2(), generator.Psi().Psi());
+ Assert.AreEqual(point.Psi2(), point.Psi().Psi());
+ // psi(P) is a morphism
+ Assert.AreEqual(generator.Double().Psi(), generator.Psi().Double());
+ Assert.AreEqual(point.Psi() + generator.Psi(), (point + generator).Psi());
+ // psi(P) behaves in the same way on the same projective point
+ var normalized_points = new G2Affine[1];
+ G2Projective.BatchNormalize(new[] { point }, normalized_points);
+ var normalized_point = new G2Projective(normalized_points[0]);
+ Assert.AreEqual(point.Psi(), normalized_point.Psi());
+ Assert.AreEqual(point.Psi2(), normalized_point.Psi2());
+ }
+
+ [TestMethod]
+ public void TestClearCofactor()
+ {
+ var z = new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x0ef2ddffab187c0a,
+ 0x2424522b7d5ecbfc,
+ 0xc6f341a3398054f4,
+ 0x5523ddf409502df0,
+ 0xd55c0b5a88e0dd97,
+ 0x066428d704923e52
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x538bbe0c95b4878d,
+ 0xad04a50379522881,
+ 0x6d5c05bf5c12fb64,
+ 0x4ce4a069a2d34787,
+ 0x59ea6c8d0dffaeaf,
+ 0x0d42a083a75bd6f3
+ }));
+
+ // `point` is a random point in the curve
+ var point = new G2Projective(new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xee4c8cb7c047eaf2,
+ 0x44ca22eee036b604,
+ 0x33b3affb2aefe101,
+ 0x15d3e45bbafaeb02,
+ 0x7bfc2154cd7419a4,
+ 0x0a2d0c2b756e5edc
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xfc224361029a8777,
+ 0x4cbf2baab8740924,
+ 0xc5008c6ec6592c89,
+ 0xecc2c57b472a9c2d,
+ 0x8613eafd9d81ffb1,
+ 0x10fe54daa2d3d495
+ })) * z, new Fp2(Fp.FromRawUnchecked(new ulong[]
+ {
+ 0x7de7edc43953b75c,
+ 0x58be1d2de35e87dc,
+ 0x5731d30b0e337b40,
+ 0xbe93b60cfeaae4c9,
+ 0x8b22c203764bedca,
+ 0x01616c8d1033b771
+ }), Fp.FromRawUnchecked(new ulong[]
+ {
+ 0xea126fe476b5733b,
+ 0x85cee68b5dae1652,
+ 0x98247779f7272b04,
+ 0xa649c8b468c6e808,
+ 0xb5b9a62dff0c4e45,
+ 0x1555b67fc7bbe73d
+ })), z.Square() * z);
+
+ Assert.IsTrue(point.IsOnCurve);
+ Assert.IsFalse(new G2Affine(point).IsTorsionFree);
+ var cleared_point = point.ClearCofactor();
+
+ Assert.IsTrue(cleared_point.IsOnCurve);
+ Assert.IsTrue(new G2Affine(cleared_point).IsTorsionFree);
+
+ // the generator (and the identity) are always on the curve,
+ // even after clearing the cofactor
+ var generator = G2Projective.Generator;
+ Assert.IsTrue(generator.ClearCofactor().IsOnCurve);
+ var id = G2Projective.Identity;
+ Assert.IsTrue(id.ClearCofactor().IsOnCurve);
+
+ // test the effect on q-torsion points multiplying by h_eff modulo |Scalar|
+ // h_eff % q = 0x2b116900400069009a40200040001ffff
+ byte[] h_eff_modq =
+ {
+ 0xff, 0xff, 0x01, 0x00, 0x04, 0x00, 0x02, 0xa4, 0x09, 0x90, 0x06, 0x00, 0x04, 0x90, 0x16,
+ 0xb1, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00
+ };
+ Assert.AreEqual(generator * h_eff_modq, generator.ClearCofactor());
+ Assert.AreEqual(cleared_point * h_eff_modq, cleared_point.ClearCofactor());
+ }
+
+ [TestMethod]
+ public void TestBatchNormalize()
+ {
+ var a = G2Projective.Generator.Double();
+ var b = a.Double();
+ var c = b.Double();
+
+ foreach (bool a_identity in new[] { false, true })
+ {
+ foreach (bool b_identity in new[] { false, true })
+ {
+ foreach (bool c_identity in new[] { false, true })
+ {
+ var v = new[] { a, b, c };
+ if (a_identity)
+ {
+ v[0] = G2Projective.Identity;
+ }
+ if (b_identity)
+ {
+ v[1] = G2Projective.Identity;
+ }
+ if (c_identity)
+ {
+ v[2] = G2Projective.Identity;
+ }
+
+ var t = new G2Affine[3];
+ var expected = new[] { new G2Affine(v[0]), new G2Affine(v[1]), new G2Affine(v[2]) };
+
+ G2Projective.BatchNormalize(v, t);
+
+ CollectionAssert.AreEqual(t, expected);
+ }
+ }
+ }
+ }
+}
diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Pairings.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Pairings.cs
new file mode 100644
index 0000000000..9019477a8f
--- /dev/null
+++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Pairings.cs
@@ -0,0 +1,69 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// UT_Pairings.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+namespace Neo.Cryptography.BLS12_381.Tests;
+
+[TestClass]
+public class UT_Pairings
+{
+ [TestMethod]
+ public void TestGtGenerator()
+ {
+ Assert.AreEqual(
+ Gt.Generator,
+ Bls12.Pairing(in G1Affine.Generator, in G2Affine.Generator)
+ );
+ }
+
+ [TestMethod]
+ public void TestBilinearity()
+ {
+ var a = Scalar.FromRaw(new ulong[] { 1, 2, 3, 4 }).Invert().Square();
+ var b = Scalar.FromRaw(new ulong[] { 5, 6, 7, 8 }).Invert().Square();
+ var c = a * b;
+
+ var g = new G1Affine(G1Affine.Generator * a);
+ var h = new G2Affine(G2Affine.Generator * b);
+ var p = Bls12.Pairing(in g, in h);
+
+ Assert.AreNotEqual(Gt.Identity, p);
+
+ var expected = new G1Affine(G1Affine.Generator * c);
+
+ Assert.AreEqual(p, Bls12.Pairing(in expected, in G2Affine.Generator));
+ Assert.AreEqual(
+ p,
+ Bls12.Pairing(in G1Affine.Generator, in G2Affine.Generator) * c
+ );
+ }
+
+ [TestMethod]
+ public void TestUnitary()
+ {
+ var g = G1Affine.Generator;
+ var h = G2Affine.Generator;
+ var p = -Bls12.Pairing(in g, in h);
+ var q = Bls12.Pairing(in g, -h);
+ var r = Bls12.Pairing(-g, in h);
+
+ Assert.AreEqual(p, q);
+ Assert.AreEqual(q, r);
+ }
+
+ [TestMethod]
+ public void TestMillerLoopResultDefault()
+ {
+ Assert.AreEqual(
+ Gt.Identity,
+ new MillerLoopResult(Fp12.One).FinalExponentiation()
+ );
+ }
+}
diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Scalar.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Scalar.cs
new file mode 100644
index 0000000000..03c1526a7b
--- /dev/null
+++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Scalar.cs
@@ -0,0 +1,411 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// UT_Scalar.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using static Neo.Cryptography.BLS12_381.ScalarConstants;
+
+namespace Neo.Cryptography.BLS12_381.Tests;
+
+[TestClass]
+public class UT_Scalar
+{
+ private static readonly Scalar LARGEST = new(new ulong[]
+ {
+ 0xffff_ffff_0000_0000,
+ 0x53bd_a402_fffe_5bfe,
+ 0x3339_d808_09a1_d805,
+ 0x73ed_a753_299d_7d48
+ });
+
+ [TestMethod]
+ public void TestInv()
+ {
+ // Compute -(q^{-1} mod 2^64) mod 2^64 by exponentiating
+ // by totient(2**64) - 1
+
+ var inv = 1ul;
+ for (int i = 0; i < 63; i++)
+ {
+ inv = unchecked(inv * inv);
+ inv = unchecked(inv * MODULUS_LIMBS_64[0]);
+ }
+ inv = unchecked(~inv + 1);
+
+ Assert.AreEqual(INV, inv);
+ }
+
+ [TestMethod]
+ public void TestToString()
+ {
+ Assert.AreEqual("0x0000000000000000000000000000000000000000000000000000000000000000", Scalar.Zero.ToString());
+ Assert.AreEqual("0x0000000000000000000000000000000000000000000000000000000000000001", Scalar.One.ToString());
+ Assert.AreEqual("0x1824b159acc5056f998c4fefecbc4ff55884b7fa0003480200000001fffffffe", R2.ToString());
+ }
+
+ [TestMethod]
+ public void TestEquality()
+ {
+ Assert.AreEqual(Scalar.Zero, Scalar.Zero);
+ Assert.AreEqual(Scalar.One, Scalar.One);
+ Assert.AreEqual(R2, R2);
+
+ Assert.AreNotEqual(Scalar.Zero, Scalar.One);
+ Assert.AreNotEqual(Scalar.One, R2);
+ }
+
+ [TestMethod]
+ public void TestToBytes()
+ {
+ CollectionAssert.AreEqual(new byte[]
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0
+ }, Scalar.Zero.ToArray());
+
+ CollectionAssert.AreEqual(new byte[]
+ {
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0
+ }, Scalar.One.ToArray());
+
+ CollectionAssert.AreEqual(new byte[]
+ {
+ 254, 255, 255, 255, 1, 0, 0, 0, 2, 72, 3, 0, 250, 183, 132, 88, 245, 79, 188, 236, 239,
+ 79, 140, 153, 111, 5, 197, 172, 89, 177, 36, 24
+ }, R2.ToArray());
+
+ CollectionAssert.AreEqual(new byte[]
+ {
+ 0, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+ 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115
+ }, (-Scalar.One).ToArray());
+ }
+
+ [TestMethod]
+ public void TestFromBytes()
+ {
+ Assert.AreEqual(Scalar.Zero, Scalar.FromBytes(new byte[]
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0
+ }));
+
+ Assert.AreEqual(Scalar.One, Scalar.FromBytes(new byte[]
+ {
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0
+ }));
+
+ Assert.AreEqual(R2, Scalar.FromBytes(new byte[]
+ {
+ 254, 255, 255, 255, 1, 0, 0, 0, 2, 72, 3, 0, 250, 183, 132, 88, 245, 79, 188, 236, 239,
+ 79, 140, 153, 111, 5, 197, 172, 89, 177, 36, 24
+ }));
+
+ // -1 should work
+ Scalar.FromBytes(new byte[]
+ {
+ 0, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+ 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115
+ });
+
+ // modulus is invalid
+ Assert.ThrowsException(() => Scalar.FromBytes(new byte[]
+ {
+ 1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+ 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115
+ }));
+
+ // Anything larger than the modulus is invalid
+ Assert.ThrowsException(() => Scalar.FromBytes(new byte[]
+ {
+ 2, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+ 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115
+ }));
+ Assert.ThrowsException(() => Scalar.FromBytes(new byte[]
+ {
+ 1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+ 216, 58, 51, 72, 125, 157, 41, 83, 167, 237, 115
+ }));
+ Assert.ThrowsException(() => Scalar.FromBytes(new byte[]
+ {
+ 1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+ 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 116
+ }));
+ }
+
+ [TestMethod]
+ public void TestFromBytesWideR2()
+ {
+ Assert.AreEqual(R2, Scalar.FromBytesWide(new byte[]
+ {
+ 254, 255, 255, 255, 1, 0, 0, 0, 2, 72, 3, 0, 250, 183, 132, 88, 245, 79, 188, 236, 239,
+ 79, 140, 153, 111, 5, 197, 172, 89, 177, 36, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ }));
+ }
+
+ [TestMethod]
+ public void TestFromBytesWideNegativeOne()
+ {
+ Assert.AreEqual(-Scalar.One, Scalar.FromBytesWide(new byte[]
+ {
+ 0, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+ 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }));
+ }
+
+ [TestMethod]
+ public void TestFromBytesWideMaximum()
+ {
+ Assert.AreEqual(new Scalar(new ulong[]
+ {
+ 0xc62c_1805_439b_73b1,
+ 0xc2b9_551e_8ced_218e,
+ 0xda44_ec81_daf9_a422,
+ 0x5605_aa60_1c16_2e79
+ }), Scalar.FromBytesWide(Enumerable.Repeat(0xff, 64).ToArray()));
+ }
+
+ [TestMethod]
+ public void TestZero()
+ {
+ Assert.AreEqual(Scalar.Zero, -Scalar.Zero);
+ Assert.AreEqual(Scalar.Zero, Scalar.Zero + Scalar.Zero);
+ Assert.AreEqual(Scalar.Zero, Scalar.Zero - Scalar.Zero);
+ Assert.AreEqual(Scalar.Zero, Scalar.Zero * Scalar.Zero);
+ }
+
+ [TestMethod]
+ public void TestAddition()
+ {
+ var tmp = LARGEST;
+ tmp += LARGEST;
+
+ Assert.AreEqual(new Scalar(new ulong[]
+ {
+ 0xffff_fffe_ffff_ffff,
+ 0x53bd_a402_fffe_5bfe,
+ 0x3339_d808_09a1_d805,
+ 0x73ed_a753_299d_7d48
+ }), tmp);
+
+ tmp = LARGEST;
+ tmp += new Scalar(new ulong[] { 1, 0, 0, 0 });
+
+ Assert.AreEqual(Scalar.Zero, tmp);
+ }
+
+ [TestMethod]
+ public void TestNegation()
+ {
+ var tmp = -LARGEST;
+
+ Assert.AreEqual(new Scalar(new ulong[] { 1, 0, 0, 0 }), tmp);
+
+ tmp = -Scalar.Zero;
+ Assert.AreEqual(Scalar.Zero, tmp);
+ tmp = -new Scalar(new ulong[] { 1, 0, 0, 0 });
+ Assert.AreEqual(LARGEST, tmp);
+ }
+
+ [TestMethod]
+ public void TestSubtraction()
+ {
+ var tmp = LARGEST;
+ tmp -= LARGEST;
+
+ Assert.AreEqual(Scalar.Zero, tmp);
+
+ tmp = Scalar.Zero;
+ tmp -= LARGEST;
+
+ var tmp2 = MODULUS;
+ tmp2 -= LARGEST;
+
+ Assert.AreEqual(tmp, tmp2);
+ }
+
+ [TestMethod]
+ public void TestMultiplication()
+ {
+ var cur = LARGEST;
+
+ for (int i = 0; i < 100; i++)
+ {
+ var tmp = cur;
+ tmp *= cur;
+
+ var tmp2 = Scalar.Zero;
+ foreach (bool b in cur
+ .ToArray()
+ .SelectMany(p => Enumerable.Range(0, 8).Select(q => ((p >> q) & 1) == 1))
+ .Reverse())
+ {
+ var tmp3 = tmp2;
+ tmp2 += tmp3;
+
+ if (b)
+ {
+ tmp2 += cur;
+ }
+ }
+
+ Assert.AreEqual(tmp, tmp2);
+
+ cur += LARGEST;
+ }
+ }
+
+ [TestMethod]
+ public void TestSquaring()
+ {
+ var cur = LARGEST;
+
+ for (int i = 0; i < 100; i++)
+ {
+ var tmp = cur;
+ tmp = tmp.Square();
+
+ var tmp2 = Scalar.Zero;
+ foreach (bool b in cur
+ .ToArray()
+ .SelectMany(p => Enumerable.Range(0, 8).Select(q => ((p >> q) & 1) == 1))
+ .Reverse())
+ {
+ var tmp3 = tmp2;
+ tmp2 += tmp3;
+
+ if (b)
+ {
+ tmp2 += cur;
+ }
+ }
+
+ Assert.AreEqual(tmp, tmp2);
+
+ cur += LARGEST;
+ }
+ }
+
+ [TestMethod]
+ public void TestInversion()
+ {
+ Assert.ThrowsException(() => Scalar.Zero.Invert());
+ Assert.AreEqual(Scalar.One, Scalar.One.Invert());
+ Assert.AreEqual(-Scalar.One, (-Scalar.One).Invert());
+
+ var tmp = R2;
+
+ for (int i = 0; i < 100; i++)
+ {
+ var tmp2 = tmp.Invert();
+ tmp2 *= tmp;
+
+ Assert.AreEqual(Scalar.One, tmp2);
+
+ tmp += R2;
+ }
+ }
+
+ [TestMethod]
+ public void TestInvertIsPow()
+ {
+ ulong[] q_minus_2 =
+ {
+ 0xffff_fffe_ffff_ffff,
+ 0x53bd_a402_fffe_5bfe,
+ 0x3339_d808_09a1_d805,
+ 0x73ed_a753_299d_7d48
+ };
+
+ var r1 = R;
+ var r2 = R;
+ var r3 = R;
+
+ for (int i = 0; i < 100; i++)
+ {
+ r1 = r1.Invert();
+ r2 = r2.PowVartime(q_minus_2);
+ r3 = r3.Pow(q_minus_2);
+
+ Assert.AreEqual(r1, r2);
+ Assert.AreEqual(r2, r3);
+ // Add R so we check something different next time around
+ r1 += R;
+ r2 = r1;
+ r3 = r1;
+ }
+ }
+
+ [TestMethod]
+ public void TestSqrt()
+ {
+ Assert.AreEqual(Scalar.Zero.Sqrt(), Scalar.Zero);
+
+ var square = new Scalar(new ulong[]
+ {
+ 0x46cd_85a5_f273_077e,
+ 0x1d30_c47d_d68f_c735,
+ 0x77f6_56f6_0bec_a0eb,
+ 0x494a_a01b_df32_468d
+ });
+
+ var none_count = 0;
+
+ for (int i = 0; i < 100; i++)
+ {
+ Scalar square_root;
+ try
+ {
+ square_root = square.Sqrt();
+ Assert.AreEqual(square, square_root * square_root);
+ }
+ catch (ArithmeticException)
+ {
+ none_count++;
+ }
+ square -= Scalar.One;
+ }
+
+ Assert.AreEqual(49, none_count);
+ }
+
+ [TestMethod]
+ public void TestFromRaw()
+ {
+ Assert.AreEqual(Scalar.FromRaw(new ulong[]
+ {
+ 0x0001_ffff_fffd,
+ 0x5884_b7fa_0003_4802,
+ 0x998c_4fef_ecbc_4ff5,
+ 0x1824_b159_acc5_056f
+ }), Scalar.FromRaw(Enumerable.Repeat(0xffff_ffff_ffff_ffff, 4).ToArray()));
+
+ Assert.AreEqual(Scalar.Zero, Scalar.FromRaw(MODULUS_LIMBS_64));
+
+ Assert.AreEqual(R, Scalar.FromRaw(new ulong[] { 1, 0, 0, 0 }));
+ }
+
+ [TestMethod]
+ public void TestDouble()
+ {
+ var a = Scalar.FromRaw(new ulong[]
+ {
+ 0x1fff_3231_233f_fffd,
+ 0x4884_b7fa_0003_4802,
+ 0x998c_4fef_ecbc_4ff3,
+ 0x1824_b159_acc5_0562
+ });
+
+ Assert.AreEqual(a + a, a.Double());
+ }
+}
diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/Usings.cs b/tests/Neo.Cryptography.BLS12_381.Tests/Usings.cs
new file mode 100644
index 0000000000..d35553e8ae
--- /dev/null
+++ b/tests/Neo.Cryptography.BLS12_381.Tests/Usings.cs
@@ -0,0 +1,12 @@
+// Copyright (C) 2015-2024 The Neo Project.
+//
+// Usings.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+global using Microsoft.VisualStudio.TestTools.UnitTesting;
diff --git a/tests/Neo.Json.UnitTests/Neo.Json.UnitTests.csproj b/tests/Neo.Json.UnitTests/Neo.Json.UnitTests.csproj
index a7671e8430..f8dec3e7d5 100644
--- a/tests/Neo.Json.UnitTests/Neo.Json.UnitTests.csproj
+++ b/tests/Neo.Json.UnitTests/Neo.Json.UnitTests.csproj
@@ -1,15 +1,13 @@
-
-
-
- enable
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ net7.0
+ enable
+ false
+
+
+
+
+
+
+
diff --git a/tests/Neo.Json.UnitTests/UT_JObject.cs b/tests/Neo.Json.UnitTests/UT_JObject.cs
index a3d5bc01b4..e3660a7d3e 100644
--- a/tests/Neo.Json.UnitTests/UT_JObject.cs
+++ b/tests/Neo.Json.UnitTests/UT_JObject.cs
@@ -117,7 +117,7 @@ public void TestGetNull()
[TestMethod]
public void TestClone()
{
- var bobClone = bob.Clone();
+ var bobClone = (JObject)bob.Clone();
bobClone.Should().NotBeSameAs(bob);
foreach (var key in bobClone.Properties.Keys)
{
diff --git a/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs b/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs
index 6102b88938..c6e818cdf7 100644
--- a/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs
+++ b/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs
@@ -70,7 +70,7 @@ public void TestValidTransaction()
senderProbe.ExpectMsg(p => p.Result == VerifyResult.Succeed);
senderProbe.Send(system.Blockchain, tx);
- senderProbe.ExpectMsg(p => p.Result == VerifyResult.AlreadyExists);
+ senderProbe.ExpectMsg(p => p.Result == VerifyResult.AlreadyInPool);
}
internal static StorageKey CreateStorageKey(byte prefix, byte[] key = null)
diff --git a/tests/Neo.UnitTests/Neo.UnitTests.csproj b/tests/Neo.UnitTests/Neo.UnitTests.csproj
index 909c3c53e3..2e61bedbcc 100644
--- a/tests/Neo.UnitTests/Neo.UnitTests.csproj
+++ b/tests/Neo.UnitTests/Neo.UnitTests.csproj
@@ -1,13 +1,15 @@
+ net7.0
true
+ false
-
+
@@ -21,11 +23,4 @@
-
-
-
-
-
-
-
diff --git a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs
index f019318d49..a0ab12fdc3 100644
--- a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs
+++ b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs
@@ -56,7 +56,7 @@ public void Runtime_GetNotifications_Test()
scriptHash2 = script.ToArray().ToScriptHash();
snapshot.DeleteContract(scriptHash2);
- ContractState contract = TestUtils.GetContract(script.ToArray(), TestUtils.CreateManifest("test", ContractParameterType.Any, ContractParameterType.Integer, ContractParameterType.Integer));
+ var contract = TestUtils.GetContract(script.ToArray(), TestUtils.CreateManifest("test", ContractParameterType.Any, ContractParameterType.Integer, ContractParameterType.Integer));
contract.Manifest.Abi.Events = new[]
{
new ContractEventDescriptor
@@ -294,8 +294,8 @@ public void TestRuntime_CheckWitness()
{
byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};
- KeyPair keyPair = new(privateKey);
- ECPoint pubkey = keyPair.PublicKey;
+ var keyPair = new KeyPair(privateKey);
+ var pubkey = keyPair.PublicKey;
var engine = GetEngine(true);
((Transaction)engine.ScriptContainer).Signers[0].Account = Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash();
@@ -316,8 +316,8 @@ public void TestRuntime_CheckWitness_Null_ScriptContainer()
{
byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};
- KeyPair keyPair = new(privateKey);
- ECPoint pubkey = keyPair.PublicKey;
+ var keyPair = new KeyPair(privateKey);
+ var pubkey = keyPair.PublicKey;
var engine = GetEngine();
@@ -328,7 +328,7 @@ public void TestRuntime_CheckWitness_Null_ScriptContainer()
public void TestRuntime_Log()
{
var engine = GetEngine(true);
- string message = "hello";
+ var message = "hello";
ApplicationEngine.Log += LogEvent;
engine.RuntimeLog(Encoding.UTF8.GetBytes(message));
((Transaction)engine.ScriptContainer).Script.Span.ToHexString().Should().Be(new byte[] { 0x01, 0x02, 0x03 }.ToHexString());
@@ -396,16 +396,16 @@ public void TestRuntime_GetCurrentSigners_SysCall()
public void TestCrypto_Verify()
{
var engine = GetEngine(true);
- IVerifiable iv = engine.ScriptContainer;
- byte[] message = iv.GetSignData(TestProtocolSettings.Default.Network);
+ var iv = engine.ScriptContainer;
+ var message = iv.GetSignData(TestProtocolSettings.Default.Network);
byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};
KeyPair keyPair = new(privateKey);
- ECPoint pubkey = keyPair.PublicKey;
- byte[] signature = Crypto.Sign(message, privateKey, pubkey.EncodePoint(false).Skip(1).ToArray());
+ var pubkey = keyPair.PublicKey;
+ var signature = Crypto.Sign(message, privateKey, pubkey.EncodePoint(false).Skip(1).ToArray());
engine.CheckSig(pubkey.EncodePoint(false), signature).Should().BeTrue();
- byte[] wrongkey = pubkey.EncodePoint(false);
+ var wrongkey = pubkey.EncodePoint(false);
wrongkey[0] = 5;
Assert.ThrowsException(() => engine.CheckSig(wrongkey, signature));
}
@@ -424,7 +424,7 @@ public void TestBlockchain_GetBlock()
NativeContract.Ledger.GetBlock(engine.Snapshot, UInt256.Zero).Should().BeNull();
- byte[] data1 = new byte[] { 0x01, 0x01, 0x01 ,0x01, 0x01, 0x01, 0x01, 0x01,
+ var data1 = new byte[] { 0x01, 0x01, 0x01 ,0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};
@@ -436,7 +436,7 @@ public void TestBlockchain_GetBlock()
public void TestBlockchain_GetTransaction()
{
var engine = GetEngine(true, true);
- byte[] data1 = new byte[] { 0x01, 0x01, 0x01 ,0x01, 0x01, 0x01, 0x01, 0x01,
+ var data1 = new byte[] { 0x01, 0x01, 0x01 ,0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};
@@ -469,7 +469,7 @@ public void TestBlockchain_GetTransactionHeight()
public void TestBlockchain_GetContract()
{
var engine = GetEngine(true, true);
- byte[] data1 = new byte[] { 0x01, 0x01, 0x01 ,0x01, 0x01,
+ var data1 = new byte[] { 0x01, 0x01, 0x01 ,0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01 };
@@ -638,7 +638,7 @@ public void TestStorageContext_AsReadOnly()
public void TestContract_Call()
{
var snapshot = TestBlockchain.GetTestSnapshot();
- string method = "method";
+ var method = "method";
var args = new VM.Types.Array { 0, 1 };
var state = TestUtils.GetContract(method, args.Count);
@@ -696,13 +696,13 @@ public void TestContract_Destroy()
[TestMethod]
public void TestContract_CreateStandardAccount()
{
- ECPoint pubkey = ECPoint.Parse("024b817ef37f2fc3d4a33fe36687e592d9f30fe24b3e28187dc8f12b3b3b2b839e", ECCurve.Secp256r1);
+ var pubkey = ECPoint.Parse("024b817ef37f2fc3d4a33fe36687e592d9f30fe24b3e28187dc8f12b3b3b2b839e", ECCurve.Secp256r1);
GetEngine().CreateStandardAccount(pubkey).ToArray().ToHexString().Should().Be("c44ea575c5f79638f0e73f39d7bd4b3337c81691");
}
public static void LogEvent(object sender, LogEventArgs args)
{
- Transaction tx = (Transaction)args.ScriptContainer;
+ var tx = (Transaction)args.ScriptContainer;
tx.Script = new byte[] { 0x01, 0x02, 0x03 };
}
@@ -711,7 +711,7 @@ private static ApplicationEngine GetEngine(bool hasContainer = false, bool hasSn
var tx = hasContainer ? TestUtils.GetTransaction(UInt160.Zero) : null;
var snapshot = hasSnapshot ? TestBlockchain.GetTestSnapshot() : null;
var block = hasBlock ? new Block { Header = new Header() } : null;
- ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, tx, snapshot, block, TestBlockchain.TheNeoSystem.Settings, gas: gas);
+ var engine = ApplicationEngine.Create(TriggerType.Application, tx, snapshot, block, TestBlockchain.TheNeoSystem.Settings, gas: gas);
if (addScript) engine.LoadScript(new byte[] { 0x01 });
return engine;
}
diff --git a/tests/Neo.VM.Tests/Neo.VM.Tests.csproj b/tests/Neo.VM.Tests/Neo.VM.Tests.csproj
index b86f27296a..1020d1c55a 100644
--- a/tests/Neo.VM.Tests/Neo.VM.Tests.csproj
+++ b/tests/Neo.VM.Tests/Neo.VM.Tests.csproj
@@ -2,7 +2,6 @@
net7.0
- 9.0
Neo.Test
true
false