diff --git a/.appveyor.yml b/.appveyor.yml
new file mode 100644
index 0000000..38aebc2
--- /dev/null
+++ b/.appveyor.yml
@@ -0,0 +1,86 @@
+version: '{build}'
+
+cache:
+- x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z -> .appveyor.yml
+- i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z -> .appveyor.yml
+- Nim -> .appveyor.yml
+
+matrix:
+ # We always want 32 and 64-bit compilation
+ fast_finish: false
+
+platform:
+ - x86
+ - x64
+
+install:
+ - setlocal EnableExtensions EnableDelayedExpansion
+
+ - IF "%PLATFORM%" == "x86" (
+ SET "MINGW_ARCHIVE=i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z" &
+ SET "MINGW_URL=https://sourceforge.net/projects/mingw-w64/files/Toolchains%%20targetting%%20Win32/Personal%%20Builds/mingw-builds/4.9.2/threads-win32/dwarf/i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z" &
+ SET "MINGW_DIR=mingw32"
+ ) ELSE (
+ IF "%PLATFORM%" == "x64" (
+ SET "MINGW_ARCHIVE=x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z" &
+ SET "MINGW_URL=https://sourceforge.net/projects/mingw-w64/files/Toolchains%%20targetting%%20Win64/Personal%%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z" &
+ SET "MINGW_DIR=mingw64"
+ ) else (
+ echo "Unknown platform"
+ )
+ )
+
+ - SET PATH=%CD%\%MINGW_DIR%\bin;%CD%\Nim\bin;%PATH%
+
+ # Unpack mingw
+ - IF NOT EXIST "%MINGW_ARCHIVE%" appveyor DownloadFile "%MINGW_URL%" -FileName "%MINGW_ARCHIVE%"
+ - 7z x -y "%MINGW_ARCHIVE%" > nul
+
+ # build nim from our own branch - this to avoid the day-to-day churn and
+ # regressions of the fast-paced Nim development while maintaining the
+ # flexibility to apply patches
+ - SET "NEED_REBUILD="
+
+ - IF NOT EXIST "Nim\\.git\\" (
+ git clone https://github.com/status-im/Nim.git
+ ) ELSE (
+ ( cd Nim ) &
+ ( git pull ) &
+ ( cd .. )
+ )
+
+ # Rebuild Nim if HEAD has moved or if we don't yet have a cached version
+ - IF NOT EXIST "Nim\\ver.txt" (
+ SET NEED_REBUILD=1
+ ) ELSE (
+ ( CD Nim ) &
+ ( git rev-parse HEAD > ..\\cur_ver.txt ) &
+ ( fc ver.txt ..\\cur_ver.txt || SET NEED_REBUILD=1 ) &
+ ( cd .. )
+ )
+
+ - IF NOT EXIST "Nim\\bin\\nim.exe" SET NEED_REBUILD=1
+ - IF NOT EXIST "Nim\\bin\\nimble.exe" SET NEED_REBUILD=1
+
+ # after building nim, wipe csources to save on cache space
+ - IF DEFINED NEED_REBUILD (
+ cd Nim &
+ ( IF EXIST "csources" rmdir /s /q csources ) &
+ git clone --depth 1 https://github.com/nim-lang/csources &
+ cd csources &
+ ( IF "%PLATFORM%" == "x64" ( build64.bat ) else ( build.bat ) ) &
+ cd .. &
+ bin\nim c koch &
+ koch boot -d:release &
+ koch nimble &
+ git rev-parse HEAD > ver.txt &
+ rmdir /s /q csources
+ )
+
+build_script:
+ - cd C:\projects\%APPVEYOR_PROJECT_SLUG%
+ - nimble install -y
+test_script:
+ - nimble test
+
+deploy: off
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..8e56f7e
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,39 @@
+language: c # or other C/C++ variants
+
+sudo: false
+
+# https://docs.travis-ci.com/user/caching/
+#
+# Caching the whole nim folder is better than relying on ccache - this way, we
+# skip the expensive bootstrap process and linking
+cache:
+ directories:
+ - nim
+
+os:
+ - linux
+ - osx
+
+install:
+ # build nim from our own branch - this to avoid the day-to-day churn and
+ # regressions of the fast-paced Nim development while maintaining the
+ # flexibility to apply patches
+ #
+ # check version of remote branch
+ - "export NIMVER=$(git ls-remote https://github.com/status-im/nim.git HEAD | cut -f 1)"
+
+ # after building nim, wipe csources to save on cache space
+ - "{ [ -f nim/$NIMVER/bin/nim ] && [ -f nim/$NIMVER/bin/nimble ] ; } ||
+ { rm -rf nim ;
+ mkdir -p nim ;
+ git clone --depth=1 https://github.com/status-im/nim.git nim/$NIMVER ;
+ cd nim/$NIMVER ;
+ sh build_all.sh ;
+ rm -rf csources ;
+ cd ../.. ;
+ }"
+ - "export PATH=$PWD/nim/$NIMVER/bin:$PATH"
+
+script:
+ - nimble install -y
+ - nimble test
diff --git a/LICENSE-APACHEv2 b/LICENSE-APACHEv2
new file mode 100644
index 0000000..782d1bf
--- /dev/null
+++ b/LICENSE-APACHEv2
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2018 Status Research & Development GmbH
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/LICENSE b/LICENSE-MIT
similarity index 93%
rename from LICENSE
rename to LICENSE-MIT
index b4c21de..8766e65 100644
--- a/LICENSE
+++ b/LICENSE-MIT
@@ -1,6 +1,6 @@
-MIT License
+The MIT License (MIT)
-Copyright (c) 2018 Status
+Copyright (c) 2018 Status Research & Development GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4c11aed
--- /dev/null
+++ b/README.md
@@ -0,0 +1,47 @@
+## BNCurve
+[![Build Status](https://travis-ci.org/status-im/nim-bncurve.svg?branch=master)](https://travis-ci.org/status-im/nim-bncurve)
+[![Build status](https://ci.appveyor.com/api/projects/status/hvv14l9v31mksam6/branch/master?svg=true)](https://ci.appveyor.com/project/nimbus/nim-bncurve/branch/master)
+[![License: Apache](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
+[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
+![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg)
+
+## Introduction
+This pure Nim implementation of Barreto-Naehrig pairing-friendly elliptic curve.
+
+This is a [pairing cryptography](https://en.wikipedia.org/wiki/Pairing-based_cryptography) library written in pure Nim. It makes use of the Barreto-Naehrig (BN) curve construction from [[BCTV2015]](https://eprint.iacr.org/2013/879.pdf) to provide two cyclic groups **G1** and **G2**, with an efficient bilinear pairing:
+
+*e: G1 × G2 → GT*
+
+This code is adaptation of (bn)[https://github.com/zcash-hackworks/bn.git] library.
+
+## Security warnings
+
+This library, like other pairing cryptography libraries implementing this construction, is not resistant to side-channel attacks.
+
+## Installation
+
+Add to your `.nimble` file:
+```
+requires "https://github.com/status-im/nim-bncurve"
+```
+
+or install it via
+```
+nimble install https://github.com/status-im/nim-bncurve
+```
+
+## Build and test
+
+```
+nimble install https://github.com/status-im/nim-bncurve
+nimble test
+```
+
+## License
+
+Licensed and distributed under either of
+
+* MIT license: [LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT
+* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
+
+at your option. This file may not be copied, modified, or distributed except according to those terms.
diff --git a/bncurve.nim b/bncurve.nim
new file mode 100644
index 0000000..fb232bd
--- /dev/null
+++ b/bncurve.nim
@@ -0,0 +1,10 @@
+# Nim Barreto-Naehrig pairing-friendly elliptic curve implementation
+# Copyright (c) 2018 Status Research & Development GmbH
+# Licensed under either of
+# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
+# * MIT license ([LICENSE-MIT](LICENSE-MIT))
+# at your option.
+# This file may not be copied, modified, or distributed except according to
+# those terms.
+import bncurve/[fields, groups]
+export fields, groups
diff --git a/bncurve.nimble b/bncurve.nimble
new file mode 100644
index 0000000..252557c
--- /dev/null
+++ b/bncurve.nimble
@@ -0,0 +1,26 @@
+packageName = "bncurve"
+version = "1.0.0"
+author = "Status Research & Development GmbH"
+description = "Barreto-Naehrig pairing-friendly elliptic curve implementation"
+license = "Apache License 2.0 or MIT"
+skipDirs = @["tests", "Nim", "nim"]
+
+### Dependencies
+
+requires "nim > 0.18.0"
+
+task test, "Run all tests":
+ exec "nim c -r tests/tarith"
+ exec "nim c -r -d:release tests/tarith"
+
+ exec "nim c -r tests/tfields"
+ exec "nim c -r -d:release tests/tfields"
+
+ exec "nim c -r tests/tgroups"
+ exec "nim c -r -d:release tests/tgroups"
+
+ exec "nim c -r tests/tpairing"
+ exec "nim c -r -d:release tests/tpairing"
+
+ exec "nim c -r tests/tvectors"
+ exec "nim c -r -d:release tests/tvectors"
diff --git a/bncurve/arith.nim b/bncurve/arith.nim
new file mode 100644
index 0000000..92d9a6b
--- /dev/null
+++ b/bncurve/arith.nim
@@ -0,0 +1,423 @@
+# Nim Barreto-Naehrig pairing-friendly elliptic curve implementation
+# Copyright (c) 2018 Status Research & Development GmbH
+# Licensed under either of
+# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
+# * MIT license ([LICENSE-MIT](LICENSE-MIT))
+# at your option.
+# This file may not be copied, modified, or distributed except according to
+# those terms.
+import options, endians
+import nimcrypto/[utils, sysrand]
+
+export options
+
+type
+ BNU256* = array[4, uint64]
+ BNU512* = array[8, uint64]
+
+proc setRandom*(a: var BNU512) {.inline, noinit.} =
+ ## Set value of integer ``a`` to random value.
+ let ret = randomBytes(a)
+ doAssert(ret == 8)
+
+proc random*(t: typedesc[BNU512]): BNU512 {.inline, noinit.} =
+ ## Return random 512bit integer.
+ setRandom(result)
+
+proc setZero*(a: var BNU256) {.inline, noinit.} =
+ ## Set value of integer ``a`` to zero.
+ a[0] = 0'u64
+ a[1] = 0'u64
+ a[2] = 0'u64
+ a[3] = 0'u64
+
+proc setOne*(a: var BNU256) {.inline, noinit.} =
+ ## Set value of integer ``a`` to one.
+ a[0] = 1'u64
+ a[1] = 0'u64
+ a[2] = 0'u64
+ a[3] = 0'u64
+
+proc zero*(t: typedesc[BNU256]): BNU256 {.inline, noinit.} =
+ ## Return zero 256bit integer.
+ setZero(result)
+
+proc one*(t: typedesc[BNU256]): BNU256 {.inline, noinit.} =
+ ## Return one 256bit integer.
+ setOne(result)
+
+proc isZero*(a: BNU256): bool {.inline, noinit.} =
+ ## Check if integer ``a`` is zero.
+ (a[0] == 0'u64) and (a[1] == 0'u64) and (a[2] == 0'u64) and (a[3] == 0'u64)
+
+proc setBit*(a: var openarray[uint64], n: int,
+ to: bool): bool {.inline, noinit.} =
+ ## Set bit of integer ``a`` at position ``n`` to value ``to``.
+ if n >= 256:
+ return
+ let part = n shr 6
+ let bit = n - (part shl 6)
+ if to:
+ a[part] = a[part] or (1'u64 shl bit)
+ else:
+ a[part] = a[part] and not(1'u64 shl bit)
+ result = true
+
+proc getBit*(a: openarray[uint64], n: int): bool {.inline, noinit.} =
+ ## Get value of bit at position ``n`` in integer ``a``.
+ let part = n shr 6
+ let bit = n - (part shl 6)
+ result = ((a[part] and (1'u64 shl bit)) != 0)
+
+template splitU64(n: uint64, hi, lo: untyped) =
+ ## Split 64bit unsigned integer to 32bit parts
+ hi = n shr 32
+ lo = n and 0xFFFF_FFFF'u64
+
+template combineU64(hi, lo: untyped): uint64 =
+ ## Combine 64bit unsigned integer from 32bit parts
+ (hi shl 32) or lo
+
+proc div2*(a: var BNU256) {.inline, noinit.} =
+ ## Divide integer ``a`` in place by ``2``.
+ var t = a[3] shl 63
+ a[3] = a[3] shr 1
+ let b = a[2] shl 63
+ a[2] = a[2] shr 1
+ a[2] = a[2] or t
+ t = a[1] shl 63
+ a[1] = a[1] shr 1
+ a[1] = a[1] or b
+ a[0] = a[0] shr 1
+ a[0] = a[0] or t
+
+proc mul2*(a: var BNU256) {.inline, noinit.} =
+ ## Multiply integer ``a`` in place by ``2``.
+ var last = 0'u64
+ for i in a.mitems():
+ let tmp = i shr 63
+ i = i shl 1
+ i = i or last
+ last = tmp
+
+proc adc(a, b: uint64, carry: var uint64): uint64 {.inline, noinit.} =
+ ## Calculate ``a + b`` and return result, set ``carry`` to addition
+ ## operation carry.
+ var a0, a1, b0, b1, c, r0, r1: uint64
+ splitU64(a, a1, a0)
+ splitU64(b, b1, b0)
+ let tmp0 = a0 + b0 + carry
+ splitU64(tmp0, c, r0)
+ let tmp1 = a1 + b1 + c
+ splitU64(tmp1, c, r1)
+ carry = c
+ result = combineU64(r1, r0)
+
+proc addNoCarry*(a: var BNU256, b: BNU256) {.inline, noinit.} =
+ ## Calculate integer addition ``a = a + b``.
+ var carry = 0'u64
+ a[0] = adc(a[0], b[0], carry)
+ a[1] = adc(a[1], b[1], carry)
+ a[2] = adc(a[2], b[2], carry)
+ a[3] = adc(a[3], b[3], carry)
+ assert(carry == 0)
+
+proc subNoBorrow*(a: var BNU256, b: BNU256) {.inline, noinit.} =
+ ## Calculate integer substraction ``a = a - b``.
+ proc sbb(a: uint64, b: uint64,
+ borrow: var uint64): uint64 {.inline, noinit.}=
+ var a0, a1, b0, b1, t0, r0, r1: uint64
+ splitU64(a, a1, a0)
+ splitU64(b, b1, b0)
+ let tmp0 = (1'u64 shl 32) + a0 - b0 - borrow
+ splitU64(tmp0, t0, r0)
+ let tmp1 = (1'u64 shl 32) + a1 - b1 - uint64(t0 == 0'u64)
+ splitU64(tmp1, t0, r1)
+ borrow = uint64(t0 == 0)
+ result = combineU64(r1, r0)
+ var borrow = 0'u64
+ a[0] = sbb(a[0], b[0], borrow)
+ a[1] = sbb(a[1], b[1], borrow)
+ a[2] = sbb(a[2], b[2], borrow)
+ a[3] = sbb(a[3], b[3], borrow)
+ assert(borrow == 0)
+
+proc macDigit(acc: var openarray[uint64], pos: int, b: openarray[uint64],
+ c: uint64) {.noinit.} =
+ proc macWithCarry(a, b, c: uint64, carry: var uint64): uint64 {.noinit.} =
+ var
+ bhi, blo, chi, clo, ahi, alo, carryhi, carrylo: uint64
+ xhi, xlo, yhi, ylo, zhi, zlo, rhi, rlo: uint64
+ splitU64(b, bhi, blo)
+ splitU64(c, chi, clo)
+ splitU64(a, ahi, alo)
+ splitU64(carry, carryhi, carrylo)
+ splitU64(blo * clo + alo + carrylo, xhi, xlo)
+ splitU64(blo * chi, yhi, ylo)
+ splitU64(bhi * clo, zhi, zlo)
+ splitU64(x_hi + y_lo + z_lo + a_hi + carry_hi, rhi, rlo)
+ carry = (bhi * chi) + rhi + yhi + zhi
+ result = combineU64(rlo, xlo)
+
+ if c == 0'u64:
+ return
+ var carry = 0'u64
+ for i in pos.. b``, ``0`` if ``a == b``.
+ for i in countdown(3, 0):
+ if a[i] < b[i]:
+ return -1
+ elif a[i] > b[i]:
+ return 1
+ return 0
+
+proc `<`*(a: BNU256, b: BNU256): bool {.noinit, inline.} =
+ ## Return true if `a < b`.
+ result = (compare(a, b) == -1)
+
+proc `<=`*(a: BNU256, b: BNU256): bool {.noinit, inline.} =
+ ## Return true if `a <= b`.
+ result = (compare(a, b) <= 0)
+
+proc `==`*(a: BNU256, b: BNU256): bool {.noinit, inline.} =
+ ## Return true if `a == b`.
+ result = (compare(a, b) == 0)
+
+proc mul*(a: var BNU256, b: BNU256, modulo: BNU256,
+ inv: uint64) {.inline, noinit.} =
+ ## Multiply integer ``a`` by ``b`` (mod ``modulo``) via the Montgomery
+ ## multiplication method.
+ mulReduce(a, b, modulo, inv)
+ if a >= modulo:
+ subNoBorrow(a, modulo)
+
+proc add*(a: var BNU256, b: BNU256, modulo: BNU256) {.inline, noinit.} =
+ ## Add integer ``b`` from integer ``a`` (mod ``modulo``).
+ addNoCarry(a, b)
+ if a >= modulo:
+ subNoBorrow(a, modulo)
+
+proc sub*(a: var BNU256, b: BNU256, modulo: BNU256) {.inline, noinit.} =
+ ## Subtract integer ``b`` from integer ``a`` (mod ``modulo``).
+ if a < b:
+ addNoCarry(a, modulo)
+ subNoBorrow(a, b)
+
+proc neg*(a: var BNU256, modulo: BNU256) {.inline.} =
+ ## Turn integer ``a`` into its additive inverse (mod ``modulo``).
+ if a > BNU256.zero():
+ var tmp = modulo
+ subNoBorrow(tmp, a)
+ a = tmp
+
+proc isEven*(a: BNU256): bool {.inline, noinit.} =
+ ## Check if ``a`` is even.
+ ((a[0] and 1'u64) == 0'u64)
+
+proc divrem*(a: BNU512, modulo: BNU256, reminder: var BNU256): Option[BNU256] =
+ ## Divides integer ``a`` by ``modulo``, set ``remainder`` to reminder and, if
+ ## possible, return quotient smaller than the modulus.
+ var q: BNU256
+ reminder.setZero()
+ result = some[BNU256](q)
+ for i in countdown(511, 0):
+ mul2(reminder)
+ let ret = reminder.setBit(0, a.getBit(i))
+ assert ret
+ if reminder >= modulo:
+ subNoBorrow(reminder, modulo)
+ if result.isSome():
+ if not q.setBit(i, true):
+ result = none[BNU256]()
+ else:
+ result = some[BNU256](q)
+
+ if result.isSome() and result.get() >= modulo:
+ result = none[BNU256]()
+
+proc into*(t: typedesc[BNU512], c1: BNU256,
+ c0: BNU256, modulo: BNU256): BNU512 =
+ ## Return 512bit integer of value ``c1 * modulo + c0``.
+ macDigit(result, 0, modulo, c1[0])
+ macDigit(result, 1, modulo, c1[1])
+ macDigit(result, 2, modulo, c1[2])
+ macDigit(result, 3, modulo, c1[3])
+ var carry = 0'u64
+ for i in 0.. i:
+ result[i] = adc(result[i], c0[i], carry)
+ elif carry != 0'u64:
+ result[i] = adc(result[i], 0'u64, carry)
+ else:
+ break
+ assert(carry == 0'u64)
+
+proc fromBytes*(dst: var BNU256, src: openarray[byte]): bool =
+ ## Create 256bit integer from big-endian bytes representation ``src``.
+ ## Returns ``true`` if ``dst`` was successfully initialized, ``false``
+ ## otherwise.
+ var buffer: array[32, byte]
+ if len(src) == 0:
+ return false
+ let length = if len(src) > 32: 32 else: len(src)
+ copyMem(addr buffer[0], unsafeAddr src[0], length)
+ bigEndian64(addr dst[0], addr buffer[3 * sizeof(uint64)])
+ bigEndian64(addr dst[1], addr buffer[2 * sizeof(uint64)])
+ bigEndian64(addr dst[2], addr buffer[1 * sizeof(uint64)])
+ bigEndian64(addr dst[3], addr buffer[0 * sizeof(uint64)])
+ result = true
+
+proc fromBytes*(dst: var BNU512, src: openarray[byte]): bool {.noinit.} =
+ ## Create 512bit integer form big-endian bytes representation ``src``.
+ ## Returns ``true`` if ``dst`` was successfully initialized, ``false``
+ ## otherwise.
+ var buffer: array[64, byte]
+ if len(src) == 0:
+ return false
+ let length = if len(src) > 64: 64 else: len(src)
+ copyMem(addr buffer[0], unsafeAddr src[0], length)
+ bigEndian64(addr dst[0], addr buffer[7 * sizeof(uint64)])
+ bigEndian64(addr dst[1], addr buffer[6 * sizeof(uint64)])
+ bigEndian64(addr dst[2], addr buffer[5 * sizeof(uint64)])
+ bigEndian64(addr dst[3], addr buffer[4 * sizeof(uint64)])
+ bigEndian64(addr dst[4], addr buffer[3 * sizeof(uint64)])
+ bigEndian64(addr dst[5], addr buffer[2 * sizeof(uint64)])
+ bigEndian64(addr dst[6], addr buffer[1 * sizeof(uint64)])
+ bigEndian64(addr dst[7], addr buffer[0 * sizeof(uint64)])
+ result = true
+
+proc fromHexString*(dst: var BNU256, src: string): bool {.inline, noinit.} =
+ ## Create 256bit integer from big-endian hexadecimal string
+ ## representation ``src``.
+ ## Returns ``true`` if ``dst`` was successfully initialized, ``false``
+ ## otherwise.
+ result = dst.fromBytes(fromHex(src))
+
+proc toBytes*(src: BNU256, dst: var openarray[byte]): bool {.noinit.} =
+ ## Convert 256bit integer ``src`` to big-endian bytes representation.
+ ## Return ``true`` if ``dst`` was successfully set, ``false`` otherwise.
+ if len(dst) < 4 * sizeof(uint64):
+ return false
+ bigEndian64(addr dst[0 * sizeof(uint64)], unsafeAddr src[3])
+ bigEndian64(addr dst[1 * sizeof(uint64)], unsafeAddr src[2])
+ bigEndian64(addr dst[2 * sizeof(uint64)], unsafeAddr src[1])
+ bigEndian64(addr dst[3 * sizeof(uint64)], unsafeAddr src[0])
+ result = true
+
+proc toBytes*(src: BNU512, dst: var openarray[byte]): bool {.noinit.} =
+ ## Convert 512bit integer ``src`` to big-endian bytes representation.
+ ## Return ``true`` if ``dst`` was successfully set, ``false`` otherwise.
+ if len(dst) < 8 * sizeof(uint64):
+ return false
+ bigEndian64(addr dst[0 * sizeof(uint64)], unsafeAddr src[7])
+ bigEndian64(addr dst[1 * sizeof(uint64)], unsafeAddr src[6])
+ bigEndian64(addr dst[2 * sizeof(uint64)], unsafeAddr src[5])
+ bigEndian64(addr dst[3 * sizeof(uint64)], unsafeAddr src[4])
+ bigEndian64(addr dst[4 * sizeof(uint64)], unsafeAddr src[3])
+ bigEndian64(addr dst[5 * sizeof(uint64)], unsafeAddr src[2])
+ bigEndian64(addr dst[6 * sizeof(uint64)], unsafeAddr src[1])
+ bigEndian64(addr dst[7 * sizeof(uint64)], unsafeAddr src[0])
+ result = true
+
+proc toString*(src: BNU256, lowercase = true): string =
+ ## Convert 256bit integer ``src`` to big-endian hexadecimal representation.
+ var a: array[4 * sizeof(uint64), byte]
+ discard src.toBytes(a)
+ result = a.toHex(lowercase)
+
+proc toString*(src: BNU512, lowercase = true): string =
+ ## Convert 512bit integer ``src`` to big-endian hexadecimal representation.
+ var a: array[8 * sizeof(uint64), byte]
+ discard src.toBytes(a)
+ result = a.toHex(lowercase)
+
+proc `$`*(src: BNU256): string =
+ ## Return hexadecimal string representation of integer ``src``.
+ result = toString(src, false)
+
+proc `$`*(src: BNU512): string =
+ ## Return hexadecimal string representation of integer ``src``.
+ result = toString(src, false)
+
+proc setRandom*(a: var BNU256, modulo: BNU256) {.noinit, inline.} =
+ ## Set value of integer ``a`` to random value (mod ``modulo``).
+ var r = BNU512.random()
+ discard divrem(r, modulo, a)
+
+proc random*(t: typedesc[BNU256], modulo: BNU256): BNU256 {.noinit, inline.} =
+ ## Return random 256bit integer (mod ``modulo``).
+ result.setRandom(modulo)
+
+proc invert*(a: var BNU256, modulo: BNU256) =
+ ## Turn integer ``a`` into its multiplicative inverse (mod ``modulo``).
+ var u = a
+ var v = modulo
+ var b = BNU256.one()
+ var c = BNU256.zero()
+
+ while u != BNU256.one() and v != BNU256.one():
+ while u.isEven():
+ u.div2()
+ if b.isEven():
+ b.div2()
+ else:
+ b.addNoCarry(modulo)
+ b.div2()
+ while v.isEven():
+ v.div2()
+ if c.isEven():
+ c.div2()
+ else:
+ c.addNoCarry(modulo)
+ c.div2()
+ if u >= v:
+ u.subNoBorrow(v)
+ b.sub(c, modulo)
+ else:
+ v.subNoBorrow(u)
+ c.sub(b, modulo)
+
+ if u == BNU256.one():
+ a = b
+ else:
+ a = c
+
+iterator bits*(a: BNU256): bool =
+ ## Iterate over bits of integer ``a``.
+ for i in countdown(255, 0):
+ yield a.getBit(i)
+
+iterator pairs*(a: BNU256): tuple[key: int, value: bool] =
+ ## Iterate over index and bit value of integer ``a``.
+ var k = 0
+ for i in countdown(255, 0):
+ yield (k, a.getBit(i))
+ inc(k)
diff --git a/bncurve/fields.nim b/bncurve/fields.nim
new file mode 100644
index 0000000..945bafe
--- /dev/null
+++ b/bncurve/fields.nim
@@ -0,0 +1,10 @@
+# Nim Barreto-Naehrig pairing-friendly elliptic curve implementation
+# Copyright (c) 2018 Status Research & Development GmbH
+# Licensed under either of
+# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
+# * MIT license ([LICENSE-MIT](LICENSE-MIT))
+# at your option.
+# This file may not be copied, modified, or distributed except according to
+# those terms.
+import options, arith, fp, fq2, fq6, fq12
+export options, arith, fp, fq2, fq6, fq12
diff --git a/bncurve/fp.nim b/bncurve/fp.nim
new file mode 100644
index 0000000..4aa635b
--- /dev/null
+++ b/bncurve/fp.nim
@@ -0,0 +1,193 @@
+# Nim Barreto-Naehrig pairing-friendly elliptic curve implementation
+# Copyright (c) 2018 Status Research & Development GmbH
+# Licensed under either of
+# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
+# * MIT license ([LICENSE-MIT](LICENSE-MIT))
+# at your option.
+# This file may not be copied, modified, or distributed except according to
+# those terms.
+import arith, options
+
+template fieldImplementation(finame, fimodulus, firsquared, fircubed,
+ fionep, fiinv: untyped): untyped {.dirty.} =
+ type finame* = distinct BNU256
+
+ proc setZero*(dst: var finame) {.noinit, inline.} =
+ ## Set ``zero`` representation in Fp to ``dst``.
+ dst = finame([0'u64, 0'u64, 0'u64, 0'u64])
+
+ proc isZero*(src: finame): bool {.noinit, inline.} =
+ ## Check if ``src`` is ``zero``.
+ result = BNU256(src).isZero()
+
+ proc setOne*(dst: var finame) {.noinit, inline.}=
+ ## Set ``one`` representation in Fp to ``dst``.
+ dst = finame(fionep)
+
+ proc zero*(t: typedesc[finame]): finame {.noinit, inline.} =
+ ## Return ``zero`` representation in Fp.
+ result.setZero()
+
+ proc one*(t: typedesc[finame]): finame {.noinit, inline.} =
+ ## Return ``one`` representation in Fp.
+ result.setOne()
+
+ proc modulus*(t: typedesc[finame]): BNU256 {.noinit, inline} =
+ ## Return ``Fp`` modulus.
+ result = fimodulus
+
+ proc setRandom*(dst: var finame) {.noinit, inline.} =
+ ## Set ``dst`` to random value
+ var a = BNU256.random(fimodulus)
+ dst = finame(a)
+
+ proc random*(t: typedesc[finame]): finame {.noinit, inline.} =
+ ## Return random ``Fp``.
+ result.setRandom()
+
+ proc `+`*(x, y: finame): finame {.noinit, inline.} =
+ ## Return result of ``x + y``.
+ result = x
+ add(BNU256(result), BNU256(y), fimodulus)
+
+ proc `+=`*(x: var finame, y: finame) {.noinit, inline.} =
+ ## Perform inplace addition ``x = x + y``.
+ add(BNU256(x), BNU256(y), fimodulus)
+
+ proc `-`*(x, y: finame): finame {.noinit, inline.} =
+ ## Return result of ``x - y``.
+ result = x
+ sub(BNU256(result), BNU256(y), fimodulus)
+
+ proc `-=`*(x: var finame, y: finame) {.noinit, inline.} =
+ ## Perform inplace substraction ``x = x - y``.
+ sub(BNU256(x), BNU256(y), fimodulus)
+
+ proc `*`*(x, y: finame): finame {.noinit, inline.} =
+ ## Return result of ``x * y``.
+ result = x
+ mul(BNU256(result), BNU256(y), fimodulus, fiinv)
+
+ proc `*=`*(x: var finame, y: finame) {.noinit, inline.} =
+ ## Perform inplace multiplication ``x = x * y``.
+ mul(BNU256(x), BNU256(y), fimodulus, fiinv)
+
+ proc `-`*(x: finame): finame {.noinit, inline.} =
+ ## Negotiation of ``x``.
+ result = x
+ neg(BNU256(result), fimodulus)
+
+ proc fromString*(t: typedesc[finame],
+ number: string): finame {.noinit, inline.} =
+ ## Convert decimal string representation to ``Fp``.
+ var numis = newSeq[finame](11)
+ var acc = finame.zero()
+ for number in numis.mitems():
+ number = acc
+ acc += finame.one()
+ result.setZero()
+ for ch in number:
+ assert(ch in {'0'..'9'})
+ let idx = ord(ch) - ord('0')
+ result *= numis[10]
+ result += numis[idx]
+
+ proc into*(t: typedesc[BNU256], num: finame): BNU256 =
+ ## Convert Fp ``num`` to 256bit integer.
+ result = BNU256(num)
+ mul(result, BNU256.one(), BNU256(fimodulus), fiinv)
+
+ proc init*(t: typedesc[finame], num: BNU256): Option[finame] =
+ ## Initialize Fp from 256bit integer ``num``.
+ if num >= BNU256(fimodulus):
+ result = none[finame]()
+ else:
+ var res: finame
+ res = finame(num)
+ mul(BNU256(res), BNU256(firsquared), BNU256(fimodulus), fiinv)
+ result = some[finame](res)
+
+ proc fromBytes*(dst: var finame, src: openarray[byte]): bool {.noinit.} =
+ ## Create integer FP/FQ from big-endian bytes representation ``src``.
+ ## Returns ``true`` if ``dst`` was successfully initialized, ``false``
+ ## otherwise.
+ result = false
+ var bn: BNU256
+ if bn.fromBytes(src):
+ var optr = finame.init(bn)
+ if isSome(optr):
+ dst = optr.get()
+ result = true
+
+ proc toBytes*(src: finame,
+ dst: var openarray[byte]): bool {.noinit, inline.} =
+ ## Encode integer FP/FQ to big-endian bytes representation ``dst``.
+ ## Returns ``true`` if integer was successfully serialized, ``false``
+ ## otherwise.
+ result = BNU256.into(src).toBytes(dst)
+
+ proc fromHexString*(dst: var finame,
+ src: string): bool {.noinit, inline.} =
+ ## Create integer FP/FQ from hexadecimal string representation ``src``.
+ ## Returns ``true`` if ``dst`` was successfully initialized, ``false``
+ ## otherwise.
+ result = false
+ var bn: BNU256
+ if bn.fromHexString(src):
+ var optr = finame.init(bn)
+ if isSome(optr):
+ dst = optr.get()
+ result = true
+
+ proc inverse*(num: finame): Option[finame] =
+ ## Perform inversion of ``Fp``.
+ if num.isZero():
+ result = none[finame]()
+ else:
+ var res: BNU256
+ res = BNU256(num)
+ invert(res, BNU256(fimodulus))
+ mul(res, BNU256(fircubed), BNU256(fimodulus), fiinv)
+ result = some[finame](finame(res))
+
+ proc `==`*(a: finame, b: finame): bool {.inline, noinit.} =
+ ## Return ``true`` if ``a == b``.
+ result = (BNU256(a) == BNU256(b))
+
+ proc squared*(a: finame): finame {.inline, noinit.} =
+ ## Return ``a * a``.
+ result = a * a
+
+ proc pow*(a: finame, by: BNU256): finame {.inline, noinit.} =
+ ## Return ``a^by``.
+ result = finame.one()
+ for i in by.bits():
+ result = result.squared()
+ if i:
+ result *= a
+
+fieldImplementation(
+ FR,
+ [0x43e1f593f0000001'u64, 0x2833e84879b97091'u64,
+ 0xb85045b68181585d'u64, 0x30644e72e131a029'u64],
+ [0x1bb8e645ae216da7'u64, 0x53fe3ab1e35c59e3'u64,
+ 0x8c49833d53bb8085'u64, 0x0216d0b17f4e44a5'u64],
+ [0x5e94d8e1b4bf0040'u64, 0x2a489cbe1cfbb6b8'u64,
+ 0x893cc664a19fcfed'u64, 0x0cf8594b7fcc657c'u64],
+ [0xac96341c4ffffffb'u64, 0x36fc76959f60cd29'u64,
+ 0x666ea36f7879462e'u64, 0xe0a77c19a07df2f'u64],
+ 0xc2e1f593efffffff'u64
+)
+
+fieldImplementation(
+ FQ,
+ [0x3c208c16d87cfd47'u64, 0x97816a916871ca8d'u64,
+ 0xb85045b68181585d'u64, 0x30644e72e131a029'u64],
+ [0xf32cfc5b538afa89'u64, 0xb5e71911d44501fb'u64,
+ 0x47ab1eff0a417ff6'u64, 0x06d89f71cab8351f'u64],
+ [0xb1cd6dafda1530df'u64, 0x62f210e6a7283db6'u64,
+ 0xef7f0b0c0ada0afb'u64, 0x20fd6e902d592544'u64],
+ [0xd35d438dc58f0d9d'u64, 0xa78eb28f5c70b3d'u64,
+ 0x666ea36f7879462c'u64, 0xe0a77c19a07df2f'u64],
+ 0x87d20782e4866389'u64
+)
diff --git a/bncurve/fq12.nim b/bncurve/fq12.nim
new file mode 100644
index 0000000..4fad0c8
--- /dev/null
+++ b/bncurve/fq12.nim
@@ -0,0 +1,309 @@
+# Nim Barreto-Naehrig pairing-friendly elliptic curve implementation
+# Copyright (c) 2018 Status Research & Development GmbH
+# Licensed under either of
+# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
+# * MIT license ([LICENSE-MIT](LICENSE-MIT))
+# at your option.
+# This file may not be copied, modified, or distributed except according to
+# those terms.
+import options
+import fq6, fq2, fp, arith
+
+const frobeniusCoeffsC1: array[4, FQ2] = [
+ FQ2.one(),
+ FQ2(
+ c0: Fq([12653890742059813127'u64, 14585784200204367754'u64,
+ 1278438861261381767'u64, 212598772761311868'u64]),
+ c1: Fq([11683091849979440498'u64, 14992204589386555739'u64,
+ 15866167890766973222'u64, 1200023580730561873'u64])
+ ),
+ FQ2(
+ c0: Fq([14595462726357228530'u64, 17349508522658994025'u64,
+ 1017833795229664280'u64, 299787779797702374'u64]),
+ c1: Fq.zero()
+ ),
+ FQ2(
+ c0: Fq([3914496794763385213'u64, 790120733010914719'u64,
+ 7322192392869644725'u64, 581366264293887267'u64]),
+ c1: Fq([12817045492518885689'u64, 4440270538777280383'u64,
+ 11178533038884588256'u64, 2767537931541304486'u64])
+ )
+]
+
+type
+ FQ12* = object
+ c0*: FQ6
+ c1*: FQ6
+
+proc init*(c0, c1: FQ6): FQ12 {.inline, noinit.} =
+ result.c0 = c0
+ result.c1 = c1
+
+proc zero*(t: typedesc[FQ12]): FQ12 {.inline, noinit.} =
+ result.c0 = FQ6.zero()
+ result.c1 = FQ6.zero()
+
+proc one*(t: typedesc[FQ12]): FQ12 {.inline, noinit.} =
+ result.c0 = FQ6.one()
+ result.c1 = FQ6.zero()
+
+proc random*(t: typedesc[FQ12]): FQ12 {.inline, noinit.} =
+ result.c0 = FQ6.random()
+ result.c1 = FQ6.random()
+
+proc isZero*(x: FQ12): bool {.inline, noinit.} =
+ result = (x.c0.isZero() and x.c1.isZero())
+
+proc squared*(x: FQ12): FQ12 {.inline, noinit.} =
+ let ab = x.c0 * x.c1
+ result.c0 = (x.c1.mulByNonresidue() + x.c0) * (x.c0 + x.c1) - ab -
+ ab.mulByNonresidue()
+ result.c1 = ab + ab
+
+proc inverse*(x: FQ12): Option[FQ12] {.inline, noinit.} =
+ let opt = (x.c0.squared() - (x.c1.squared().mulByNonresidue())).inverse()
+ if isSome(opt):
+ let tmp = opt.get()
+ result = some[FQ12](FQ12(c0: x.c0 * tmp, c1: -(x.c1 * tmp)))
+ else:
+ result = none[FQ12]()
+
+proc `+`*(x, y: FQ12): FQ12 {.noinit, inline.} =
+ ## Return result of ``x + y``.
+ result.c0 = x.c0 + y.c0
+ result.c1 = x.c1 + y.c1
+
+proc `+=`*(x: var FQ12, y: FQ12) {.noinit, inline.} =
+ ## Perform inplace addition ``x = x + y``.
+ x.c0 += y.c0
+ x.c1 += y.c1
+
+proc `-`*(x, y: FQ12): FQ12 {.noinit, inline.} =
+ ## Return result of ``x - y``.
+ result.c0 = x.c0 - y.c0
+ result.c1 = x.c1 - y.c1
+
+proc `-=`*(x: var FQ12, y: FQ12) {.noinit, inline.} =
+ ## Perform inplace substraction ``x = x - y``.
+ x.c0 -= y.c0
+ x.c1 -= y.c1
+
+proc `*`*(x, y: FQ12): FQ12 {.noinit, inline.} =
+ ## Return result of ``x * y``.
+ let aa = x.c0 * y.c0
+ let bb = x.c1 * y.c1
+ result.c0 = bb.mulByNonresidue() + aa
+ result.c1 = (x.c0 + x.c1) * (y.c0 + y.c1) - aa - bb
+
+proc `*=`*(x: var FQ12, y: FQ12) {.noinit, inline.} =
+ ## Perform inplace multiplication ``x = x * y``.
+ let aa = x.c0 * y.c0
+ let bb = x.c1 * y.c1
+ let cc = x.c0 + x.c1
+ x.c0 = bb.mulByNonresidue() + aa
+ x.c1 = cc * (y.c0 + y.c1) - aa - bb
+
+proc `-`*(x: FQ12): FQ12 {.noinit, inline.} =
+ ## Negotiation of ``x``.
+ result.c0 = -x.c0
+ result.c1 = -x.c1
+
+proc pow*(x: FQ12, by: BNU256): FQ12 {.noinit.} =
+ result = FQ12.one()
+ for i in by.bits():
+ result = result.squared()
+ if i:
+ result *= x
+
+proc pow*(x: FQ12, by: FR): FQ12 {.inline, noinit.} =
+ result = pow(x, BNU256.into(by))
+
+proc frobeniusMap*(x: FQ12, power: uint64): FQ12 =
+ result.c0 = x.c0.frobeniusMap(power)
+ result.c1 = x.c1.frobeniusMap(power).scale(frobeniusCoeffsC1[power mod 12])
+
+proc unitaryInverse*(x: FQ12): FQ12 =
+ result.c0 = x.c0
+ result.c1 = -x.c1
+
+proc cyclotomicSquared*(x: FQ12): FQ12 =
+ var z0 = x.c0.c0
+ var z4 = x.c0.c1
+ var z3 = x.c0.c2
+ var z2 = x.c1.c0
+ var z1 = x.c1.c1
+ var z5 = x.c1.c2
+
+ var tmp = z0 * z1
+ let t0 = (z0 + z1) * (z1.mulByNonresidue() + z0) - tmp - tmp.mulByNonresidue()
+ let t1 = tmp + tmp
+
+ tmp = z2 * z3;
+ let t2 = (z2 + z3) * (z3.mulByNonresidue() + z2) - tmp - tmp.mulByNonresidue()
+ let t3 = tmp + tmp
+
+ tmp = z4 * z5;
+ let t4 = (z4 + z5) * (z5.mulByNonresidue() + z4) - tmp - tmp.mulByNonresidue()
+ let t5 = tmp + tmp
+
+ z0 = t0 - z0
+ z0 = z0 + z0
+ z0 = z0 + t0
+
+ z1 = t1 + z1
+ z1 = z1 + z1
+ z1 = z1 + t1
+
+ tmp = t5.mulByNonresidue()
+ z2 = tmp + z2
+ z2 = z2 + z2
+ z2 = z2 + tmp
+
+ z3 = t4 - z3
+ z3 = z3 + z3
+ z3 = z3 + t4
+
+ z4 = t2 - z4
+ z4 = z4 + z4
+ z4 = z4 + t2
+
+ z5 = t3 + z5
+ z5 = z5 + z5
+ z5 = z5 + t3
+
+ result.c0 = init(z0, z4, z3)
+ result.c1 = init(z2, z1, z5)
+
+proc cyclotomicPow*(x: FQ12, by: BNU256): FQ12 =
+ result = FQ12.one()
+ var foundOne = false
+
+ for i in by.bits():
+ if foundOne:
+ result = result.cyclotomicSquared()
+ if i:
+ foundOne = true
+ result = x * result
+
+proc expByNegZ*(x: FQ12): FQ12 =
+ let uconst = BNU256([4965661367192848881'u64, 0'u64, 0'u64, 0'u64])
+ result = x.cyclotomicPow(uconst).unitaryInverse()
+
+proc finalExpFirstChunk*(x: FQ12): Option[FQ12] =
+ let opt = x.inverse()
+ if isSome(opt):
+ let b = opt.get()
+ let a = x.unitaryInverse()
+ let c = a * b
+ let d = c.frobeniusMap(2)
+ result = some[FQ12](d * c)
+ else:
+ result = none[FQ12]()
+
+proc finalExpLastChunk*(x: FQ12): FQ12 =
+ let a = x.expByNegZ()
+ let b = a.cyclotomicSquared()
+ let c = b.cyclotomicSquared()
+ let d = c * b
+
+ let e = d.expByNegZ()
+ let f = e.cyclotomicSquared()
+ let g = f.expByNegZ()
+ let h = d.unitaryInverse()
+ let i = g.unitaryInverse()
+
+ let j = i * e
+ let k = j * h
+ let ll = k * b
+ let m = k * e
+ let n = x * m
+
+ let o = ll.frobeniusMap(1)
+ let p = o * n
+
+ let q = k.frobeniusMap(2)
+ let r = q * p
+
+ let s = x.unitaryInverse()
+ let t = s * ll
+ let u = t.frobeniusMap(3)
+ let v = u * r
+
+ result = v
+
+proc finalExponentiation*(x: FQ12): Option[FQ12] =
+ let opt = x.finalExpFirstChunk()
+ if opt.isSome():
+ result = some[FQ12](opt.get().finalExpLastChunk())
+ else:
+ result = none[FQ12]()
+
+proc mulBy024*(x: FQ12, ell0, ellvw, ellvv: FQ2): FQ12 =
+ var
+ z0, z1, z2, z3, z4, z5: FQ2
+ x0, x2, x4, d0, d2, d4: FQ2
+ s0, s1, t0, t1, t2, t3, t4: FQ2
+
+ z0 = x.c0.c0
+ z1 = x.c0.c1
+ z2 = x.c0.c2
+ z3 = x.c1.c0
+ z4 = x.c1.c1
+ z5 = x.c1.c2
+
+ x0 = ell0
+ x2 = ellvv
+ x4 = ellvw
+
+ d0 = z0 * x0
+ d2 = z2 * x2
+ d4 = z4 * x4
+ t2 = z0 + z4
+ t1 = z0 + z2
+ s0 = z1 + z3 + z5
+
+ s1 = z1 * x2
+ t3 = s1 + d4
+ t4 = t3.mulByNonresidue() + d0
+ z0 = t4
+
+ t3 = z5 * x4
+ s1 = s1 + t3
+ t3 = t3 + d2
+ t4 = t3.mulByNonresidue()
+ t3 = z1 * x0
+ s1 = s1 + t3
+ t4 = t4 + t3
+ z1 = t4
+
+ t0 = x0 + x2
+ t3 = t1 * t0 - d0 - d2
+ t4 = z3 * x4
+ s1 = s1 + t4
+ t3 = t3 + t4
+
+ t0 = z2 + z4
+ z2 = t3
+
+ t1 = x2 + x4
+ t3 = t0 * t1 - d2 - d4
+ t4 = t3.mulByNonresidue()
+ t3 = z3 * x0
+ s1 = s1 + t3
+ t4 = t4 + t3
+ z3 = t4
+
+ t3 = z5 * x2
+ s1 = s1 + t3
+ t4 = t3.mulByNonresidue()
+ t0 = x0 + x4
+ t3 = t2 * t0 - d0 - d4
+ t4 = t4 + t3
+ z4 = t4
+
+ t0 = x0 + x2 + x4
+ t3 = s0 * t0 - s1
+ z5 = t3
+
+ result.c0 = init(z0, z1, z2)
+ result.c1 = init(z3, z4, z5)
diff --git a/bncurve/fq2.nim b/bncurve/fq2.nim
new file mode 100644
index 0000000..2e6c85e
--- /dev/null
+++ b/bncurve/fq2.nim
@@ -0,0 +1,149 @@
+# Nim Barreto-Naehrig pairing-friendly elliptic curve implementation
+# Copyright (c) 2018 Status Research & Development GmbH
+# Licensed under either of
+# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
+# * MIT license ([LICENSE-MIT](LICENSE-MIT))
+# at your option.
+# This file may not be copied, modified, or distributed except according to
+# those terms.
+import options
+import fp, arith
+
+type
+ FQ2* = object
+ c0*: FQ
+ c1*: FQ
+
+const
+ FQNonResidue = FQ([
+ 0x68c3488912edefaa'u64, 0x8d087f6872aabf4f'u64,
+ 0x51e1a24709081231'u64, 0x2259d6b14729c0fa'u64
+ ])
+
+ FQ2NonResidue* = FQ2(
+ c0: FQ([
+ 0xf60647ce410d7ff7'u64, 0x2f3d6f4dd31bd011'u64,
+ 0x2943337e3940c6d1'u64, 0x1d9598e8a7e39857'u64
+ ]),
+ c1: FQ([
+ 0xd35d438dc58f0d9d'u64, 0x0a78eb28f5c70b3d'u64,
+ 0x666ea36f7879462c'u64, 0x0e0a77c19a07df2f'u64
+ ])
+ )
+
+proc init*(c0, c1: FQ): FQ2 {.inline, noinit.} =
+ result.c0 = c0
+ result.c1 = c1
+
+proc zero*(t: typedesc[FQ2]): FQ2 {.inline, noinit.} =
+ result.c0 = FQ.zero()
+ result.c1 = FQ.zero()
+
+proc one*(t: typedesc[FQ2]): FQ2 {.inline, noinit.} =
+ result.c0 = FQ.one()
+ result.c1 = FQ.zero()
+
+proc random*(t: typedesc[FQ2]): FQ2 {.inline, noinit.} =
+ result.c0 = FQ.random()
+ result.c1 = FQ.random()
+
+proc isZero*(x: FQ2): bool {.inline, noinit.} =
+ result = (x.c0.isZero() and x.c1.isZero())
+
+proc scale*(x: FQ2, by: FQ): FQ2 {.inline, noinit.} =
+ result.c0 = x.c0 * by
+ result.c1 = x.c1 * by
+
+proc squared*(x: FQ2): FQ2 {.inline, noinit.} =
+ let ab = x.c0 * x.c1
+ result.c0 = (x.c1 * FQNonResidue + x.c0) * (x.c0 + x.c1) -
+ ab - ab * FQNonResidue
+ result.c1 = ab + ab
+
+proc inverse*(x: FQ2): Option[FQ2] {.inline, noinit.} =
+ let opt = (x.c0.squared() - (x.c1.squared() * FQNonResidue)).inverse()
+ if isSome(opt):
+ let tmp = opt.get()
+ result = some[FQ2](FQ2(c0: x.c0 * tmp, c1: -(x.c1 * tmp)))
+ else:
+ result = none[FQ2]()
+
+proc `+`*(x, y: FQ2): FQ2 {.noinit, inline.} =
+ ## Return result of ``x + y``.
+ result.c0 = x.c0 + y.c0
+ result.c1 = x.c1 + y.c1
+
+proc `+=`*(x: var FQ2, y: FQ2) {.noinit, inline.} =
+ ## Perform inplace addition ``x = x + y``.
+ x.c0 += y.c0
+ x.c1 += y.c1
+
+proc `-`*(x, y: FQ2): FQ2 {.noinit, inline.} =
+ ## Return result of ``x - y``.
+ result.c0 = x.c0 - y.c0
+ result.c1 = x.c1 - y.c1
+
+proc `-=`*(x: var FQ2, y: FQ2) {.noinit, inline.} =
+ ## Perform inplace substraction ``x = x - y``.
+ x.c0 -= y.c0
+ x.c1 -= y.c1
+
+proc `*`*(x, y: FQ2): FQ2 {.noinit, inline.} =
+ ## Return result of ``x * y``.
+ let aa = x.c0 * y.c0
+ let bb = x.c1 * y.c1
+ result.c0 = bb * FQNonResidue + aa
+ result.c1 = (x.c0 + x.c1) * (y.c0 + y.c1) - aa - bb
+
+proc `*=`*(x: var FQ2, y: FQ2) {.noinit, inline.} =
+ ## Perform inplace multiplication ``x = x * y``.
+ let aa = x.c0 * y.c0
+ let bb = x.c1 * y.c1
+ let cc = x.c1 + x.c1
+ x.c0 = bb * FQNonResidue + aa
+ x.c1 = cc * (y.c0 + y.c1) - aa - bb
+
+proc `-`*(x: FQ2): FQ2 {.noinit, inline.} =
+ ## Negotiation of ``x``.
+ result.c0 = -x.c0
+ result.c1 = -x.c1
+
+proc frobeniusMap*(x: FQ2, power: uint64): FQ2 =
+ if power mod 2 == 0:
+ result = x
+ else:
+ result.c0 = x.c0
+ result.c1 = x.c1 * FQNonResidue
+
+proc `==`*(x: FQ2, y: FQ2): bool =
+ ## Return ``true`` if ``a == b``.
+ result = (x.c0 == y.c0) and (x.c1 == y.c1)
+
+proc mulByNonresidue*(x: FQ2): FQ2 =
+ result = x * FQ2NonResidue
+
+proc fromBytes*(dst: var FQ2, src: openarray[byte]): bool {.noinit.} =
+ ## Create 512bit integer FQ2 from big-endian bytes representation ``src``.
+ ## Returns ``true`` if ``dst`` was successfully initialized, ``false``
+ ## otherwise.
+ result = false
+ var value: BNU512
+ if fromBytes(value, src):
+ var b0: BNU256
+ var b1o = value.divrem(FQ.modulus(), b0)
+ if isSome(b1o):
+ var c0o = FQ.init(b0)
+ var c1o = FQ.init(b1o.get())
+ if isSome(c0o) and isSome(c1o):
+ dst = init(c0o.get(), c1o.get())
+ result = true
+
+proc toBytes*(src: FQ2,
+ dst: var openarray[byte]): bool {.noinit, inline.} =
+ ## Encode 512bit integer FQ2 to big-endian bytes representation ``dst``.
+ ## Returns ``true`` if integer was successfully serialized, ``false``
+ ## otherwise.
+ var c0, c1: BNU256
+ c0 = BNU256.into(src.c0)
+ c1 = BNU256.into(src.c1)
+ result = BNU512.into(c1, c0, FQ.modulus()).toBytes(dst)
diff --git a/bncurve/fq6.nim b/bncurve/fq6.nim
new file mode 100644
index 0000000..30be2e7
--- /dev/null
+++ b/bncurve/fq6.nim
@@ -0,0 +1,178 @@
+# Nim Barreto-Naehrig pairing-friendly elliptic curve implementation
+# Copyright (c) 2018 Status Research & Development GmbH
+# Licensed under either of
+# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
+# * MIT license ([LICENSE-MIT](LICENSE-MIT))
+# at your option.
+# This file may not be copied, modified, or distributed except according to
+# those terms.
+import options
+import fq2, fp, arith
+
+const frobeniusCoeffsC1: array[4, FQ2] = [
+ FQ2.one(),
+ FQ2(
+ c0: Fq([13075984984163199792'u64, 3782902503040509012'u64,
+ 8791150885551868305'u64, 1825854335138010348'u64]),
+ c1: Fq([7963664994991228759'u64, 12257807996192067905'u64,
+ 13179524609921305146'u64, 2767831111890561987'u64])
+ ),
+ FQ2(
+ c0: Fq([3697675806616062876'u64, 9065277094688085689'u64,
+ 6918009208039626314'u64, 2775033306905974752'u64]),
+ c1: Fq.zero()
+ ),
+ FQ2(
+ c0: Fq([14532872967180610477'u64, 12903226530429559474'u64,
+ 1868623743233345524'u64, 2316889217940299650'u64]),
+ c1: Fq([12447993766991532972'u64, 4121872836076202828'u64,
+ 7630813605053367399'u64, 740282956577754197'u64])
+ )
+]
+
+const frobeniusCoeffsC2: array[4, FQ2] = [
+ FQ2.one(),
+ FQ2(
+ c0: Fq([8314163329781907090'u64, 11942187022798819835'u64,
+ 11282677263046157209'u64, 1576150870752482284'u64]),
+ c1: Fq([6763840483288992073'u64, 7118829427391486816'u64,
+ 4016233444936635065'u64, 2630958277570195709'u64])
+ ),
+ FQ2(
+ c0: Fq([8183898218631979349'u64, 12014359695528440611'u64,
+ 12263358156045030468'u64, 3187210487005268291'u64]),
+ c1: Fq.zero()
+ ),
+ FQ2(
+ c0: Fq([4938922280314430175'u64, 13823286637238282975'u64,
+ 15589480384090068090'u64, 481952561930628184'u64]),
+ c1: Fq([3105754162722846417'u64, 11647802298615474591'u64,
+ 13057042392041828081'u64, 1660844386505564338'u64])
+ )
+]
+
+type
+ FQ6* = object
+ c0*: FQ2
+ c1*: FQ2
+ c2*: FQ2
+
+proc init*(c0, c1, c2: FQ2): FQ6 {.inline, noinit.} =
+ result.c0 = c0
+ result.c1 = c1
+ result.c2 = c2
+
+proc zero*(t: typedesc[FQ6]): FQ6 {.inline, noinit.} =
+ result.c0 = FQ2.zero()
+ result.c1 = FQ2.zero()
+ result.c2 = FQ2.zero()
+
+proc one*(t: typedesc[FQ6]): FQ6 {.inline, noinit.} =
+ result.c0 = FQ2.one()
+ result.c1 = FQ2.zero()
+ result.c2 = FQ2.zero()
+
+proc random*(t: typedesc[FQ6]): FQ6 {.inline, noinit.} =
+ result.c0 = FQ2.random()
+ result.c1 = FQ2.random()
+ result.c2 = FQ2.random()
+
+proc isZero*(x: FQ6): bool {.inline, noinit.} =
+ result = (x.c0.isZero() and x.c1.isZero() and x.c2.isZero())
+
+proc scale*(x: FQ6, by: FQ2): FQ6 {.inline, noinit.} =
+ result.c0 = x.c0 * by
+ result.c1 = x.c1 * by
+ result.c2 = x.c2 * by
+
+proc squared*(x: FQ6): FQ6 {.inline, noinit.} =
+ let s0 = x.c0.squared()
+ let ab = x.c0 * x.c1
+ let s1 = ab + ab
+ let s2 = (x.c0 - x.c1 + x.c2).squared()
+ let bc = x.c1 * x.c2
+ let s3 = bc + bc
+ let s4 = x.c2.squared()
+
+ result.c0 = s0 + s3.mulByNonresidue()
+ result.c1 = s1 + s4.mulByNonresidue()
+ result.c2 = s1 + s2 + s3 - s0 - s4
+
+proc inverse*(x: FQ6): Option[FQ6] {.inline, noinit.} =
+ let c0 = x.c0.squared() - (x.c1 * x.c2.mulByNonresidue())
+ let c1 = x.c2.squared().mulByNonresidue() - (x.c0 * x.c1)
+ let c2 = x.c1.squared() - (x.c0 * x.c2)
+ let opt = ((x.c2 * c1 + x.c1 * c2).mulByNonresidue() +
+ x.c0 * c0).inverse()
+ if isSome(opt):
+ let tmp = opt.get()
+ result = some[FQ6](FQ6(c0: tmp * c0, c1: tmp * c1, c2: tmp * c2))
+ else:
+ result = none[FQ6]()
+
+proc `+`*(x, y: FQ6): FQ6 {.noinit, inline.} =
+ ## Return result of ``x + y``.
+ result.c0 = x.c0 + y.c0
+ result.c1 = x.c1 + y.c1
+ result.c2 = x.c2 + y.c2
+
+proc `+=`*(x: var FQ6, y: FQ6) {.noinit, inline.} =
+ ## Perform inplace addition ``x = x + y``.
+ x.c0 += y.c0
+ x.c1 += y.c1
+ x.c2 += y.c2
+
+proc `-`*(x, y: FQ6): FQ6 {.noinit, inline.} =
+ ## Return result of ``x - y``.
+ result.c0 = x.c0 - y.c0
+ result.c1 = x.c1 - y.c1
+ result.c2 = x.c2 - y.c2
+
+proc `-=`*(x: var FQ6, y: FQ6) {.noinit, inline.} =
+ ## Perform inplace substraction ``x = x - y``.
+ x.c0 -= y.c0
+ x.c1 -= y.c1
+ x.c2 -= y.c2
+
+proc `*`*(x, y: FQ6): FQ6 {.noinit, inline.} =
+ ## Return result of ``x * y``.
+ let aa = x.c0 * y.c0
+ let bb = x.c1 * y.c1
+ let cc = x.c2 * y.c2
+ result.c0 = ((x.c1 + x.c2) * (y.c1 + y.c2) - bb - cc).mulByNonresidue() +
+ aa
+ result.c1 = (x.c0 + x.c1) * (y.c0 + y.c1) - aa - bb + cc.mulByNonresidue()
+ result.c2 = (x.c0 + x.c2) * (y.c0 + y.c2) - aa + bb - cc
+
+proc `*=`*(x: var FQ6, y: FQ6) {.noinit, inline.} =
+ ## Perform inplace multiplication ``x = x * y``.
+ let aa = x.c0 * y.c0
+ let bb = x.c1 * y.c1
+ let cc = x.c2 * y.c2
+ let dd = x.c1 + x.c2
+ let ee = x.c0 + x.c1
+ let ff = x.c0 + x.c2
+
+ x.c0 = (dd * (y.c1 + y.c2) - bb - cc).mulByNonresidue() + aa
+ x.c1 = ee * (y.c0 + y.c1) - aa - bb + cc.mulByNonresidue()
+ x.c2 = ff * (y.c0 + y.c2) - aa - bb - cc
+
+proc `-`*(x: FQ6): FQ6 {.noinit, inline.} =
+ ## Negotiation of ``x``.
+ result.c0 = -x.c0
+ result.c1 = -x.c1
+ result.c2 = -x.c2
+
+proc frobeniusMap*(x: FQ6, power: uint64): FQ6 =
+ result.c0 = x.c0.frobeniusMap(power)
+ result.c1 = x.c1.frobeniusMap(power) * frobeniusCoeffsC1[power mod 6]
+ result.c2 = x.c2.frobeniusMap(power) * frobeniusCoeffsC2[power mod 6]
+
+proc `==`*(x: FQ6, y: FQ6): bool =
+ ## Return ``true`` if ``a == b``.
+ result = (x.c0 == y.c0) and (x.c1 == y.c1) and (x.c2 == y.c2)
+
+proc mulByNonresidue*(x: FQ6): FQ6 =
+ result.c0 = x.c2.mulByNonresidue()
+ result.c1 = x.c0
+ result.c2 = x.c1
diff --git a/bncurve/groups.nim b/bncurve/groups.nim
new file mode 100644
index 0000000..2fed26b
--- /dev/null
+++ b/bncurve/groups.nim
@@ -0,0 +1,515 @@
+# Nim Barreto-Naehrig pairing-friendly elliptic curve implementation
+# Copyright (c) 2018 Status Research & Development GmbH
+# Licensed under either of
+# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
+# * MIT license ([LICENSE-MIT](LICENSE-MIT))
+# at your option.
+# This file may not be copied, modified, or distributed except according to
+# those terms.
+import fields, arith, options
+export fields, arith, options
+import nimcrypto/utils
+
+type
+ G1* = object
+ G2* = object
+
+ Point*[T: G1|G2] = object
+ when T is G1:
+ x*, y*, z*: FQ
+ else:
+ x*, y*, z*: FQ2
+
+ AffinePoint*[T: G1|G2] = object
+ when T is G1:
+ x*, y*: FQ
+ else:
+ x*, y*: FQ2
+
+ Coeff*[T: G1|G2] = object
+ when T is G1:
+ b*: FQ
+ else:
+ b*: FQ2
+
+ EllCoeffs* = object
+ ell_0*: FQ2
+ ell_vw*: FQ2
+ ell_vv*: FQ2
+
+ G2Precomp* = object
+ q*: AffinePoint[G2]
+ coeffs*: seq[EllCoeffs]
+
+const
+ G1One = Point[G1](
+ x: FQ.one(),
+ y: FQ([0xa6ba871b8b1e1b3a'u64, 0x14f1d651eb8e167b'u64,
+ 0xccdd46def0f28c58'u64, 0x1c14ef83340fbe5e'u64]),
+ z: FQ.one()
+ )
+
+ G1B = FQ([0x7a17caa950ad28d7'u64, 0x1f6ac17ae15521b9'u64,
+ 0x334bea4e696bd284'u64, 0x2a1f6744ce179d8e'u64])
+
+ G2One = Point[G2](
+ x: FQ2(
+ c0: FQ([0x8e83b5d102bc2026'u64, 0xdceb1935497b0172'u64,
+ 0xfbb8264797811adf'u64, 0x19573841af96503b'u64]),
+ c1: FQ([0xafb4737da84c6140'u64, 0x6043dd5a5802d8c4'u64,
+ 0x09e950fc52a02f86'u64, 0x14fef0833aea7b6b'u64])
+ ),
+ y: FQ2(
+ c0: FQ([0x619dfa9d886be9f6'u64, 0xfe7fd297f59e9b78'u64,
+ 0xff9e1a62231b7dfe'u64, 0x28fd7eebae9e4206'u64]),
+ c1: FQ([0x64095b56c71856ee'u64, 0xdc57f922327d3cbb'u64,
+ 0x55f935be33351076'u64, 0x0da4a0e693fd6482'u64])
+ ),
+ z: FQ2.one()
+ )
+
+ G2B = FQ2(
+ c0: FQ([0x3bf938e377b802a8'u64, 0x020b1b273633535d'u64,
+ 0x26b7edf049755260'u64, 0x2514c6324384a86d'u64]),
+ c1: FQ([0x38e7ecccd1dcff67'u64, 0x65f0b37d93ce0d3e'u64,
+ 0xd749d0dd22ac00aa'u64, 0x0141b9ce4a688d4d'u64])
+ )
+
+ AteLoopCount = BNU256([
+ 0x9d797039be763ba8'u64, 0x0000000000000001'u64,
+ 0x0000000000000000'u64, 0x0000000000000000'u64
+ ])
+
+ TwoInv = FQ([
+ 9781510331150239090'u64, 15059239858463337189'u64,
+ 10331104244869713732'u64, 2249375503248834476'u64
+ ])
+
+ Twist = FQ2NonResidue
+
+ TwistMulByQx = FQ2(
+ c0: FQ([
+ 13075984984163199792'u64, 3782902503040509012'u64,
+ 8791150885551868305'u64, 1825854335138010348'u64
+ ]),
+ c1: FQ([
+ 7963664994991228759'u64, 12257807996192067905'u64,
+ 13179524609921305146'u64, 2767831111890561987'u64
+ ])
+ )
+
+ TwistMulByQy = FQ2(
+ c0: FQ([
+ 16482010305593259561'u64, 13488546290961988299'u64,
+ 3578621962720924518'u64, 2681173117283399901'u64
+ ]),
+ c1: FQ([
+ 11661927080404088775'u64, 553939530661941723'u64,
+ 7860678177968807019'u64, 3208568454732775116'u64
+ ])
+ )
+
+proc one*[T: G1|G2](t: typedesc[T]): Point[T] {.inline, noinit.} =
+ when T is G1:
+ result = G1One
+ else:
+ result = G2One
+
+# proc one*(t: typedesc[Gt]): Gt {.inline, noinit.} =
+# result = FQ12.one()
+
+proc name*[T: G1|G2](t: typedesc[T]): string {.inline, noinit.} =
+ when T is G1:
+ result = "G1"
+ else:
+ result = "G2"
+
+proc coeff*[T: G1|G2](t: typedesc[T]): Coeff[T] {.inline, noinit.} =
+ when T is G1:
+ result = G1B
+ else:
+ result = G2B
+
+proc zero*[T: G1|G2](t: typedesc[T]): Point[T] {.inline, noinit.} =
+ when T is G1:
+ result.x = FQ.zero()
+ result.y = FQ.one()
+ result.z = FQ.zero()
+ else:
+ result.x = FQ2.zero()
+ result.y = FQ2.one()
+ result.z = FQ2.zero()
+
+proc isZero*[T: G1|G2](p: Point[T]): bool {.inline, noinit.} =
+ result = p.z.isZero()
+
+proc double*[T: G1|G2](p: Point[T]): Point[T] {.noinit.} =
+ let a = p.x.squared()
+ let b = p.y.squared()
+ let c = b.squared()
+ var d = (p.x + b).squared() - a - c
+ d = d + d
+ let e = a + a + a
+ let f = e.squared()
+ let x3 = f - (d + d)
+ var eightc = c + c
+ eightc = eightc + eightc
+ eightc = eightc + eightc
+ let y1z1 = p.y * p.z
+ result.x = x3
+ result.y = e * (d - x3) - eightc
+ result.z = y1z1 + y1z1
+
+proc `*`*[T: G1|G2](p: Point[T], by: FR): Point[T] =
+ result = T.zero()
+ var foundOne = false
+ for i in BNU256.into(by).bits():
+ if foundOne:
+ result = result.double()
+ if i:
+ foundOne = true
+ result = result + p
+
+proc random*[T: G1|G2](t: typedesc[T]): Point[T] {.inline, noinit.} =
+ result = t.one() * FR.random()
+
+proc `+`*[T: G1|G2](p1, p2: Point[T]): Point[T] {.noinit.} =
+ if p1.isZero():
+ return p2
+ if p2.isZero():
+ return p1
+
+ let z1squared = p1.z.squared()
+ let z2squared = p2.z.squared()
+ let u1 = p1.x * z2squared
+ let u2 = p2.x * z1squared
+ let z1cubed = p1.z * z1squared
+ let z2cubed = p2.z * z2squared
+ let s1 = p1.y * z2cubed
+ let s2 = p2.y * z1cubed
+
+ if u1 == u2 and s1 == s2:
+ result = p1.double()
+ else:
+ let h = u2 - u1
+ let s2minuss1 = s2 - s1
+ let i = (h + h).squared()
+ let j = h * i
+ let r = s2minuss1 + s2minuss1
+ let v = u1 * i
+ let s1j = s1 * j
+ let x3 = r.squared() - j - (v + v)
+ result.x = x3
+ result.y = r * (v - x3) - (s1j + s1j)
+ result.z = ((p1.z + p2.z).squared() - z1squared - z2squared) * h
+
+proc `-`*[T: G1|G2](p: Point[T]): Point[T] {.inline, noinit.} =
+ if p.isZero():
+ return p
+ else:
+ result.x = p.x
+ result.y = -p.y
+ result.z = p.z
+
+proc `-`*[T: G1|G2](p: AffinePoint[T]): AffinePoint[T] {.inline, noinit.} =
+ result.x = p.x
+ result.y = -p.y
+
+proc `-`*[T: G1|G2](p1, p2: Point[T]): Point[T] {.inline, noinit.} =
+ result = p1 + (-p2)
+
+proc `==`*[T: G1|G2](p1, p2: Point[T]): bool =
+ if p1.isZero():
+ return p2.isZero()
+ if p2.isZero():
+ return false
+ let z1squared = p1.z.squared()
+ let z2squared = p2.z.squared()
+ if (p1.x * z2squared) != (p2.x * z1squared):
+ return false
+ let z1cubed = p1.z * z1squared
+ let z2cubed = p2.z * z2squared
+ if (p1.y * z2cubed) != (p2.y * z1cubed):
+ return false
+ return true
+
+proc toJacobian*[T: G1|G2](p: AffinePoint[T]): Point[T] {.inline, noinit.} =
+ ## Convert affine coordinates' point ``p`` to point.
+ result.x = p.x
+ result.y = p.y
+ when T is G1:
+ result.z = FQ.one()
+ else:
+ result.z = FQ2.one()
+
+proc toAffine*[T: G1|G2](p: Point[T]): Option[AffinePoint[T]] =
+ ## Attempt to convert point ``p`` to affine coordinates.
+ when T is G1:
+ var fone = FQ.one()
+ else:
+ var fone = FQ2.one()
+
+ if p.z.isZero():
+ result = none[AffinePoint[T]]()
+ elif p.z == fone:
+ result = some[AffinePoint[T]](AffinePoint[T](x: p.x, y: p.y))
+ else:
+ let ozinv = p.z.inverse()
+ if isSome(ozinv):
+ let zinv = ozinv.get()
+ var zinvsquared = zinv.squared()
+ result = some[AffinePoint[T]](
+ AffinePoint[T](
+ x: p.x * zinvsquared,
+ y: p.y * (zinvsquared * zinv)
+ )
+ )
+ else:
+ result = none[AffinePoint[T]]()
+
+proc normalize*(p: var Point[G2]) {.inline, noinit.} =
+ let aopt = p.toAffine()
+ if isSome(aopt):
+ p = aopt.get().toJacobian()
+ else:
+ return
+
+proc isOnCurve*[T: G1|G2](p: AffinePoint[T]): bool =
+ when T is G1:
+ result = (p.y.squared() == (p.x.squared() * p.x) + G1B)
+ else:
+ result = (p.y.squared() == (p.x.squared() * p.x) + G2B)
+
+proc mulByQ(p: AffinePoint[G2]): AffinePoint[G2] =
+ result.x = TwistMulByQx * p.x.frobeniusMap(1)
+ result.y = TwistMulByQy * p.y.frobeniusMap(1)
+
+proc mixedAdditionStepForFlippedML(p: var Point[G2],
+ base: AffinePoint[G2]): EllCoeffs =
+ let d = p.x - p.z * base.x
+ let e = p.y - p.z * base.y
+ let f = d.squared()
+ let g = e.squared()
+ let h = d * f
+ let i = p.x * f
+ let j = p.z * g + h - (i + i)
+
+ p.x = d * j
+ p.y = e * (i - j) - h * p.y
+ p.z = p.z * h
+
+ result.ell_0 = Twist * (e * base.x - d * base.y)
+ result.ell_vv = -e
+ result.ell_vw = d
+
+proc doublingStepForFlippedML(p: var Point[G2]): EllCoeffs =
+ let a = (p.x * p.y).scale(TwoInv)
+ let b = p.y.squared()
+ let c = p.z.squared()
+ let d = c + c + c
+ let e = G2B * d
+ let f = e + e + e
+ let g = (b + f).scale(TwoInv)
+ let h = (p.y + p.z).squared() - (b + c)
+ let i = e - b
+ let j = p.x.squared()
+ let e_sq = e.squared()
+
+ p.x = a * (b - f)
+ p.y = g.squared() - (e_sq + e_sq + e_sq)
+ p.z = b * h
+
+ result.ell_0 = Twist * i
+ result.ell_vw = -h
+ result.ell_vv = j + j + j
+
+proc precompute*(p: AffinePoint[G2]): G2Precomp =
+ var r = p.toJacobian()
+ result.coeffs = newSeqOfCap[EllCoeffs](102)
+ var foundOne = false
+
+ for i in AteLoopCount.bits():
+ if not foundOne:
+ foundOne = i
+ continue
+ result.coeffs.add(r.doublingStepForFlippedML())
+ if i:
+ result.coeffs.add(r.mixedAdditionStepForFlippedML(p))
+
+ let q1 = p.mulByQ()
+ let q2 = -(q1.mulByQ())
+
+ result.coeffs.add(r.mixedAdditionStepForFlippedML(q1))
+ result.coeffs.add(r.mixedAdditionStepForFlippedML(q2))
+ result.q = p
+
+proc millerLoop*(pc: G2Precomp, g1: AffinePoint[G1]): FQ12 =
+ result = FQ12.one()
+ var idx = 0
+ var foundOne = false
+ var c: EllCoeffs
+ for i in AteLoopCount.bits():
+ if not foundOne:
+ foundOne = i
+ continue
+ c = pc.coeffs[idx]
+ inc(idx)
+ result = result.squared().mulBy024(c.ell_0, c.ell_vw.scale(g1.y),
+ c.ell_vv.scale(g1.x))
+ if i:
+ c = pc.coeffs[idx]
+ idx += 1
+ result = result.mulBy024(c.ell_0, c.ell_vw.scale(g1.y),
+ c.ell_vv.scale(g1.x))
+ c = pc.coeffs[idx]
+ idx += 1
+ result = result.mulBy024(c.ell_0, c.ell_vw.scale(g1.y), c.ell_vv.scale(g1.x))
+ c = pc.coeffs[idx]
+ result = result.mulBy024(c.ell_0, c.ell_vw.scale(g1.y), c.ell_vv.scale(g1.x))
+
+proc pairing*(p: Point[G1], q: Point[G2]): FQ12 {.noinit, inline.} =
+ result = FQ12.one()
+ var optp = p.toAffine()
+ var optq = q.toAffine()
+ if optp.isSome() and optq.isSome():
+ let pc = optq.get().precompute()
+ let ores = finalExponentiation(pc.millerLoop(optp.get()))
+ if ores.isSome():
+ result = ores.get()
+
+proc toBytes*[T: G1|G2](p: AffinePoint[T], dst: var openarray[byte]): bool =
+ ## Encode affine point coordinates (x, y) to big-endian bytes representation
+ ## ``dst``.
+ ## Returns ``true`` if coordinates was successfully serialized, ``false``
+ ## otherwise.
+ when T is G1:
+ if len(dst) >= 64:
+ if p.x.toBytes(toOpenArray(dst, 0, 31)):
+ if p.y.toBytes(toOpenArray(dst, 32, 63)):
+ result = true
+ else:
+ if len(dst) >= 128:
+ if p.x.toBytes(toOpenArray(dst, 0, 63)):
+ if p.y.toBytes(toOpenArray(dst, 64, 127)):
+ result = true
+
+proc fromBytes*[T: G1|G2](p: var AffinePoint[T], src: openarray[byte]): bool =
+ ## Decode affine point coordinates (x, y) from big endian bytes representation
+ ## ``src``.
+ ## Returns ``true`` if coordinates was successfully serialized, ``false``
+ ## otherwise.
+ when T is G1:
+ const
+ nextOffset = 32
+ coeff = G1B
+ var
+ x, y: FQ
+ point: Point[G1]
+ else:
+ const
+ nextOffset = 64
+ coeff = G2B
+ var
+ x, y: FQ2
+ point: Point[G2]
+
+ if len(src) >= nextOffset * 2:
+ if x.fromBytes(src):
+ if y.fromBytes(toOpenArray(src, nextOffset, len(src) - 1)):
+ if y.squared() == (x.squared() * x) + coeff:
+ ## Check if point on curve.
+ point.x = x
+ point.y = y
+ when T is G1:
+ point.z = FQ.one()
+ else:
+ point.z = FQ2.one()
+ if (point * (-FR.one())) + point == T.zero():
+ p.x = x
+ p.y = y
+ result = true
+ else:
+ ## Point is not in the subgroup
+ discard
+
+proc fromHexString*[T: G1|G2](p: var AffinePoint[T],
+ src: string): bool {.inline.} =
+ ## Decode affine point coordinates (x, y) from hexadecimal string
+ ## representation ``src``.
+ ## Returns ``true`` if coordinates was successfully serialized, ``false``
+ ## otherwise.
+ ## ``Note:`` Can raise exception on malformed hexadecimal string.
+ result = fromBytes(p, fromHex(src))
+
+proc toHexString*[T: G1|G2](p: AffinePoint[T],
+ lowercase = false): string {.inline.} =
+ ## Encode affine point coordinates (x, y) and return hexadecimal string
+ ## representation.
+ when T is G1:
+ var buffer: array[64, byte]
+ else:
+ var buffer: array[128, byte]
+ if toBytes(p, buffer):
+ result = toHex(buffer, lowercase)
+
+proc toBytes*[T: G1|G2](p: Point[T],
+ dst: var openarray[byte]): bool {.inline.} =
+ ## Encode point coordinates (x, y, z) to big-endian bytes representation
+ ## ``dst``.
+ ## Returns ``true`` if coordinates was successfully serialized, ``false``
+ ## otherwise.
+ when T is G1:
+ const outputSize = 64
+ else:
+ const outputSize = 128
+ if p.isZero():
+ if len(dst) >= 1:
+ dst[0] = 0x00'u8
+ result = true
+ else:
+ result = false
+ else:
+ if len(dst) >= 1 + outputSize:
+ var apo = p.toAffine()
+ if isSome(apo):
+ dst[0] = 0x04'u8
+ result = apo.get().toBytes(toOpenArray(dst, 1, outputSize))
+
+proc fromBytes*[T: G1|G2](p: var Point[T],
+ src: openarray[byte]): bool {.inline.} =
+ ## Decode affine point coordinates (x, y, z) from big endian bytes
+ ## representation ``src``.
+ ## Returns ``true`` if coordinates was successfully serialized, ``false``
+ ## otherwise.
+ when T is G1:
+ const inputSize = 64
+ else:
+ const inputSize = 128
+ if len(src) > 0:
+ if src[0] == 0x00'u8:
+ p = T.zero()
+ result = true
+ elif src[0] == 0x04'u8:
+ if len(src) >= inputSize + 1:
+ var ap: AffinePoint[T]
+ if ap.fromBytes(toOpenArray(src, 1, inputSize)):
+ p = toJacobian(ap)
+ result = true
+
+proc fromHexString*[T: G1|G2](p: var Point[T], src: string): bool {.inline.} =
+ ## Decode point coordinates (x, y, z) from hexadecimal string
+ ## representation ``src``.
+ ## Returns ``true`` if coordinates was successfully serialized, ``false``
+ ## otherwise.
+ ## ``Note:`` Can raise exception on malformed hexadecimal string.
+ result = fromBytes(p, fromHex(src))
+
+proc toHexString*[T: G1|G2](p: Point[T], lowercase = false): string {.inline.} =
+ ## Encode affine point coordinates (x, y, z) and return hexadecimal string
+ ## representation.
+ when T is G1:
+ var buffer: array[64 + 1, byte]
+ else:
+ var buffer: array[128 + 1, byte]
+ if toBytes(p, buffer):
+ result = toHex(buffer, lowercase)
diff --git a/tests/tarith.nim b/tests/tarith.nim
new file mode 100644
index 0000000..c2a9be7
--- /dev/null
+++ b/tests/tarith.nim
@@ -0,0 +1,186 @@
+import unittest
+import ../bncurve/arith
+
+when isMainModule:
+ let modulo = [
+ 0x3c208c16d87cfd47'u64, 0x97816a916871ca8d'u64,
+ 0xb85045b68181585d'u64, 0x30644e72e131a029'u64
+ ]
+
+ suite "Modular arithmetic test suite":
+ test "[256] Serialize/Deserialize tests":
+ for i in 0..<100:
+ var c0, c1, c2: BNU256
+ var c0b: array[4 * sizeof(uint64), byte]
+ c0 = BNU256.random(modulo)
+ var c0s = c0.toString()
+ check:
+ c0.toBytes(c0b) == true
+ c1.fromBytes(c0b) == true
+ c2.fromHexString(c0s) == true
+ c0 == c1
+ c0 == c2
+
+ test "[512] Serialize/Deserialize tests":
+ for i in 0..<100:
+ var cb: BNU512
+ var cbs: array[8 * sizeof(uint64), byte]
+ var e0 = BNU256.random(modulo)
+ var e1 = BNU256.random(modulo)
+ var bb12 = BNU512.into(e1, e0, modulo)
+ check:
+ bb12.toBytes(cbs) == true
+ cb.fromBytes(cbs) == true
+ var c0: BNU256
+ var c1opt = cb.divrem(modulo, c0)
+ check:
+ isSome(c1opt) == true
+ c1opt.get() == e1
+ c0 == e0
+
+ test "Setting bits":
+ var moduloS = [
+ 0xfffffffffffffffff'u64, 0xfffffffffffffffff'u64,
+ 0xfffffffffffffffff'u64, 0xfffffffffffffffff'u64
+ ]
+ var a = BNU256.random(moduloS)
+ var e = BNU256.zero()
+ for i, b in a.pairs():
+ let ret = e.setBit(255 - i, b)
+ doAssert ret
+ check e == a
+
+ test "fromValue & divrem on random numbers":
+ for i in 0..<100:
+ var nc0: BNU256
+ var nc1: Option[BNU256]
+ var c0 = BNU256.random(modulo)
+ var c1 = BNU256.random(modulo)
+ var c1q = BNU512.into(c1, c0, modulo)
+ nc1 = c1q.divrem(modulo, nc0)
+ check:
+ nc1.get() == c1
+ nc0 == c0
+
+ test "Modulus should become 1*q + 0":
+ var a = [
+ 0x3c208c16d87cfd47'u64, 0x97816a916871ca8d'u64,
+ 0xb85045b68181585d'u64, 0x30644e72e131a029'u64,
+ 0'u64, 0'u64, 0'u64, 0'u64
+ ]
+
+ var c0, c2: BNU256
+ var c1: Option[BNU256]
+ c1 = a.divrem(modulo, c0)
+ c2 = c1.get()
+ check:
+ c2 == BNU256.one()
+ c0 == BNU256.zero()
+ c2 == BNU256.one()
+ c0 == BNU256.zero()
+
+ test "Modulus squared minus 1 should be (q-1) q + q-1":
+ let a = [
+ 0x3b5458a2275d69b0'u64, 0xa602072d09eac101'u64,
+ 0x4a50189c6d96cadc'u64, 0x04689e957a1242c8'u64,
+ 0x26edfa5c34c6b38d'u64, 0xb00b855116375606'u64,
+ 0x599a6f7c0348d21c'u64, 0x0925c4b8763cbf9c'u64
+ ]
+ let expect = [
+ 0x3c208c16d87cfd46'u64, 0x97816a916871ca8d'u64,
+ 0xb85045b68181585d'u64, 0x30644e72e131a029'u64
+ ]
+ var c0, c2: BNU256
+ var c1: Option[BNU256]
+ c1 = a.divrem(modulo, c0)
+ c2 = c1.get()
+ check:
+ c0 == expect
+ c2 == expect
+
+ test "Modulus squared minus 2 should be (q-1) q + q-2":
+ let a = [
+ 0x3b5458a2275d69af'u64, 0xa602072d09eac101'u64,
+ 0x4a50189c6d96cadc'u64, 0x04689e957a1242c8'u64,
+ 0x26edfa5c34c6b38d'u64, 0xb00b855116375606'u64,
+ 0x599a6f7c0348d21c'u64, 0x0925c4b8763cbf9c'u64
+ ]
+ let expectc1 = [
+ 0x3c208c16d87cfd46'u64, 0x97816a916871ca8d'u64,
+ 0xb85045b68181585d'u64, 0x30644e72e131a029'u64
+ ]
+ let expectc0 = [
+ 0x3c208c16d87cfd45'u64, 0x97816a916871ca8d'u64,
+ 0xb85045b68181585d'u64, 0x30644e72e131a029'u64
+ ]
+ var c0, c2: BNU256
+ var c1: Option[BNU256]
+ c1 = a.divrem(modulo, c0)
+ c2 = c1.get()
+ check:
+ c0 == expectc0
+ c2 == expectc1
+
+ test "Ridiculously large number should fail":
+ let a = [
+ 0xfffffffffffffffff'u64, 0xfffffffffffffffff'u64,
+ 0xfffffffffffffffff'u64, 0xfffffffffffffffff'u64,
+ 0xfffffffffffffffff'u64, 0xfffffffffffffffff'u64,
+ 0xfffffffffffffffff'u64, 0xfffffffffffffffff'u64
+ ]
+ let expectc0 = [
+ 0xf32cfc5b538afa88'u64, 0xb5e71911d44501fb'u64,
+ 0x47ab1eff0a417ff6'u64, 0x06d89f71cab8351f'u64
+ ]
+ var c0: BNU256
+ var c1: Option[BNU256]
+ c1 = a.divrem(modulo, c0)
+ check:
+ c1.isNone() == true
+ c0 == expectc0
+
+ test "Modulus squared should fail":
+ let a = [
+ 0x3b5458a2275d69b1'u64, 0xa602072d09eac101'u64,
+ 0x4a50189c6d96cadc'u64, 0x04689e957a1242c8'u64,
+ 0x26edfa5c34c6b38d'u64, 0xb00b855116375606'u64,
+ 0x599a6f7c0348d21c'u64, 0x0925c4b8763cbf9c'u64
+ ]
+ var c0: BNU256
+ var c1: Option[BNU256]
+ c1 = a.divrem(modulo, c0)
+ check:
+ c1.isNone() == true
+ c0.isZero() == true
+
+ test "Modulus squared plus one should fail":
+ let a = [
+ 0x3b5458a2275d69b2'u64, 0xa602072d09eac101'u64,
+ 0x4a50189c6d96cadc'u64, 0x04689e957a1242c8'u64,
+ 0x26edfa5c34c6b38d'u64, 0xb00b855116375606'u64,
+ 0x599a6f7c0348d21c'u64, 0x0925c4b8763cbf9c'u64
+ ]
+ var c0: BNU256
+ var c1: Option[BNU256]
+ c1 = a.divrem(modulo, c0)
+ check:
+ c1.isNone() == true
+ c0 == BNU256.one()
+
+ test "Fr modulus masked off is valid":
+ let a = [
+ 0xffffffffffffffff'u64, 0xffffffffffffffff'u64,
+ 0xffffffffffffffff'u64, 0xffffffffffffffff'u64,
+ 0xffffffffffffffff'u64, 0xffffffffffffffff'u64,
+ 0xffffffffffffffff'u64, 0x07ffffffffffffff'u64
+ ]
+ let moduloFr = [
+ 0x43e1f593f0000001'u64, 0x2833e84879b97091'u64,
+ 0xb85045b68181585d'u64, 0x30644e72e131a029'u64
+ ]
+ var c0, c2: BNU256
+ var c1: Option[BNU256]
+ c1 = a.divrem(moduloFr, c0)
+ check:
+ c1.get() < moduloFr
+ c0 < moduloFr
diff --git a/tests/tfields.nim b/tests/tfields.nim
new file mode 100644
index 0000000..89bc153
--- /dev/null
+++ b/tests/tfields.nim
@@ -0,0 +1,562 @@
+import unittest
+import nimcrypto/utils
+import ../bncurve/fields
+
+proc randomSquaring*[T](): bool =
+ for i in 0..100:
+ var a = T.random()
+ if a * a != a.squared():
+ return false
+
+ var cur = T.zero()
+ for i in 0..100:
+ if cur.squared() != cur * cur:
+ return false
+ cur = cur + T.one()
+
+ return true
+
+proc zeroTest*[T](): bool =
+ if -T.zero() != T.zero():
+ return false
+ if (-T.one() + T.one()) != T.zero():
+ return false
+ if (T.zero() - T.zero()) != T.zero():
+ return false
+ return true
+
+proc canInvert*[T](): bool =
+ var a = T.one()
+ for i in 0..100:
+ if (a * a.inverse().get()) != T.one():
+ return false
+ a = a + T.one()
+ a = -T.one()
+ for i in 0..100:
+ if (a * a.inverse().get()) != T.one():
+ return false
+ a = a - T.one()
+ return true
+
+proc randomElementInverse*[T](): bool =
+ for i in 0..100:
+ var a = T.random()
+ if a.inverse().get() * a != T.one():
+ return false
+ var b = T.random()
+ if a * b * a.inverse().get() != b:
+ return false
+ return true
+
+proc randomElementMultiplication*[T](): bool =
+ for i in 0..250:
+ var a = T.random()
+ var b = T.random()
+ var c = T.random()
+ result = ((a * b) * c == a * (b * c))
+
+proc randomElementEval*[T](): bool =
+ for i in 0..100:
+ var a = T.random()
+ var b = T.random()
+ var c = T.random()
+ var d = T.random()
+
+ var lhs = (a + b) * (c + d)
+ var rhs = (a * c) + (b * c) + (a * d) + (b * d)
+ if lhs != rhs:
+ return false
+ return true
+
+proc randomElementASN*[T](): bool =
+ for i in 0..100:
+ var a = T.random()
+ if a + (-a) != T.zero():
+ return false
+
+ for i in 0..10:
+ var a = T.random()
+ var r = T.random()
+ var b = a + r
+ var c = T.random()
+ var d = c + r
+
+ for m in 0..10:
+ let r0 = T.random()
+ a += r0
+ b += r0
+ c = c + r0
+ d = d + r0
+ let r1 = T.random()
+ a -= r1
+ b -= r1
+ c = c - r1
+ d = d - r1
+ let r2 = T.random()
+ a += (-(-r2))
+ b += (-(-r2))
+ c = c + (-(-r2))
+ d = d + (-(-r2))
+ let r3 = T.random()
+ a -= r3
+ b += -r3
+ c = c - r3
+ d = d + (-r3)
+ let r4 = T.random()
+ a += -r4
+ b -= r4
+ c = c + (-r4)
+ d = d - r4
+ b -= r
+ d = d - r
+
+ if a != b or c != d:
+ return false
+ return true
+
+proc testCyclotomicExp(): bool =
+ var orig = FQ12(
+ c0: FQ6(
+ c0: FQ2(
+ c0: FQ.fromString("2259924035228092997691937637688451143058635253053054071159756458902878894295"),
+ c1: FQ.fromString("13145690032701362144460254305183927872683620413225364127064863863535255135244")
+ ),
+ c1: FQ2(
+ c0: FQ.fromString("9910063591662383599552477067956819406417086889312288278252482503717089428441"),
+ c1: FQ.fromString("537414042055419261990282459138081732565514913399498746664966841152381183961")
+ ),
+ c2: FQ2(
+ c0: FQ.fromString("15311812409497308894370893420777496684951030254049554818293571309705780605004"),
+ c1: FQ.fromString("13657107176064455789881282546557276003626320193974643644160350907227082365810")
+ )
+ ),
+ c1: FQ6(
+ c0: FQ2(
+ c0: FQ.fromString("4913017949003742946864670837361832856526234260447029873580022776602534856819"),
+ c1: FQ.fromString("7834351480852267338070670220119081676575418514182895774094743209915633114041")
+ ),
+ c1: FQ2(
+ c0: FQ.fromString("12837298223308203788092748646758194441270207338661891973231184407371206766993"),
+ c1: FQ.fromString("12756474445699147370503225379431475413909971718057034061593007812727141391799")
+ ),
+ c2: FQ2(
+ c0: FQ.fromString("9473802207170192255373153510655867502408045964296373712891954747252332944018"),
+ c1: FQ.fromString("4583089109360519374075173304035813179013579459429335467869926761027310749713")
+ )
+ )
+ )
+
+ var expected = FQ12(
+ c0: FQ6(
+ c0: FQ2(
+ c0: FQ.fromString("14722956046055152398903846391223329501345567382234608299399030576415080188350"),
+ c1: FQ.fromString("14280703280777926697010730619606819467080027543707671882210769811674790473417")
+ ),
+ c1: FQ2(
+ c0: FQ.fromString("19969875076083990244184003223190771301761436396530543002586073549972410735411"),
+ c1: FQ.fromString("10717335566913889643303549252432531178405520196706173198634734518494041323243")
+ ),
+ c2: FQ2(
+ c0: FQ.fromString("6063612626166484870786832843320782567259894784043383626084549455432890717937"),
+ c1: FQ.fromString("17089783040131779205038789608891431427943860868115199598200376195935079808729")
+ )
+ ),
+ c1: FQ6(
+ c0: FQ2(
+ c0: FQ.fromString("10029863438921507421569931792104023129735006154272482043027653425575205672906"),
+ c1: FQ.fromString("6406252222753462799887280578845937185621081001436094637606245493619821542775")
+ ),
+ c1: FQ2(
+ c0: FQ.fromString("1048245462913506652602966692378792381004227332967846949234978073448561848050"),
+ c1: FQ.fromString("1444281375189053827455518242624554285012408033699861764136810522738182087554")
+ ),
+ c2: FQ2(
+ c0: FQ.fromString("8839610992666735109106629514135300820412539620261852250193684883379364789120"),
+ c1: FQ.fromString("11347360242067273846784836674906058940820632082713814508736182487171407730718")
+ )
+ )
+ )
+
+ let e = orig.expByNegZ()
+ result = (e == expected)
+
+proc fq12TestVector(): bool =
+ let start = FQ12(
+ c0: FQ6(
+ c0: FQ2(
+ c0: FQ.fromString("19797905000333868150253315089095386158892526856493194078073564469188852136946"),
+ c1: FQ.fromString("10509658143212501778222314067134547632307419253211327938344904628569123178733")
+ ),
+ c1: FQ2(
+ c0: FQ.fromString("208316612133170645758860571704540129781090973693601051684061348604461399206"),
+ c1: FQ.fromString("12617661120538088237397060591907161689901553895660355849494983891299803248390")
+ ),
+ c2: FQ2(
+ c0: FQ.fromString("2897490589776053688661991433341220818937967872052418196321943489809183508515"),
+ c1: FQ.fromString("2730506433347642574983433139433778984782882168213690554721050571242082865799")
+ )
+ ),
+ c1: FQ6(
+ c0: FQ2(
+ c0: FQ.fromString("17870056122431653936196746815433147921488990391314067765563891966783088591110"),
+ c1: FQ.fromString("14314041658607615069703576372547568077123863812415914883625850585470406221594")
+ ),
+ c1: FQ2(
+ c0: FQ.fromString("10123533891707846623287020000407963680629966110211808794181173248765209982878"),
+ c1: FQ.fromString("5062091880848845693514855272640141851746424235009114332841857306926659567101")
+ ),
+ c2: FQ2(
+ c0: FQ.fromString("9839781502639936537333620974973645053542086898304697594692219798017709586567"),
+ c1: FQ.fromString("1583892292110602864638265389721494775152090720173641072176370350017825640703")
+ )
+ )
+ )
+ let expect = FQ12(
+ c0: FQ6(
+ c0: FQ2(
+ c0: FQ.fromString("18388750939593263065521177085001223024106699964957029146547831509155008229833"),
+ c1: FQ.fromString("18370529854582635460997127698388761779167953912610241447912705473964014492243")
+ ),
+ c1: FQ2(
+ c0: FQ.fromString("3691824277096717481466579496401243638295254271265821828017111951446539785268"),
+ c1: FQ.fromString("20513494218085713799072115076991457239411567892860153903443302793553884247235")
+ ),
+ c2: FQ2(
+ c0: FQ.fromString("12214155472433286415803224222551966441740960297013786627326456052558698216399"),
+ c1: FQ.fromString("10987494248070743195602580056085773610850106455323751205990078881956262496575")
+ )
+ ),
+ c1: FQ6(
+ c0: FQ2(
+ c0: FQ.fromString("5134522153456102954632718911439874984161223687865160221119284322136466794876"),
+ c1: FQ.fromString("20119236909927036376726859192821071338930785378711977469360149362002019539920")
+ ),
+ c1: FQ2(
+ c0: FQ.fromString("8839766648621210419302228913265679710586991805716981851373026244791934012854"),
+ c1: FQ.fromString("9103032146464138788288547957401673544458789595252696070370942789051858719203")
+ ),
+ c2: FQ2(
+ c0: FQ.fromString("10378379548636866240502412547812481928323945124508039853766409196375806029865"),
+ c1: FQ.fromString("9021627154807648093720460686924074684389554332435186899318369174351765754041")
+ )
+ )
+ )
+
+ var next = start
+ for i in 0..<100:
+ next = next * start
+
+ var cpy = next
+ for i in 0..<10:
+ next = next.squared()
+
+ for i in 0..<10:
+ next = next + start
+ next = next - cpy
+ next = -next
+
+ next = next.squared()
+ result = (expect == next)
+
+proc fpSerializeTests[T](): bool =
+ when (T is FQ) or (T is FR):
+ var buffer: array[32, byte]
+ elif (T is FQ2):
+ var buffer: array[64, byte]
+ else:
+ {.fatal.}
+
+ for i in 0..<1000:
+ var e = T.random()
+ zeroMem(addr buffer[0], sizeof(buffer))
+ if not e.toBytes(buffer):
+ return false
+ var a: T
+ if not a.fromBytes(buffer):
+ return false
+ if a != e:
+ return false
+ return true
+
+proc fq2SerializeTestVectors(): bool =
+ const vectors = [
+ FQ2(
+ c0: FQ([12685471316754074400'u64, 5151117139186389981'u64,
+ 1811926512010801501'u64, 2926027770199945729'u64]),
+ c1: FQ([13288357145490715372'u64, 8465179270531902744'u64,
+ 2331932027798174928'u64, 1169568334929779847'u64])
+ ),
+ FQ2(
+ c0: FQ([6571363706651148129'u64, 12259671536166748744'u64,
+ 13297153216522874336'u64, 3368736813872212066'u64]),
+ c1: FQ([7356918428694088001'u64, 13325610168162790738'u64,
+ 11761401944674591087'u64, 2142266911265180485'u64])
+ ),
+ FQ2(
+ c0: FQ([12770271250542491457'u64, 5841829129088508933'u64,
+ 5021659154182959822'u64, 765728708107386899'u64]),
+ c1: FQ([9814770014224857768'u64, 169926129335489937'u64,
+ 4476430648250845846'u64, 575721800450622933'u64])
+ ),
+ FQ2(
+ c0: FQ([10535443743532733005'u64, 18354663162560926093'u64,
+ 3005889269269496788'u64, 892863378917010121'u64]),
+ c1: FQ([9912639056721134596'u64, 6115953886839683024'u64,
+ 4097812286267812943'u64, 1337629367136352970'u64])
+ ),
+ FQ2(
+ c0: FQ([7658679475413450244'u64, 11440992707440007515'u64,
+ 16146061400040738154'u64, 991671862947387812'u64]),
+ c1: FQ([2385857951922426638'u64, 6278331068203224119'u64,
+ 8247542493832618243'u64, 2945883060694238627'u64])
+ )
+ ]
+
+ const expects = [
+ "06b812bee59693d4f9f18dc46c55afe42fc5c18965669316117850ca22f55ffa44dda4f58baf6cdf629ecacae4a810098fc7d68a6bfcd200ca59322e37a4be3c",
+ "00a41dd99c355e6984dedfc9c6752cd22b6d4d70283a128e4399734fbaa715724e0494d2d0cc7b0c71bda29d304a60cf6b3a69e366a3d50d80bfe441192d778d",
+ "08f943db03ed61e8f2633740bdc071b76c27547891fe90d56776f9ef2a16de98dfd7d0a481fd5efb55374ea3762d879d226ac9bf7c0b347bae142e27f97d03ed",
+ "068744cde0af982bff29d66a0e5799e78b350216ce53da6d828ca64d94bcd482af8816b7cad0dea041604d5b3ee5ddf2c5b65fc394e1752f6fa52133547a44bc",
+ "09042d4acda2f2ff75073700783010461c5250f10724a0c27ecd295b2bda961245c9a740d3d8de3dbf6ed4fe142ee5480bd96a70d9a4442385718c4995b04b8b"
+ ]
+
+ var buffer: array[64, byte]
+ for i in 0..