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..