From 4ff0e3d90b15b8176404e9878260340a0646b7bd Mon Sep 17 00:00:00 2001 From: Mamy Ratsimbazafy Date: Mon, 16 Mar 2020 16:33:51 +0100 Subject: [PATCH] Internals refactor + renewed focus on perf (#17) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Lay out the refactoring objectives and tradeoffs * Refactor the 32 and 64-bit primitives [skip ci] * BigInts and Modular BigInts compile * Make the bigints test compile * Fix modular reduction * Fix reduction tests vs GMP * Implement montegomery mul, pow, inverse, WIP finite field compilation * Make FiniteField compile * Fix exponentiation compilation * Fix Montgomery magic constant computation for 2^64 words * Fix typo in non-optimized CIOS - passing finite fields IO tests * Add limbs comparisons [skip ci] * Fix on precomputation of the Montgomery magic constant * Passing all tests including đť”˝p2 * modular addition, the test for mersenne prime was wrong * update benches * Fix "nimble test" + typo on out-of-place field addition * bigint division, normalization is needed: https://travis-ci.com/github/mratsim/constantine/jobs/298359743 * missing conversion in subborrow non-x86 fallback - https://travis-ci.com/github/mratsim/constantine/jobs/298359744 * Fix little-endian serialization * Constantine32 flag to run 32-bit constantine on 64-bit machines * IO Field test, ensure that BaseType is used instead of uint64 when the prime can field in uint32 * Implement proper addcarry and subborrow fallback for the compile-time VM * Fix export issue when the logical wordbitwidth == physical wordbitwidth - passes all tests (32-bit and 64-bit) * Fix uint128 on ARM * Fix C++ conditional copy and ARM addcarry/subborrow * Add investigation for SIGFPE in Travis * Fix debug display for unsafeDiv2n1n * multiplexer typo * moveMem bug in glibc of Ubuntu 16.04? * Was probably missing an early clobbered register annotation on conditional mov * Note on Montgomery-friendly moduli * Strongly suspect a GCC before GCC 7 codegen bug (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87139) * hex conversion was (for debugging) not taking requested order into account + inlining comment * Use 32-bit limbs on ARM64, uint128 builtin __udivti4 bug? * Revert "Use 32-bit limbs on ARM64, uint128 builtin __udivti4 bug?" This reverts commit 087f9aa7fb40bbd058d05cbd8eec7fc082911f49. * Fix subborrow fallback for non-x86 (need to maks the borrow) --- .travis.yml | 8 +- README.md | 130 ++- azure-pipelines.yml | 4 +- bench_eth_curves_clang_old | Bin 122904 -> 0 bytes bench_eth_curves_gcc_old | Bin 154600 -> 0 bytes benchmarks/bls12_381_fp.nim | 2 +- benchmarks/bn254_fp.nim | 2 +- benchmarks/secp256k1_fp.nim | 2 +- constantine.nimble | 67 +- constantine/arithmetic/README.md | 15 + .../{bigints_checked.nim => bigints.nim} | 199 +++-- constantine/arithmetic/bigints_raw.nim | 830 ------------------ constantine/arithmetic/finite_fields.nim | 37 +- constantine/arithmetic/limbs.nim | 445 ++++++++++ constantine/arithmetic/montgomery.nim | 473 ++++++++++ constantine/arithmetic/precomputed.nim | 126 ++- constantine/config/common.nim | 18 +- constantine/config/curves.nim | 20 +- constantine/config/curves_parser.nim | 2 +- constantine/io/io_bigints.nim | 49 +- constantine/io/io_fields.nim | 2 +- constantine/primitives.nim | 21 + constantine/primitives/README.md | 83 ++ constantine/primitives/addcarry_subborrow.nim | 168 ++++ constantine/primitives/constant_time.nim | 95 +- .../primitives/constant_time_types.nim | 41 + constantine/primitives/extended_precision.nim | 250 +----- .../extended_precision_64bit_uint128.nim | 81 ++ .../extended_precision_x86_64_gcc.nim | 60 ++ .../extended_precision_x86_64_msvc.nim | 78 ++ constantine/primitives/multiplexers.nim | 161 ++++ constantine/primitives/research/README.md | 45 + .../research/addcarry_subborrow_compiler.nim | 133 +++ .../tower_field_extensions/abelian_groups.nim | 2 +- tests/prng.nim | 5 +- tests/test_bigints.nim | 4 +- tests/test_bigints_multimod.nim | 52 +- tests/test_bigints_vs_gmp.nim | 24 +- tests/test_finite_fields_powinv.nim | 2 +- tests/test_finite_fields_vs_gmp.nim | 4 +- tests/test_fp2.nim | 4 +- tests/test_io_bigints.nim | 10 +- tests/test_io_fields.nim | 42 +- tests/test_primitives.nim | 2 +- 44 files changed, 2413 insertions(+), 1385 deletions(-) delete mode 100755 bench_eth_curves_clang_old delete mode 100755 bench_eth_curves_gcc_old rename constantine/arithmetic/{bigints_checked.nim => bigints.nim} (65%) delete mode 100644 constantine/arithmetic/bigints_raw.nim create mode 100644 constantine/arithmetic/limbs.nim create mode 100644 constantine/arithmetic/montgomery.nim create mode 100644 constantine/primitives.nim create mode 100644 constantine/primitives/addcarry_subborrow.nim create mode 100644 constantine/primitives/constant_time_types.nim create mode 100644 constantine/primitives/extended_precision_64bit_uint128.nim create mode 100644 constantine/primitives/extended_precision_x86_64_gcc.nim create mode 100644 constantine/primitives/extended_precision_x86_64_msvc.nim create mode 100644 constantine/primitives/multiplexers.nim create mode 100644 constantine/primitives/research/README.md create mode 100644 constantine/primitives/research/addcarry_subborrow_compiler.nim diff --git a/.travis.yml b/.travis.yml index 4a6202a..d172ad6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,21 +11,23 @@ matrix: # Constantine only works with Nim devel # Build and test using both gcc and clang # Build and test on both x86-64 and ARM64 - - os: linux + # Ubuntu Bionic (18.04) is needed, it includes + # GCC 7 codegen fixes to addcarry_u64. + - dist: bionic arch: amd64 env: - ARCH=amd64 - CHANNEL=devel compiler: gcc - - os: linux + - dist: bionic arch: arm64 env: - ARCH=arm64 - CHANNEL=devel compiler: gcc - - os: linux + - dist: bionic arch: amd64 env: - ARCH=amd64 diff --git a/README.md b/README.md index 59c183d..db7ac10 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,20 @@ You can install the developement version of the library through nimble with the nimble install https://github.com/mratsim/constantine@#master ``` +For speed it is recommended to prefer Clang, MSVC or ICC over GCC. +GCC does not properly optimize add-with-carry and sub-with-borrow loops (see [Compiler-caveats](#Compiler-caveats)). + +Further if using GCC, GCC 7 at minimum is required, previous versions +generated incorrect add-with-carry code. + ## Target audience The library aims to be a portable, compact and hardened library for elliptic curve cryptography needs, in particular for blockchain protocols and zero-knowledge proofs system. The library focuses on following properties: - constant-time (not leaking secret data via side-channels) -- generated code size, datatype size and stack usage - performance +- generated code size, datatype size and stack usage in this order @@ -54,6 +60,128 @@ actively hinder you by: A growing number of attack vectors is being collected for your viewing pleasure at https://github.com/mratsim/constantine/wiki/Constant-time-arithmetics +## Performance + +High-performance is a sought out property. +Note that security and side-channel resistance takes priority over performance. + +New applications of elliptic curve cryptography like zero-knowledge proofs or +proof-of-stake based blockchain protocols are bottlenecked by cryptography. + +### In blockchain + +Ethereum 2 clients spent or use to spend anywhere between 30% to 99% of their processing time verifying the signatures of block validators on R&D testnets +Assuming we want nodes to handle a thousand peers, if a cryptographic pairing takes 1ms, that represents 1s of cryptography per block to sign with a target +block frequency of 1 every 6 seconds. + +### In zero-knowledge proofs + +According to https://medium.com/loopring-protocol/zksnark-prover-optimizations-3e9a3e5578c0 +a 16-core CPU can prove 20 transfers/second or 10 transactions/second. +The previous implementation was 15x slower and one of the key optimizations +was changing the elliptic curve cryptography backend. +It had a direct implication on hardware cost and/or cloud computing resources required. + +### Compiler caveats + +Unfortunately compilers and in particular GCC are not very good at optimizing big integers and/or cryptographic code even when using intrinsics like `addcarry_u64`. + +Compilers with proper support of `addcarry_u64` like Clang, MSVC and ICC +may generate code up to 20~25% faster than GCC. + +This is explained by the GMP team: https://gmplib.org/manual/Assembly-Carry-Propagation.html +and can be reproduced with the following C code. + +See https://gcc.godbolt.org/z/2h768y +```C +#include +#include + +void add256(uint64_t a[4], uint64_t b[4]){ + uint8_t carry = 0; + for (int i = 0; i < 4; ++i) + carry = _addcarry_u64(carry, a[i], b[i], &a[i]); +} +``` + +GCC +```asm +add256: + movq (%rsi), %rax + addq (%rdi), %rax + setc %dl + movq %rax, (%rdi) + movq 8(%rdi), %rax + addb $-1, %dl + adcq 8(%rsi), %rax + setc %dl + movq %rax, 8(%rdi) + movq 16(%rdi), %rax + addb $-1, %dl + adcq 16(%rsi), %rax + setc %dl + movq %rax, 16(%rdi) + movq 24(%rsi), %rax + addb $-1, %dl + adcq %rax, 24(%rdi) + ret +``` + +Clang +```asm +add256: + movq (%rsi), %rax + addq %rax, (%rdi) + movq 8(%rsi), %rax + adcq %rax, 8(%rdi) + movq 16(%rsi), %rax + adcq %rax, 16(%rdi) + movq 24(%rsi), %rax + adcq %rax, 24(%rdi) + retq +``` + +### Inline assembly + +Constantine uses inline assembly for a very restricted use-case: "conditional mov", +and a temporary use-case "hardware 128-bit division" that will be replaced ASAP (as hardware division is not constant-time). + +Using intrinsics otherwise significantly improve code readability, portability, auditability and maintainability. + +#### Future optimizations + +In the future more inline assembly primitives might be added provided the performance benefit outvalues the significant complexity. +In particular, multiprecision multiplication and squaring on x86 can use the instructions MULX, ADCX and ADOX +to multiply-accumulate on 2 carry chains in parallel (with instruction-level parallelism) +and improve performance by 15~20% over an uint128-based implementation. +As no compiler is able to generate such code even when using the `_mulx_u64` and `_addcarryx_u64` intrinsics, +either the assembly for each supported bigint size must be hardcoded +or a "compiler" must be implemented in macros that will generate the required inline assembly at compile-time. + +Such a compiler can also be used to overcome GCC codegen deficiencies, here is an example for add-with-carry: +https://github.com/mratsim/finite-fields/blob/d7f6d8bb/macro_add_carry.nim + +## Sizes: code size, stack usage + +Thanks to 10x smaller key sizes for the same security level as RSA, elliptic curve cryptography +is widely used on resource-constrained devices. + +Constantine is actively optimize for code-size and stack usage. +Constantine does not use heap allocation. + +At the moment Constantine is optimized for 32-bit and 64-bit CPUs. + +When performance and code size conflicts, a careful and informed default is chosen. +In the future, a compile-time flag that goes beyond the compiler `-Os` might be provided. + +### Example tradeoff + +Unrolling Montgomery Multiplication brings about 15% performance improvement +which translate to ~15% on all operations in Constantine as field multiplication bottlenecks +all cryptographic primitives. +This is considered a worthwhile tradeoff on all but the most constrained CPUs +with those CPUs probably being 8-bit or 16-bit. + ## License Licensed and distributed under either of diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bb936fc..c379eaa 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -19,12 +19,12 @@ strategy: CHANNEL: devel TEST_LANG: cpp Linux_devel_64bit: - VM: 'ubuntu-16.04' + VM: 'ubuntu-18.04' UCPU: amd64 CHANNEL: devel TEST_LANG: c Linux_cpp_devel_64bit: - VM: 'ubuntu-16.04' + VM: 'ubuntu-18.04' UCPU: amd64 CHANNEL: devel WEAVE_TEST_LANG: cpp diff --git a/bench_eth_curves_clang_old b/bench_eth_curves_clang_old deleted file mode 100755 index 7d2fb6643d4b3f0dcdf878f5507d31430d65ab92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122904 zcmeEveS8!}w)RYtfk6^_P=ZlG5{*j0k3>btLLh;F-8ey1R9yK`kpL?Kk{LneBRC1r zw1cd=>bmagU3Zncx~uNGh^UYd$RxM|0YwmjfQUVgd@2w?$oo81-If@;_6|vKIvw^e(b3<*&^;YxOum)y{MKlmd`BNgdq=+GVnA=Uzn!o7b7BG#@w1<98FVD#$#P4r^ybelRzCY_x6UWawf1{HS*F-ehew84&c#${!B+2f9c|H6o~q6aH(Y;p$<(W_75!%Y z%zNXo+XYCZFRLmMCOKau-W-k#@VpTJ)9{}(aUTBjN77+E?Bn_P&pO!0&iJ2#|L5Yr z4G&(=`o*@#M0;#vGZLL0@V`C&EBJ3sGsii2Z-@V>_;3F@;f3wl|Lypn^K64cgtz&R z?WQ-6M7S3JZCGNH8xUxzp4VH6|4l3LueK7upq2Q)w-P_2mH3UV#AmdEhssv+FK#9N z3dE=5fAilKM6`sT9j(MqYbE}hR^s1lC4PP@@$*`VKio?E4Tw+2|K`7Qoesw(%_9;1 z68~GG69?w!gPe{xIL2xtipJhQe){+clS+K!r;jKaG3^zUejk zmeac_^4hC=aUjTD62OxZz@HMD{UaZXk7Hhza3Xgxp5$x%J@e~l!O~og>oC5I>ojQ+ zSX+{#zZHH?q$fI5E9}2;64+9L<2Ho(Yg)%-&@tR1eviGrS@HIyNNX1VgtgwKH;W&c zDI4hCEdDYpKD$}Goxe}B_;0NG^P9!n`PF9e=UDX*Zx(OoAK6O$Jf5kADs_>yR zh`8)NhRIUH^&#(Je&De87qSx@9I5czxk zf{&~-s(whVJTO45Y)Dj{)oR@tUnf*>8s-F6;BKrhR=O2N%M-rRJQvD3{MX&C2J$|q z;X&2E@};Oo-m_@8F|`I!<~a1d+Vuq@oTICXuU5L15e!FFarB+^!TQ1zkk^85xXGc2 z<|(+d;Evlgy%BKlUXb8e*$1iSYs1-O6*_(Nf&{!J;%!0-Al3(YyhcfyS8p^2-NHPB z(u&HD`LZ-)$RRaQ(V*#kHng2_z80uHggXEF6-os{C~?f3*BvE-y)I@&!PLveBc(eY z1@njmW!WZmr4y5k|5fFcEhXvIiBpzy7UnOTvVybo?R!^o(k@Ky zo4@e-^X{%v11A&w>7{+I0&tNm#`t?pKjSs>e)WLUp&0|rYkz2LEGpkS=a!u9-r!XaDva&7Q!Q`w z^@*Z;iE8x5_<7XwW13QUS~HXtgwp&o3af!CrI&q>^X`IC1)~eb6pRfIL4rm1(YC~RVQxI~rJNhdoNXP{+>okx8&o~-Uc^@o zM~1XLG6NZ-;adGGgp58I=7%ew5>uV z4lyq8U4plQ<)GNc!8x_&OKd5qf}Bn`A0DRZ3s_#8x1bac7F4>s`hnLd9Rb5g&wD1g z8mFBx$PJiOXULm%Sk>Emorf^d+&Ra*`Vl||^i4p$<`HnS;w85?4e)SH-=gWqHT|?2 zT;Rw-?c^(};YRBhD^F}jld4f_lADxQ_`lYS-`l-OMsL)4Ck`TM?#LhS(Z?sEJyPbs ziaIsDT=uFwrdP%7TIkgSZf`KNr#i3F1IW=YHCTR#(4cQC_~O;S&N*fpXhAc|&1;!b z)|K!U>3?R>tIz7{_UgW_9yL_h)qw;@Bu&-Vsg-+@kmg~U$3sS->gY6w(W1A}RpuJZ zeDafgWm&aqB!$R)J7Rp*+-Yv#IjSL$awMmsIsZ`g!)oP;cFM9!yVOOo6pQkz+t)s( zyeJS(4D9Zs8ij5TCi@}fsR}F)Wq=pN!TC7iHVhU?s&rLtHX?mPG~?ZkcoUT5%{`_B z|A=TX&6cQ2Yasf-Ai$#n4Nhgj4&)7=06sKh!Skfhyv9ZhWfv{9fR#G(9gda!W7j_G z)yNRF(o9fYjik8C9EXVVCz)4u)~dNRsxqtw1c4d{tVH<%n1F{hJs!d*#(>CM0`D)! z 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 diff --git a/bench_eth_curves_gcc_old b/bench_eth_curves_gcc_old deleted file mode 100755 index ff68a117d557badd6f325e347928fd3d5e48792f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154600 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∈ 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 1.1.0" ### Helper functions -proc test(path: string) = +proc test(flags, path: string) = if not dirExists "build": mkDir "build" # Compilation language is controlled by WEAVE_TEST_LANG @@ -17,35 +17,64 @@ proc test(path: string) = if existsEnv"TEST_LANG": lang = getEnv"TEST_LANG" + var cc = "" + if existsEnv"CC": + cc = " --cc:" & getEnv"CC" + echo "\n========================================================================================" - echo "Running ", path + echo "Running [flags: ", flags, "] ", path echo "========================================================================================" - exec "nim " & lang & " --verbosity:0 --outdir:build -r --hints:off --warnings:off " & path + exec "nim " & lang & cc & " " & flags & " --verbosity:0 --outdir:build -r --hints:off --warnings:off " & path ### tasks task test, "Run all tests": # -d:testingCurves is configured in a *.nim.cfg for convenience - test "tests/test_primitives.nim" + test "", "tests/test_primitives.nim" - test "tests/test_io_bigints.nim" - test "tests/test_bigints.nim" - test "tests/test_bigints_multimod.nim" + test "", "tests/test_io_bigints.nim" + test "", "tests/test_bigints.nim" + test "", "tests/test_bigints_multimod.nim" - test "tests/test_io_fields" - test "tests/test_finite_fields.nim" - test "tests/test_finite_fields_powinv.nim" + test "", "tests/test_io_fields" + test "", "tests/test_finite_fields.nim" + test "", "tests/test_finite_fields_powinv.nim" - test "tests/test_bigints_vs_gmp.nim" - test "tests/test_finite_fields_vs_gmp.nim" + test "", "tests/test_bigints_vs_gmp.nim" + test "", "tests/test_finite_fields_vs_gmp.nim" + + if sizeof(int) == 8: # 32-bit tests + test "-d:Constantine32", "tests/test_primitives.nim" + + test "-d:Constantine32", "tests/test_io_bigints.nim" + test "-d:Constantine32", "tests/test_bigints.nim" + test "-d:Constantine32", "tests/test_bigints_multimod.nim" + + test "-d:Constantine32", "tests/test_io_fields" + test "-d:Constantine32", "tests/test_finite_fields.nim" + test "-d:Constantine32", "tests/test_finite_fields_powinv.nim" + + test "-d:Constantine32", "tests/test_bigints_vs_gmp.nim" + test "-d:Constantine32", "tests/test_finite_fields_vs_gmp.nim" task test_no_gmp, "Run tests that don't require GMP": # -d:testingCurves is configured in a *.nim.cfg for convenience - test "tests/test_primitives.nim" + test "", "tests/test_primitives.nim" - test "tests/test_io_bigints.nim" - test "tests/test_bigints.nim" - test "tests/test_bigints_multimod.nim" + test "", "tests/test_io_bigints.nim" + test "", "tests/test_bigints.nim" + test "", "tests/test_bigints_multimod.nim" - test "tests/test_io_fields" - test "tests/test_finite_fields.nim" - test "tests/test_finite_fields_powinv.nim" + test "", "tests/test_io_fields" + test "", "tests/test_finite_fields.nim" + test "", "tests/test_finite_fields_powinv.nim" + + if sizeof(int) == 8: # 32-bit tests + test "-d:Constantine32", "tests/test_primitives.nim" + + test "-d:Constantine32", "tests/test_io_bigints.nim" + test "-d:Constantine32", "tests/test_bigints.nim" + test "-d:Constantine32", "tests/test_bigints_multimod.nim" + + test "-d:Constantine32", "tests/test_io_fields" + test "-d:Constantine32", "tests/test_finite_fields.nim" + test "-d:Constantine32", "tests/test_finite_fields_powinv.nim" diff --git a/constantine/arithmetic/README.md b/constantine/arithmetic/README.md index 9ce12e1..b1c20f9 100644 --- a/constantine/arithmetic/README.md +++ b/constantine/arithmetic/README.md @@ -4,6 +4,17 @@ This folder contains the implementation of - big integers - finite field arithmetic (i.e. modular arithmetic) +As a tradeoff between speed, code size and compiler-enforced dependent type checking, the library is structured the following way: +- Finite Field: statically parametrized by an elliptic curve +- Big Integers: statically parametrized by the bit width of the field modulus +- Limbs: statically parametrized by the number of words to handle the bitwidth + +This allows to reuse the same implementation at the limbs-level for +curves that required the same number of words to save on code size, +for example secp256k1 and BN254. +It also enables compiler unrolling, inlining and register optimization, +where code size is not an issue for example for multi-precision addition. + ## References - Analyzing and Comparing Montgomery Multiplication Algorithms @@ -18,3 +29,7 @@ This folder contains the implementation of Chapter 5 of Guide to Pairing-Based Cryptography\ Jean Luc Beuchat, Luis J. Dominguez Perez, Sylvain Duquesne, Nadia El Mrabet, Laura Fuentes-Castañeda, Francisco RodrĂ­guez-HenrĂ­quez, 2017\ https://www.researchgate.net/publication/319538235_Arithmetic_of_Finite_Fields + +- Faster big-integer modular multiplication for most moduli\ + Gautam Botrel, Gus Gutoski, and Thomas Piellard, 2020\ + https://hackmd.io/@zkteam/modular_multiplication diff --git a/constantine/arithmetic/bigints_checked.nim b/constantine/arithmetic/bigints.nim similarity index 65% rename from constantine/arithmetic/bigints_checked.nim rename to constantine/arithmetic/bigints.nim index f496bdc..9399ed9 100644 --- a/constantine/arithmetic/bigints_checked.nim +++ b/constantine/arithmetic/bigints.nim @@ -7,32 +7,59 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - ./bigints_raw, - ../primitives/constant_time, - ../config/common + ../config/common, + ../primitives, + ./limbs, + ./montgomery # ############################################################ # -# BigInts type-checked API +# BigInts # # ############################################################ -# The "checked" API is exported as a building blocks -# with enforced compile-time checking of BigInt bitsize +# The API is exported as a building block +# with enforced compile-time checking of BigInt bitwidth # and memory ownership. + +# ############################################################ +# Design # -# The "raw" compute API uses views to avoid code duplication -# due to generic/static monomorphization. +# Control flow should only depends on the static maximum number of bits +# This number is defined per Finite Field/Prime/Elliptic Curve # -# The "checked" API is a thin wrapper above the "raw" API to get the best of both world: -# - small code footprint -# - compiler enforced checks: types, bitsizes (dependant types) -# - compiler enforced memory: stack allocation and buffer ownership +# Data Layout +# +# The previous implementation of Constantine used type-erased views +# to optimized code-size (1) +# Also instead of using the full 64-bit of an uint64 it used +# 63-bit with the last bit to handle carries (2) +# +# (1) brought an advantage in terms of code-size if multiple curves +# were supported. +# However it prevented unrolling for some performance critical routines +# like addition and Montgomery multiplication. Furthermore, addition +# is only 1 or 2 instructions per limbs meaning unrolling+inlining +# is probably smaller in code-size than a function call. +# +# (2) Not using the full 64-bit eased carry and borrow handling. +# Also on older x86 Arch, the add-with-carry "ADC" instruction +# may be up to 6x slower than plain "ADD" with memory operand in a carry-chain. +# +# However, recent CPUs (less than 5 years) have reasonable or lower ADC latencies +# compared to the shifting and masking required when using 63 bits. +# Also we save on words to iterate on (1 word for BN254, secp256k1, BLS12-381) +# +# Furthermore, pairing curves are not fast-reduction friendly +# meaning that lazy reductions and lazy carries are impractical +# and so it's simpler to always carry additions instead of +# having redundant representations that forces costly reductions before multiplications. +# https://github.com/mratsim/constantine/issues/15 func wordsRequired(bits: int): int {.compileTime.} = ## Compute the number of limbs required # from the **announced** bit length - (bits + WordBitSize - 1) div WordBitSize + (bits + WordBitWidth - 1) div WordBitWidth type BigInt*[bits: static int] = object @@ -41,28 +68,18 @@ type ## - "bits" is the announced bit-length of the BigInt ## This is public data, usually equal to the curve prime bitlength. ## - ## - "bitLength" is the internal bitlength of the integer - ## This differs from the canonical bit-length as - ## Constantine word-size is smaller than a machine word. - ## This value should never be used as-is to prevent leaking secret data. - ## Computing this value requires constant-time operations. - ## Using this value requires converting it to the # of limbs in constant-time - ## ## - "limbs" is an internal field that holds the internal representation ## of the big integer. Least-significant limb first. Within limbs words are native-endian. ## ## This internal representation can be changed ## without notice and should not be used by external applications or libraries. - bitLength*: uint32 limbs*: array[bits.wordsRequired, Word] -template view*(a: BigInt): BigIntViewConst = - ## Returns a borrowed type-erased immutable view to a bigint - BigIntViewConst(cast[BigIntView](a.unsafeAddr)) - -template view*(a: var BigInt): BigIntViewMut = - ## Returns a borrowed type-erased mutable view to a mutable bigint - BigIntViewMut(cast[BigIntView](a.addr)) +# For unknown reason, `bits` doesn't semcheck if +# `limbs: Limbs[bits.wordsRequired]` +# with +# `Limbs[N: static int] = distinct array[N, Word]` +# so we don't set Limbs as a distinct type debug: import strutils @@ -70,9 +87,7 @@ debug: func `$`*(a: BigInt): string = result = "BigInt[" result.add $BigInt.bits - result.add "](bitLength: " - result.add $a.bitLength - result.add ", limbs: [" + result.add "](limbs: [" result.add $BaseType(a.limbs[0]) & " (0x" & toHex(BaseType(a.limbs[0])) & ')' for i in 1 ..< a.limbs.len: result.add ", " @@ -83,54 +98,40 @@ debug: {.push raises: [].} {.push inline.} -func setInternalBitLength*(a: var BigInt) = - ## Derive the actual bitsize used internally of a BigInt - ## from the announced BigInt bitsize - ## and set the bitLength field of that BigInt - ## to that computed value. - a.bitLength = uint32 static(a.bits + a.bits div WordBitSize) - func `==`*(a, b: BigInt): CTBool[Word] = ## Returns true if 2 big ints are equal ## Comparison is constant-time - var accum: Word - for i in static(0 ..< a.limbs.len): - accum = accum or (a.limbs[i] xor b.limbs[i]) - result = accum.isZero + a.limbs == b.limbs func isZero*(a: BigInt): CTBool[Word] = ## Returns true if a big int is equal to zero - a.view.isZero + a.limbs.isZero func setZero*(a: var BigInt) = ## Set a BigInt to 0 - a.setInternalBitLength() - zeroMem(a.limbs[0].unsafeAddr, a.limbs.len * sizeof(Word)) + a.limbs.setZero() func setOne*(a: var BigInt) = ## Set a BigInt to 1 - a.setInternalBitLength() - a.limbs[0] = Word(1) - when a.limbs.len > 1: - zeroMem(a.limbs[1].unsafeAddr, (a.limbs.len-1) * sizeof(Word)) + a.limbs.setOne() func cadd*(a: var BigInt, b: BigInt, ctl: CTBool[Word]): CTBool[Word] = ## Constant-time in-place conditional addition ## The addition is only performed if ctl is "true" ## The result carry is always computed. - cadd(a.view, b.view, ctl) + (CTBool[Word]) cadd(a.limbs, b.limbs, ctl) func csub*(a: var BigInt, b: BigInt, ctl: CTBool[Word]): CTBool[Word] = ## Constant-time in-place conditional addition ## The addition is only performed if ctl is "true" ## The result carry is always computed. - csub(a.view, b.view, ctl) + (CTBool[Word]) csub(a.limbs, b.limbs, ctl) func cdouble*(a: var BigInt, ctl: CTBool[Word]): CTBool[Word] = ## Constant-time in-place conditional doubling ## The doubling is only performed if ctl is "true" ## The result carry is always computed. - cadd(a.view, a.view, ctl) + (CTBool[Word]) cadd(a.limbs, a.limbs, ctl) # ############################################################ # @@ -143,38 +144,38 @@ func cdouble*(a: var BigInt, ctl: CTBool[Word]): CTBool[Word] = func add*(a: var BigInt, b: BigInt): CTBool[Word] = ## Constant-time in-place addition ## Returns the carry - add(a.view, b.view) + (CTBool[Word]) add(a.limbs, b.limbs) func sub*(a: var BigInt, b: BigInt): CTBool[Word] = ## Constant-time in-place substraction ## Returns the borrow - sub(a.view, b.view) + (CTBool[Word]) sub(a.limbs, b.limbs) func double*(a: var BigInt): CTBool[Word] = ## Constant-time in-place doubling ## Returns the carry - add(a.view, a.view) + (CTBool[Word]) add(a.limbs, a.limbs) func sum*(r: var BigInt, a, b: BigInt): CTBool[Word] = ## Sum `a` and `b` into `r`. ## `r` is initialized/overwritten ## ## Returns the carry - sum(r.view, a.view, b.view) + (CTBool[Word]) sum(r.limbs, a.limbs, b.limbs) func diff*(r: var BigInt, a, b: BigInt): CTBool[Word] = ## Substract `b` from `a` and store the result into `r`. ## `r` is initialized/overwritten ## ## Returns the borrow - diff(r.view, a.view, b.view) + (CTBool[Word]) diff(r.limbs, a.limbs, b.limbs) func double*(r: var BigInt, a: BigInt): CTBool[Word] = ## Double `a` into `r`. ## `r` is initialized/overwritten ## ## Returns the carry - sum(r.view, a.view, a.view) + (CTBool[Word]) sum(r.limbs, a.limbs, a.limbs) # ############################################################ # @@ -182,9 +183,13 @@ func double*(r: var BigInt, a: BigInt): CTBool[Word] = # # ############################################################ -# Use "csub", which unfortunately requires the first operand to be mutable. -# for example for a <= b, we now that if a-b borrows then b > a and so a<=b is false -# This can be tested with "not csub(a, b, CtFalse)" +func `<`*(a, b: BigInt): CTBool[Word] = + ## Returns true if a < b + a.limbs < b.limbs + +func `<=`*(a, b: BigInt): CTBool[Word] = + ## Returns true if a <= b + a.limbs <= b.limbs # ############################################################ # @@ -202,9 +207,15 @@ func reduce*[aBits, mBits](r: var BigInt[mBits], a: BigInt[aBits], M: BigInt[mBi # Note: for all cryptographic intents and purposes the modulus is known at compile-time # but we don't want to inline it as it would increase codesize, better have Nim # pass a pointer+length to a fixed session of the BSS. - reduce(r.view, a.view, M.view) + reduce(r.limbs, a.limbs, aBits, M.limbs, mBits) -func montyResidue*(mres: var BigInt, a, N, r2modN: BigInt, negInvModWord: static BaseType) = +# ############################################################ +# +# Montgomery Arithmetic +# +# ############################################################ + +func montyResidue*(mres: var BigInt, a, N, r2modM: BigInt, m0ninv: static BaseType, canUseNoCarryMontyMul: static bool) = ## Convert a BigInt from its natural representation ## to the Montgomery n-residue form ## @@ -213,9 +224,15 @@ func montyResidue*(mres: var BigInt, a, N, r2modN: BigInt, negInvModWord: static ## Caller must take care of properly switching between ## the natural and montgomery domain. ## Nesting Montgomery form is possible by applying this function twice. - montyResidue(mres.view, a.view, N.view, r2modN.view, Word(negInvModWord)) + ## + ## The Montgomery Magic Constants: + ## - `m0ninv` is µ = -1/N (mod M) + ## - `r2modM` is R² (mod M) + ## with W = M.len + ## and R = (2^WordBitSize)^W + montyResidue(mres.limbs, a.limbs, N.limbs, r2modM.limbs, m0ninv, canUseNoCarryMontyMul) -func redc*[mBits](r: var BigInt[mBits], a, N: BigInt[mBits], negInvModWord: static BaseType) = +func redc*[mBits](r: var BigInt[mBits], a, M: BigInt[mBits], m0ninv: static BaseType, canUseNoCarryMontyMul: static bool) = ## Convert a BigInt from its Montgomery n-residue form ## to the natural representation ## @@ -227,31 +244,27 @@ func redc*[mBits](r: var BigInt[mBits], a, N: BigInt[mBits], negInvModWord: stat var one {.noInit.}: BigInt[mBits] one.setOne() one - redc(r.view, a.view, one.view, N.view, Word(negInvModWord)) + redc(r.limbs, a.limbs, one.limbs, M.limbs, m0ninv, canUseNoCarryMontyMul) -# ############################################################ -# -# Montgomery Arithmetic -# -# ############################################################ - -func montyMul*(r: var BigInt, a, b, M: BigInt, negInvModWord: static BaseType) = +func montyMul*(r: var BigInt, a, b, M: BigInt, negInvModWord: static BaseType, canUseNoCarryMontyMul: static bool) = ## Compute r <- a*b (mod M) in the Montgomery domain ## ## This resets r to zero before processing. Use {.noInit.} ## to avoid duplicating with Nim zero-init policy - montyMul(r.view, a.view, b.view, M.view, Word(negInvModWord)) + montyMul(r.limbs, a.limbs, b.limbs, M.limbs, negInvModWord, canUseNoCarryMontyMul) -func montySquare*(r: var BigInt, a, M: BigInt, negInvModWord: static BaseType) = +func montySquare*(r: var BigInt, a, M: BigInt, negInvModWord: static BaseType, canUseNoCarryMontyMul: static bool) = ## Compute r <- a^2 (mod M) in the Montgomery domain ## ## This resets r to zero before processing. Use {.noInit.} ## to avoid duplicating with Nim zero-init policy - montySquare(r.view, a.view, M.view, Word(negInvModWord)) + montySquare(r.limbs, a.limbs, M.limbs, negInvModWord, canUseNoCarryMontyMul) func montyPow*[mBits, eBits: static int]( a: var BigInt[mBits], exponent: BigInt[eBits], - M, one: BigInt[mBits], negInvModWord: static BaseType, windowSize: static int) = + M, one: BigInt[mBits], negInvModWord: static BaseType, windowSize: static int, + canUseNoCarryMontyMul: static bool + ) = ## Compute a <- a^exponent (mod M) ## ``a`` in the Montgomery domain ## ``exponent`` is any BigInt, in the canonical domain @@ -268,16 +281,14 @@ func montyPow*[mBits, eBits: static int]( const scratchLen = if windowSize == 1: 2 else: (1 shl windowSize) + 1 - var scratchSpace {.noInit.}: array[scratchLen, BigInt[mBits]] - var scratchPtrs {.noInit.}: array[scratchLen, BigIntViewMut] - for i in 0 ..< scratchLen: - scratchPtrs[i] = scratchSpace[i].view() - - montyPow(a.view, expBE, M.view, one.view, Word(negInvModWord), scratchPtrs) + var scratchSpace {.noInit.}: array[scratchLen, Limbs[mBits.wordsRequired]] + montyPow(a.limbs, expBE, M.limbs, one.limbs, negInvModWord, scratchSpace, canUseNoCarryMontyMul) func montyPowUnsafeExponent*[mBits, eBits: static int]( a: var BigInt[mBits], exponent: BigInt[eBits], - M, one: BigInt[mBits], negInvModWord: static BaseType, windowSize: static int) = + M, one: BigInt[mBits], negInvModWord: static BaseType, windowSize: static int, + canUseNoCarryMontyMul: static bool + ) = ## Compute a <- a^exponent (mod M) ## ``a`` in the Montgomery domain ## ``exponent`` is any BigInt, in the canonical domain @@ -298,16 +309,14 @@ func montyPowUnsafeExponent*[mBits, eBits: static int]( const scratchLen = if windowSize == 1: 2 else: (1 shl windowSize) + 1 - var scratchSpace {.noInit.}: array[scratchLen, BigInt[mBits]] - var scratchPtrs {.noInit.}: array[scratchLen, BigIntViewMut] - for i in 0 ..< scratchLen: - scratchPtrs[i] = scratchSpace[i].view() - - montyPowUnsafeExponent(a.view, expBE, M.view, one.view, Word(negInvModWord), scratchPtrs) + var scratchSpace {.noInit.}: array[scratchLen, Limbs[mBits.wordsRequired]] + montyPowUnsafeExponent(a.limbs, expBE, M.limbs, one.limbs, negInvModWord, scratchSpace, canUseNoCarryMontyMul) func montyPowUnsafeExponent*[mBits: static int]( a: var BigInt[mBits], exponent: openarray[byte], - M, one: BigInt[mBits], negInvModWord: static BaseType, windowSize: static int) = + M, one: BigInt[mBits], negInvModWord: static BaseType, windowSize: static int, + canUseNoCarryMontyMul: static bool + ) = ## Compute a <- a^exponent (mod M) ## ``a`` in the Montgomery domain ## ``exponent`` is a BigInt in canonical representation @@ -324,9 +333,5 @@ func montyPowUnsafeExponent*[mBits: static int]( const scratchLen = if windowSize == 1: 2 else: (1 shl windowSize) + 1 - var scratchSpace {.noInit.}: array[scratchLen, BigInt[mBits]] - var scratchPtrs {.noInit.}: array[scratchLen, BigIntViewMut] - for i in 0 ..< scratchLen: - scratchPtrs[i] = scratchSpace[i].view() - - montyPowUnsafeExponent(a.view, exponent, M.view, one.view, Word(negInvModWord), scratchPtrs) + var scratchSpace {.noInit.}: array[scratchLen, Limbs[mBits.wordsRequired]] + montyPowUnsafeExponent(a.limbs, exponent, M.limbs, one.limbs, negInvModWord, scratchSpace, canUseNoCarryMontyMul) diff --git a/constantine/arithmetic/bigints_raw.nim b/constantine/arithmetic/bigints_raw.nim deleted file mode 100644 index b475c0f..0000000 --- a/constantine/arithmetic/bigints_raw.nim +++ /dev/null @@ -1,830 +0,0 @@ -# Constantine -# Copyright (c) 2018-2019 Status Research & Development GmbH -# Copyright (c) 2020-Present Mamy AndrĂ©-Ratsimbazafy -# Licensed and distributed under either of -# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). -# * Apache v2 license (license terms in the root directory or at 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. - -# ############################################################ -# -# BigInt Raw representation and operations -# -# ############################################################ -# -# This file holds the raw operations done on big ints -# The representation is optimized for: -# - constant-time (not leaking secret data via side-channel) -# - generated code size, datatype size and stack usage -# - performance -# in this order - -# ############################################################ -# Design - -# To avoid carry issues we don't use the -# most significant bit of each machine word. -# i.e. for a uint64 base we only use 63-bit. -# More info: https://github.com/status-im/nim-constantine/wiki/Constant-time-arithmetics#guidelines -# Especially: -# - https://bearssl.org/bigint.html -# - https://cryptojedi.org/peter/data/pairing-20131122.pdf -# - http://docs.milagro.io/en/amcl/milagro-crypto-library-white-paper.html -# -# Note that this might also be beneficial in terms of performance. -# Due to opcode latency, on Nehalem ADC is 6x times slower than ADD -# if it has dependencies (i.e the ADC depends on a previous ADC result) -# -# Control flow should only depends on the static maximum number of bits -# This number is defined per Finite Field/Prime/Elliptic Curve -# -# We internally order the limbs in little-endian -# So the least significant limb is limb[0] -# This is independent from the base type endianness. -# -# Constantine uses Nim generic integer to prevent mixing -# BigInts of different bitlength at compile-time and -# properly statically size the BigInt buffers. -# -# To avoid code-bloat due to monomorphization (i.e. duplicating code per announced bitlength) -# actual computation is deferred to type-erased routines. - -import - ../primitives/constant_time, - ../primitives/extended_precision, - ../config/common -from typetraits import distinctBase - -# ############################################################ -# -# BigInts type-erased API -# -# ############################################################ - -# The "checked" API is exported as a building blocks -# with enforced compile-time checking of BigInt bitsize -# and memory ownership. -# -# The "raw" compute API uses views to avoid code duplication -# due to generic/static monomorphization. -# -# The "checked" API is a thin wrapper above the "raw" API to get the best of both world: -# - small code footprint -# - compiler enforced checks: types, bitsizes -# - compiler enforced memory: stack allocation and buffer ownership - -type - BigIntView* = ptr object - ## Type-erased fixed-precision big integer - ## - ## This type mirrors the BigInt type and is used - ## for the low-level computation API - ## This design - ## - avoids code bloat due to generic monomorphization - ## otherwise each bigint routines would have an instantiation for - ## each static `bits` parameter. - ## - while not forcing the caller to preallocate computation buffers - ## for the high-level API and enforcing bitsizes - ## - avoids runtime bound-checks on the view - ## for performance - ## and to ensure exception-free code - ## even when compiled in non "-d:danger" mode - ## - ## As with the BigInt type: - ## - "bitLength" is the internal bitlength of the integer - ## This differs from the canonical bit-length as - ## Constantine word-size is smaller than a machine word. - ## This value should never be used as-is to prevent leaking secret data. - ## Computing this value requires constant-time operations. - ## Using this value requires converting it to the # of limbs in constant-time - ## - ## - "limbs" is an internal field that holds the internal representation - ## of the big integer. Least-significant limb first. Within limbs words are native-endian. - ## - ## This internal representation can be changed - ## without notice and should not be used by external applications or libraries. - ## - ## Accesses should be done via BigIntViewConst / BigIntViewConst - ## to have the compiler check for mutability - bitLength: uint32 - limbs: UncheckedArray[Word] - - # "Indirection" to enforce pointer types deep immutability - BigIntViewConst* = distinct BigIntView - ## Immutable view into a BigInt - BigIntViewMut* = distinct BigIntView - ## Mutable view into a BigInt - BigIntViewAny* = BigIntViewConst or BigIntViewMut - -# No exceptions allowed -{.push raises: [].} - -# ############################################################ -# -# Deep Mutability safety -# -# ############################################################ - -template `[]`*(v: BigIntViewConst, limbIdx: int): Word = - distinctBase(type v)(v).limbs[limbIdx] - -template `[]`*(v: BigIntViewMut, limbIdx: int): var Word = - distinctBase(type v)(v).limbs[limbIdx] - -template `[]=`*(v: BigIntViewMut, limbIdx: int, val: Word) = - distinctBase(type v)(v).limbs[limbIdx] = val - -template bitSizeof(v: BigIntViewAny): uint32 = - bind BigIntView - distinctBase(type v)(v).bitLength - -const divShiftor = log2(uint32(WordPhysBitSize)) -template numLimbs*(v: BigIntViewAny): int = - ## Compute the number of limbs from - ## the **internal** bitlength - (bitSizeof(v).int + WordPhysBitSize - 1) shr divShiftor - -template setBitLength(v: BigIntViewMut, internalBitLength: uint32) = - distinctBase(type v)(v).bitLength = internalBitLength - -# TODO: Check if repeated v.numLimbs calls are optimized away - -template `[]`*(v: BigIntViewConst, limbIdxFromEnd: BackwardsIndex): Word = - distinctBase(type v)(v).limbs[numLimbs(v).int - int limbIdxFromEnd] - -template `[]`*(v: BigIntViewMut, limbIdxFromEnd: BackwardsIndex): var Word = - distinctBase(type v)(v).limbs[numLimbs(v).int - int limbIdxFromEnd] - -template `[]=`*(v: BigIntViewMut, limbIdxFromEnd: BackwardsIndex, val: Word) = - distinctBase(type v)(v).limbs[numLimbs(v).int - int limbIdxFromEnd] = val - -# ############################################################ -# -# Checks and debug/test only primitives -# -# ############################################################ - -template checkMatchingBitlengths(a, b: distinct BigIntViewAny) = - ## Check that bitlengths of bigints match - ## This is only checked - ## with "-d:debugConstantine" and when assertions are on. - debug: - assert distinctBase(type a)(a).bitLength == - distinctBase(type b)(b).bitLength, "Internal Error: operands bitlength do not match" - -template checkValidModulus(m: BigIntViewConst) = - ## Check that the modulus is valid - ## The check is approximate, it only checks that - ## the most-significant words is non-zero instead of - ## checking that the last announced bit is 1. - ## This is only checked - ## with "-d:debugConstantine" and when assertions are on. - debug: - assert not isZero(m[^1]).bool, "Internal Error: the modulus must use all declared bits" - -template checkOddModulus(m: BigIntViewConst) = - ## CHeck that the modulus is odd - ## and valid for use in the Montgomery n-residue representation - debug: - assert bool(BaseType(m[0]) and 1), "Internal Error: the modulus must be odd to use the Montgomery representation." - -template checkWordShift(k: int) = - ## Checks that the shift is less than the word bit size - debug: - assert k <= WordBitSize, "Internal Error: the shift must be less than the word bit size" - -template checkPowScratchSpaceLen(len: int) = - ## Checks that there is a minimum of scratchspace to hold the temporaries - debug: - assert len >= 2, "Internal Error: the scratchspace for powmod should be equal or greater than 2" - -debug: - func `$`*(a: BigIntViewAny): string = - let len = a.numLimbs() - result = "[" - for i in 0 ..< len - 1: - result.add $a[i] - result.add ", " - result.add $a[len-1] - result.add "] (" - result.add $a.bitSizeof - result.add " bits)" - -# ############################################################ -# -# BigInt primitives -# -# ############################################################ - -func `==`*(a, b: distinct BigIntViewAny): CTBool[Word] = - ## Returns true if 2 big ints are equal - ## Comparison is constant-time - checkMatchingBitlengths(a, b) - var accum: Word - for i in 0 ..< a.numLimbs(): - accum = accum or (a[i] xor b[i]) - result = accum.isZero - -func isZero*(a: BigIntViewAny): CTBool[Word] = - ## Returns true if a big int is equal to zero - var accum: Word - for i in 0 ..< a.numLimbs(): - accum = accum or a[i] - result = accum.isZero() - -func setZero(a: BigIntViewMut) = - ## Set a BigInt to 0 - ## It's bit size is unchanged - zeroMem(a[0].unsafeAddr, a.numLimbs() * sizeof(Word)) - -func ccopy*(a: BigIntViewMut, b: BigIntViewAny, ctl: CTBool[Word]) = - ## Constant-time conditional copy - ## If ctl is true: b is copied into a - ## if ctl is false: b is not copied and a is untouched - ## Time and memory accesses are the same whether a copy occurs or not - checkMatchingBitlengths(a, b) - for i in 0 ..< a.numLimbs(): - a[i] = ctl.mux(b[i], a[i]) - -# The arithmetic primitives all accept a control input that indicates -# if it is a placebo operation. It stills performs the -# same memory accesses to be side-channel attack resistant. - -func cadd*(a: BigIntViewMut, b: BigIntViewAny, ctl: CTBool[Word]): CTBool[Word] = - ## Constant-time in-place conditional addition - ## The addition is only performed if ctl is "true" - ## The result carry is always computed. - ## - ## a and b MAY be the same buffer - ## a and b MUST have the same announced bitlength (i.e. `bits` static parameters) - checkMatchingBitlengths(a, b) - - for i in 0 ..< a.numLimbs(): - let new_a = a[i] + b[i] + Word(result) - result = new_a.isMsbSet() - a[i] = ctl.mux(new_a.mask(), a[i]) - -func csub*(a: BigIntViewMut, b: BigIntViewAny, ctl: CTBool[Word]): CTBool[Word] = - ## Constant-time in-place conditional substraction - ## The substraction is only performed if ctl is "true" - ## The result carry is always computed. - ## - ## a and b MAY be the same buffer - ## a and b MUST have the same announced bitlength (i.e. `bits` static parameters) - checkMatchingBitlengths(a, b) - - for i in 0 ..< a.numLimbs(): - let new_a = a[i] - b[i] - Word(result) - result = new_a.isMsbSet() - a[i] = ctl.mux(new_a.mask(), a[i]) - -func dec*(a: BigIntViewMut, w: Word): CTBool[Word] = - ## Decrement a big int by a small word - ## Returns the result carry - - a[0] -= w - result = a[0].isMsbSet() - a[0] = a[0].mask() - for i in 1 ..< a.numLimbs(): - a[i] -= Word(result) - result = a[i].isMsbSet() - a[i] = a[i].mask() - -func shiftRight*(a: BigIntViewMut, k: int) = - ## Shift right by k. - ## - ## k MUST be less than the base word size (2^31 or 2^63) - # We don't reuse shr for this in-place operation - # Do we need to return the shifted out part? - # - # Note: for speed, loading a[i] and a[i+1] - # instead of a[i-1] and a[i] - # is probably easier to parallelize for the compiler - # (antidependence WAR vs loop-carried dependence RAW) - checkWordShift(k) - - let len = a.numLimbs() - for i in 0 ..< len-1: - a[i] = (a[i] shr k) or mask(a[i+1] shl (WordBitSize - k)) - a[len-1] = a[len-1] shr k - -# ############################################################ -# -# BigInt Primitives Optimized for speed -# -# ############################################################ -# -# This section implements primitives that improve the speed -# of common use-cases at the expense of a slight increase in code-size. -# Where code size is a concern, the high-level API should use -# copy and/or the conditional operations. - -func add*(a: BigIntViewMut, b: BigIntViewAny): CTBool[Word] = - ## Constant-time in-place addition - ## Returns the carry - ## - ## a and b MAY be the same buffer - ## a and b MUST have the same announced bitlength (i.e. `bits` static parameters) - checkMatchingBitlengths(a, b) - - for i in 0 ..< a.numLimbs(): - a[i] = a[i] + b[i] + Word(result) - result = a[i].isMsbSet() - a[i] = a[i].mask() - -func sub*(a: BigIntViewMut, b: BigIntViewAny): CTBool[Word] = - ## Constant-time in-place substraction - ## Returns the borrow - ## - ## a and b MAY be the same buffer - ## a and b MUST have the same announced bitlength (i.e. `bits` static parameters) - checkMatchingBitlengths(a, b) - - for i in 0 ..< a.numLimbs(): - a[i] = a[i] - b[i] - Word(result) - result = a[i].isMsbSet() - a[i] = a[i].mask() - -func sum*(r: BigIntViewMut, a, b: distinct BigIntViewAny): CTBool[Word] = - ## Sum `a` and `b` into `r`. - ## `r` is initialized/overwritten - ## - ## Returns the carry - checkMatchingBitlengths(a, b) - - r.setBitLength(bitSizeof(a)) - - for i in 0 ..< a.numLimbs(): - r[i] = a[i] + b[i] + Word(result) - result = r[i].isMsbSet() - r[i] = r[i].mask() - -func diff*(r: BigIntViewMut, a, b: distinct BigIntViewAny): CTBool[Word] = - ## Substract `b` from `a` and store the result into `r`. - ## `r` is initialized/overwritten - ## - ## Returns the borrow - checkMatchingBitlengths(a, b) - - r.setBitLength(bitSizeof(a)) - - for i in 0 ..< a.numLimbs(): - r[i] = a[i] - b[i] - Word(result) - result = r[i].isMsbSet() - r[i] = r[i].mask() - -# ############################################################ -# -# Modular BigInt -# -# ############################################################ - -func shlAddMod(a: BigIntViewMut, c: Word, M: BigIntViewConst) = - ## Fused modular left-shift + add - ## Shift input `a` by a word and add `c` modulo `M` - ## - ## With a word W = 2^WordBitSize and a modulus M - ## Does a <- a * W + c (mod M) - ## - ## The modulus `M` MUST announced most-significant bit must be set. - checkValidModulus(M) - - let aLen = a.numLimbs() - let mBits = bitSizeof(M) - - if mBits <= WordBitSize: - # If M fits in a single limb - var q: Word - - # (hi, lo) = a * 2^63 + c - let hi = a[0] shr 1 # 64 - 63 = 1 - let lo = (a[0] shl WordBitSize) or c # Assumes most-significant bit in c is not set - unsafeDiv2n1n(q, a[0], hi, lo, M[0]) # (hi, lo) mod M - return - - else: - ## Multiple limbs - let hi = a[^1] # Save the high word to detect carries - let R = mBits and WordBitSize # R = mBits mod 64 - - var a0, a1, m0: Word - if R == 0: # If the number of mBits is a multiple of 64 - a0 = a[^1] # - moveMem(a[1].addr, a[0].addr, (aLen-1) * Word.sizeof) # we can just shift words - a[0] = c # and replace the first one by c - a1 = a[^1] - m0 = M[^1] - else: # Else: need to deal with partial word shifts at the edge. - a0 = mask((a[^1] shl (WordBitSize-R)) or (a[^2] shr R)) - moveMem(a[1].addr, a[0].addr, (aLen-1) * Word.sizeof) - a[0] = c - a1 = mask((a[^1] shl (WordBitSize-R)) or (a[^2] shr R)) - m0 = mask((M[^1] shl (WordBitSize-R)) or (M[^2] shr R)) - - # m0 has its high bit set. (a0, a1)/p0 fits in a limb. - # Get a quotient q, at most we will be 2 iterations off - # from the true quotient - - let - a_hi = a0 shr 1 # 64 - 63 = 1 - a_lo = (a0 shl WordBitSize) or a1 - var q, r: Word - unsafeDiv2n1n(q, r, a_hi, a_lo, m0) # Estimate quotient - q = mux( # If n_hi == divisor - a0 == m0, MaxWord, # Quotient == MaxWord (0b0111...1111) - mux( - q.isZero, Zero, # elif q == 0, true quotient = 0 - q - One # else instead of being of by 0, 1 or 2 - ) # we returning q-1 to be off by -1, 0 or 1 - ) - - # Now substract a*2^63 - q*p - var carry = Zero - var over_p = CtTrue # Track if quotient greater than the modulus - - for i in 0 ..< M.numLimbs(): - var qp_lo: Word - - block: # q*p - # q * p + carry (doubleword) carry from previous limb - unsafeFMA(carry, qp_lo, q, M[i], carry) - - block: # a*2^63 - q*p - a[i] -= qp_lo - carry += Word(a[i].isMsbSet) # Adjust if borrow - a[i] = a[i].mask() # Normalize to u63 - - over_p = mux( - a[i] == M[i], over_p, - a[i] > M[i] - ) - - # Fix quotient, the true quotient is either q-1, q or q+1 - # - # if carry < q or carry == q and over_p we must do "a -= p" - # if carry > hi (negative result) we must do "a += p" - - let neg = carry > hi - let tooBig = not neg and (over_p or (carry < hi)) - - discard a.cadd(M, ctl = neg) - discard a.csub(M, ctl = tooBig) - return - -func reduce*(r: BigIntViewMut, a: BigIntViewAny, M: BigIntViewConst) = - ## Reduce `a` modulo `M` and store the result in `r` - ## - ## The modulus `M` MUST announced most-significant bit must be set. - ## The result `r` buffer size MUST be at least the size of `M` buffer - ## - ## CT: Depends only on the bitlength of `a` and the modulus `M` - - # Note: for all cryptographic intents and purposes the modulus is known at compile-time - # but we don't want to inline it as it would increase codesize, better have Nim - # pass a pointer+length to a fixed session of the BSS. - checkValidModulus(M) - - let aBits = bitSizeof(a) - let mBits = bitSizeof(M) - let aLen = a.numLimbs() - - r.setBitLength(bitSizeof(M)) - - if aBits < mBits: - # if a uses less bits than the modulus, - # it is guaranteed < modulus. - # This relies on the precondition that the modulus uses all declared bits - copyMem(r[0].addr, a[0].unsafeAddr, aLen * sizeof(Word)) - for i in aLen ..< r.numLimbs(): - r[i] = Zero - else: - # a length i at least equal to the modulus. - # we can copy modulus.limbs-1 words - # and modular shift-left-add the rest - let mLen = M.numLimbs() - let aOffset = aLen - mLen - copyMem(r[0].addr, a[aOffset+1].unsafeAddr, (mLen-1) * sizeof(Word)) - r[^1] = Zero - # Now shift-left the copied words while adding the new word modulo M - for i in countdown(aOffset, 0): - r.shlAddMod(a[i], M) - -# ############################################################ -# -# Montgomery Arithmetic -# -# ############################################################ - -template wordMul(a, b: Word): Word = - mask(a * b) - -func montyMul*( - r: BigIntViewMut, a, b: distinct BigIntViewAny, - M: BigIntViewConst, negInvModWord: Word) = - ## Compute r <- a*b (mod M) in the Montgomery domain - ## `negInvModWord` = -1/M (mod Word). Our words are 2^31 or 2^63 - ## - ## This resets r to zero before processing. Use {.noInit.} - ## to avoid duplicating with Nim zero-init policy - ## The result `r` buffer size MUST be at least the size of `M` buffer - ## - ## - ## Assuming 63-bit wors, the magic constant should be: - ## - ## - µ ≡ -1/M[0] (mod 2^63) for a general multiplication - ## This can be precomputed with `negInvModWord` - ## - 1 for conversion from Montgomery to canonical representation - ## The library implements a faster `redc` primitive for that use-case - ## - R^2 (mod M) for conversion from canonical to Montgomery representation - ## - # i.e. c'R <- a'R b'R * R^-1 (mod M) in the natural domain - # as in the Montgomery domain all numbers are scaled by R - - checkValidModulus(M) - checkOddModulus(M) - checkMatchingBitlengths(a, M) - checkMatchingBitlengths(b, M) - - let nLen = M.numLimbs() - r.setBitLength(bitSizeof(M)) - setZero(r) - - var r_hi = Zero # represents the high word that is used in intermediate computation before reduction mod M - for i in 0 ..< nLen: - - let zi = (r[0] + wordMul(a[i], b[0])).wordMul(negInvModWord) - var carry: Word - # (carry, _) <- a[i] * b[0] + zi * M[0] + r[0] - unsafeFMA2_hi(carry, a[i], b[0], zi, M[0], r[0]) - - for j in 1 ..< nLen: - # (carry, r[j-1]) <- a[i] * b[j] + zi * M[j] + r[j] + carry - unsafeFMA2(carry, r[j-1], a[i], b[j], zi, M[j], r[j], carry) - - r_hi += carry - r[^1] = r_hi.mask() - r_hi = r_hi shr WordBitSize - - # If the extra word is not zero or if r-M does not borrow (i.e. r > M) - # Then substract M - discard r.csub(M, r_hi.isNonZero() or not r.csub(M, CtFalse)) - -func redc*(r: BigIntViewMut, a: BigIntViewAny, one, N: BigIntViewConst, negInvModWord: Word) {.inline.} = - ## Transform a bigint ``a`` from it's Montgomery N-residue representation (mod N) - ## to the regular natural representation (mod N) - ## - ## with W = N.numLimbs() - ## and R = (2^WordBitSize)^W - ## - ## Does "a * R^-1 (mod N)" - ## - ## This is called a Montgomery Reduction - ## The Montgomery Magic Constant is µ = -1/N mod N - ## is used internally and can be precomputed with negInvModWord(Curve) - # References: - # - https://eprint.iacr.org/2017/1057.pdf (Montgomery) - # page: Radix-r interleaved multiplication algorithm - # - https://en.wikipedia.org/wiki/Montgomery_modular_multiplication#Montgomery_arithmetic_on_multiprecision_(variable-radix)_integers - # - http://langevin.univ-tln.fr/cours/MLC/extra/montgomery.pdf - # Montgomery original paper - # - montyMul(r, a, one, N, negInvModWord) - -func montyResidue*( - r: BigIntViewMut, a: BigIntViewAny, - N, r2modN: BigIntViewConst, negInvModWord: Word) {.inline.} = - ## Transform a bigint ``a`` from it's natural representation (mod N) - ## to a the Montgomery n-residue representation - ## - ## Montgomery-Multiplication - based - ## - ## with W = N.numLimbs() - ## and R = (2^WordBitSize)^W - ## - ## Does "a * R (mod N)" - ## - ## `a`: The source BigInt in the natural representation. `a` in [0, N) range - ## `N`: The field modulus. N must be odd. - ## `r2modN`: 2^WordBitSize mod `N`. Can be precomputed with `r2mod` function - ## - ## Important: `r` is overwritten - ## The result `r` buffer size MUST be at least the size of `M` buffer - # Reference: https://eprint.iacr.org/2017/1057.pdf - montyMul(r, a, r2ModN, N, negInvModWord) - -func montySquare*( - r: BigIntViewMut, a: BigIntViewAny, - M: BigIntViewConst, negInvModWord: Word) {.inline.} = - ## Compute r <- a^2 (mod M) in the Montgomery domain - ## `negInvModWord` = -1/M (mod Word). Our words are 2^31 or 2^63 - montyMul(r, a, a, M, negInvModWord) - -# Montgomery Modular Exponentiation -# ------------------------------------------ -# We use fixed-window based exponentiation -# that is constant-time: i.e. the number of multiplications -# does not depend on the number of set bits in the exponents -# those are always done and conditionally copied. -# -# The exponent MUST NOT be private data (until audited otherwise) -# - Power attack on RSA, https://www.di.ens.fr/~fouque/pub/ches06.pdf -# - Flush-and-reload on Sliding window exponentiation: https://tutcris.tut.fi/portal/files/8966761/p1639_pereida_garcia.pdf -# - Sliding right into disaster, https://eprint.iacr.org/2017/627.pdf -# - Fixed window leak: https://www.scirp.org/pdf/JCC_2019102810331929.pdf -# - Constructing sliding-windows leak, https://easychair.org/publications/open/fBNC -# -# For pairing curves, this is the case since exponentiation is only -# used for inversion via the Little Fermat theorem. -# For RSA, some exponentiations uses private exponents. -# -# Note: -# - Implementation closely follows Thomas Pornin's BearSSL -# - Apache Milagro Crypto has an alternative implementation -# that is more straightforward however: -# - the exponent hamming weight is used as loop bounds -# - the base^k is stored at each index of a temp table of size k -# - the base^k to use is indexed by the hamming weight -# of the exponent, leaking this to cache attacks -# - in contrast BearSSL touches the whole table to -# hide the actual selection - -func getWindowLen(bufLen: int): uint = - ## Compute the maximum window size that fits in the scratchspace buffer - checkPowScratchSpaceLen(bufLen) - result = 5 - while (1 shl result) + 1 > bufLen: - dec result - -func montyPowPrologue( - a: BigIntViewMut, M, one: BigIntViewConst, - negInvModWord: Word, - scratchspace: openarray[BigIntViewMut] - ): tuple[window: uint, bigIntSize: int] {.inline.}= - # Due to the high number of parameters, - # forcing this inline actually reduces the code size - - result.window = scratchspace.len.getWindowLen() - result.bigIntSize = a.numLimbs() * sizeof(Word) + - offsetof(BigIntView, limbs) + - sizeof(BigIntView.bitLength) - - # Precompute window content, special case for window = 1 - # (i.e scratchspace has only space for 2 temporaries) - # The content scratchspace[2+k] is set at a^k - # with scratchspace[0] untouched - if result.window == 1: - copyMem(pointer scratchspace[1], pointer a, result.bigIntSize) - else: - scratchspace[1].setBitLength(bitSizeof(M)) - copyMem(pointer scratchspace[2], pointer a, result.bigIntSize) - for k in 2 ..< 1 shl result.window: - scratchspace[k+1].montyMul(scratchspace[k], a, M, negInvModWord) - - # Set a to one - copyMem(pointer a, pointer one, result.bigIntSize) - -func montyPowSquarings( - a: BigIntViewMut, - exponent: openarray[byte], - M: BigIntViewConst, - negInvModWord: Word, - tmp: BigIntViewMut, - window: uint, - bigIntSize: int, - acc, acc_len: var uint, - e: var int, - ): tuple[k, bits: uint] {.inline.}= - ## Squaring step of exponentiation by squaring - ## Get the next k bits in range [1, window) - ## Square k times - ## Returns the number of squarings done and the corresponding bits - ## - ## Updates iteration variables and accumulators - # Due to the high number of parameters, - # forcing this inline actually reduces the code size - - # Get the next bits - var k = window - if acc_len < window: - if e < exponent.len: - acc = (acc shl 8) or exponent[e].uint - inc e - acc_len += 8 - else: # Drained all exponent bits - k = acc_len - - let bits = (acc shr (acc_len - k)) and ((1'u32 shl k) - 1) - acc_len -= k - - # We have k bits and can do k squaring - for i in 0 ..< k: - tmp.montySquare(a, M, negInvModWord) - copyMem(pointer a, pointer tmp, bigIntSize) - - return (k, bits) - -func montyPow*( - a: BigIntViewMut, - exponent: openarray[byte], - M, one: BigIntViewConst, - negInvModWord: Word, - scratchspace: openarray[BigIntViewMut] - ) = - ## Modular exponentiation r = a^exponent mod M - ## in the Montgomery domain - ## - ## This uses fixed-window optimization if possible - ## - ## - On input ``a`` is the base, on ``output`` a = a^exponent (mod M) - ## ``a`` is in the Montgomery domain - ## - ``exponent`` is the exponent in big-endian canonical format (octet-string) - ## Use ``exportRawUint`` for conversion - ## - ``M`` is the modulus - ## - ``one`` is 1 (mod M) in montgomery representation - ## - ``negInvModWord`` is the montgomery magic constant "-1/M[0] mod 2^WordBitSize" - ## - ``scratchspace`` with k the window bitsize of size up to 5 - ## This is a buffer that can hold between 2^k + 1 big-ints - ## A window of of 1-bit (no window optimization) requires only 2 big-ints - ## - ## Note that the best window size require benchmarking and is a tradeoff between - ## - performance - ## - stack usage - ## - precomputation - ## - ## For example BLS12-381 window size of 5 is 30% faster than no window, - ## but windows of size 2, 3, 4 bring no performance benefit, only increased stack space. - ## A window of size 5 requires (2^5 + 1)*(381 + 7)/8 = 33 * 48 bytes = 1584 bytes - ## of scratchspace (on the stack). - - let (window, bigIntSize) = montyPowPrologue(a, M, one, negInvModWord, scratchspace) - - # We process bits with from most to least significant. - # At each loop iteration with have acc_len bits in acc. - # To maintain constant-time the number of iterations - # or the number of operations or memory accesses should be the same - # regardless of acc & acc_len - var - acc, acc_len: uint - e = 0 - while acc_len > 0 or e < exponent.len: - let (k, bits) = montyPowSquarings( - a, exponent, M, negInvModWord, - scratchspace[0], window, bigIntSize, - acc, acc_len, e - ) - - # Window lookup: we set scratchspace[1] to the lookup value. - # If the window length is 1, then it's already set. - if window > 1: - # otherwise we need a constant-time lookup - # in particular we need the same memory accesses, we can't - # just index the openarray with the bits to avoid cache attacks. - for i in 1 ..< 1 shl k: - let ctl = Word(i) == Word(bits) - scratchspace[1].ccopy(scratchspace[1+i], ctl) - - # Multiply with the looked-up value - # we keep the product only if the exponent bits are not all zero - scratchspace[0].montyMul(a, scratchspace[1], M, negInvModWord) - a.ccopy(scratchspace[0], Word(bits) != Zero) - -func montyPowUnsafeExponent*( - a: BigIntViewMut, - exponent: openarray[byte], - M, one: BigIntViewConst, - negInvModWord: Word, - scratchspace: openarray[BigIntViewMut] - ) = - ## Modular exponentiation r = a^exponent mod M - ## in the Montgomery domain - ## - ## Warning ⚠️ : - ## This is an optimization for public exponent - ## Otherwise bits of the exponent can be retrieved with: - ## - memory access analysis - ## - power analysis - ## - timing analysis - - # TODO: scratchspace[1] is unused when window > 1 - - let (window, bigIntSize) = montyPowPrologue( - a, M, one, negInvModWord, scratchspace) - - var - acc, acc_len: uint - e = 0 - while acc_len > 0 or e < exponent.len: - let (k, bits) = montyPowSquarings( - a, exponent, M, negInvModWord, - scratchspace[0], window, bigIntSize, - acc, acc_len, e - ) - - ## Warning ⚠️: Exposes the exponent bits - if bits != 0: - if window > 1: - scratchspace[0].montyMul(a, scratchspace[1+bits], M, negInvModWord) - else: - # scratchspace[1] holds the original `a` - scratchspace[0].montyMul(a, scratchspace[1], M, negInvModWord) - copyMem(pointer a, pointer scratchspace[0], bigIntSize) diff --git a/constantine/arithmetic/finite_fields.nim b/constantine/arithmetic/finite_fields.nim index 4049035..fe7ceda 100644 --- a/constantine/arithmetic/finite_fields.nim +++ b/constantine/arithmetic/finite_fields.nim @@ -25,9 +25,9 @@ # which requires a prime import - ../primitives/constant_time, + ../primitives, ../config/[common, curves], - ./bigints_checked + ./bigints, ./montgomery # type # `Fp`*[C: static Curve] = object @@ -57,16 +57,16 @@ debug: func fromBig*[C: static Curve](T: type Fp[C], src: BigInt): Fp[C] {.noInit.} = ## Convert a BigInt to its Montgomery form - result.mres.montyResidue(src, C.Mod.mres, C.getR2modP(), C.getNegInvModWord()) + result.mres.montyResidue(src, C.Mod.mres, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul()) func fromBig*[C: static Curve](dst: var Fp[C], src: BigInt) {.noInit.} = ## Convert a BigInt to its Montgomery form - dst.mres.montyResidue(src, C.Mod.mres, C.getR2modP(), C.getNegInvModWord()) + dst.mres.montyResidue(src, C.Mod.mres, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul()) func toBig*(src: Fp): auto {.noInit.} = ## Convert a finite-field element to a BigInt in natral representation var r {.noInit.}: typeof(src.mres) - r.redc(src.mres, Fp.C.Mod.mres, Fp.C.getNegInvModWord()) + r.redc(src.mres, Fp.C.Mod.mres, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontyMul()) return r # ############################################################ @@ -83,6 +83,12 @@ func toBig*(src: Fp): auto {.noInit.} = # - Golden Primes (φ^2 - φ - 1 with φ = 2^k for example Ed448-Goldilocks: 2^448 - 2^224 - 1) # exist and can be implemented with compile-time specialization. +# Note: for `+=`, double, sum +# not(a.mres < Fp.C.Mod.mres) is unnecessary if the prime has the form +# (2^64)^w - 1 (if using uint64 words). +# In practice I'm not aware of such prime being using in elliptic curves. +# 2^127 - 1 and 2^521 - 1 are used but 127 and 521 are not multiple of 32/64 + func `==`*(a, b: Fp): CTBool[Word] = ## Constant-time equality check a.mres == b.mres @@ -101,7 +107,7 @@ func setOne*(a: var Fp) = func `+=`*(a: var Fp, b: Fp) = ## In-place addition modulo p var overflowed = add(a.mres, b.mres) - overflowed = overflowed or not csub(a.mres, Fp.C.Mod.mres, CtFalse) # a >= P + overflowed = overflowed or not(a.mres < Fp.C.Mod.mres) discard csub(a.mres, Fp.C.Mod.mres, overflowed) func `-=`*(a: var Fp, b: Fp) = @@ -112,14 +118,14 @@ func `-=`*(a: var Fp, b: Fp) = func double*(a: var Fp) = ## Double ``a`` modulo p var overflowed = double(a.mres) - overflowed = overflowed or not csub(a.mres, Fp.C.Mod.mres, CtFalse) # a >= P + overflowed = overflowed or not(a.mres < Fp.C.Mod.mres) discard csub(a.mres, Fp.C.Mod.mres, overflowed) func sum*(r: var Fp, a, b: Fp) = ## Sum ``a`` and ``b`` into ``r`` module p ## r is initialized/overwritten var overflowed = r.mres.sum(a.mres, b.mres) - overflowed = overflowed or not csub(r.mres, Fp.C.Mod.mres, CtFalse) # r >= P + overflowed = overflowed or not(r.mres < Fp.C.Mod.mres) discard csub(r.mres, Fp.C.Mod.mres, overflowed) func diff*(r: var Fp, a, b: Fp) = @@ -132,17 +138,17 @@ func double*(r: var Fp, a: Fp) = ## Double ``a`` into ``r`` ## `r` is initialized/overwritten var overflowed = r.mres.double(a.mres) - overflowed = overflowed or not csub(r.mres, Fp.C.Mod.mres, CtFalse) # r >= P + overflowed = overflowed or not(r.mres < Fp.C.Mod.mres) discard csub(r.mres, Fp.C.Mod.mres, overflowed) func prod*(r: var Fp, a, b: Fp) = ## Store the product of ``a`` by ``b`` modulo p into ``r`` ## ``r`` is initialized / overwritten - r.mres.montyMul(a.mres, b.mres, Fp.C.Mod.mres, Fp.C.getNegInvModWord()) + r.mres.montyMul(a.mres, b.mres, Fp.C.Mod.mres, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontyMul()) func square*(r: var Fp, a: Fp) = ## Squaring modulo p - r.mres.montySquare(a.mres, Fp.C.Mod.mres, Fp.C.getNegInvModWord()) + r.mres.montySquare(a.mres, Fp.C.Mod.mres, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontyMul()) func neg*(r: var Fp, a: Fp) = ## Negate modulo p @@ -164,7 +170,8 @@ func pow*(a: var Fp, exponent: BigInt) = a.mres.montyPow( exponent, Fp.C.Mod.mres, Fp.C.getMontyOne(), - Fp.C.getNegInvModWord(), windowSize + Fp.C.getNegInvModWord(), windowSize, + Fp.C.canUseNoCarryMontyMul() ) func powUnsafeExponent*(a: var Fp, exponent: BigInt) = @@ -182,7 +189,8 @@ func powUnsafeExponent*(a: var Fp, exponent: BigInt) = a.mres.montyPowUnsafeExponent( exponent, Fp.C.Mod.mres, Fp.C.getMontyOne(), - Fp.C.getNegInvModWord(), windowSize + Fp.C.getNegInvModWord(), windowSize, + Fp.C.canUseNoCarryMontyMul() ) func inv*(a: var Fp) = @@ -193,7 +201,8 @@ func inv*(a: var Fp) = a.mres.montyPowUnsafeExponent( Fp.C.getInvModExponent(), Fp.C.Mod.mres, Fp.C.getMontyOne(), - Fp.C.getNegInvModWord(), windowSize + Fp.C.getNegInvModWord(), windowSize, + Fp.C.canUseNoCarryMontyMul() ) # ############################################################ diff --git a/constantine/arithmetic/limbs.nim b/constantine/arithmetic/limbs.nim new file mode 100644 index 0000000..ae8c7c2 --- /dev/null +++ b/constantine/arithmetic/limbs.nim @@ -0,0 +1,445 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy AndrĂ©-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at 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. + +import + ../config/common, + ../primitives + +# ############################################################ +# +# Limbs raw representation and operations +# +# ############################################################ +# +# This file holds the raw operations done on big ints +# The representation is optimized for: +# - constant-time (not leaking secret data via side-channel) +# - performance +# - generated code size, datatype size and stack usage +# in this order +# +# The "limbs" API limits code duplication +# due to generic/static monomorphization for bit-width +# that are represented with the same number of words. +# +# It also exposes at the number of words to the compiler +# to allow aggressive unrolling and inlining for example +# of multi-precision addition which is so small (2 instructions per word) +# that inlining it improves both performance and code-size +# even for 2 curves (secp256k1 and BN254) that could share the code. +# +# The limb-endianess is little-endian, less significant limb is at index 0. +# The word-endianness is native-endian. + +type Limbs*[N: static int] = array[N, Word] + ## Limbs-type + ## Should be distinct type to avoid builtins to use non-constant time + ## implementation, for example for comparison. + ## + ## but for unknown reason, it prevents semchecking `bits` + +# No exceptions allowed +{.push raises: [].} + +# ############################################################ +# +# Accessors +# +# ############################################################ +# +# Commented out since we don't use a distinct type + +# template `[]`[N](v: Limbs[N], idx: int): Word = +# (array[N, Word])(v)[idx] +# +# template `[]`[N](v: var Limbs[N], idx: int): var Word = +# (array[N, Word])(v)[idx] +# +# template `[]=`[N](v: Limbs[N], idx: int, val: Word) = +# (array[N, Word])(v)[idx] = val + +# ############################################################ +# +# Checks and debug/test only primitives +# +# ############################################################ + +# ############################################################ +# +# Limbs Primitives +# +# ############################################################ + +{.push inline.} +# The following primitives are small enough on regular limb sizes +# (BN254 and secp256k1 -> 4 limbs, BLS12-381 -> 6 limbs) +# that inline both decreases the code size and increases speed +# as we avoid the parmeter packing/unpacking ceremony at function entry/exit +# and unrolling overhead is minimal. + +func `==`*(a, b: Limbs): CTBool[Word] = + ## Returns true if 2 limbs are equal + ## Comparison is constant-time + var accum = Zero + for i in 0 ..< a.len: + accum = accum or (a[i] xor b[i]) + result = accum.isZero() + +func isZero*(a: Limbs): CTBool[Word] = + ## Returns true if ``a`` is equal to zero + var accum = Zero + for i in 0 ..< a.len: + accum = accum or a[i] + result = accum.isZero() + +func setZero*(a: var Limbs) = + ## Set ``a`` to 0 + zeroMem(a[0].addr, sizeof(a)) + +func setOne*(a: var Limbs) = + ## Set ``a`` to 1 + a[0] = Word(1) + when a.len > 1: + zeroMem(a[1].addr, (a.len - 1) * sizeof(Word)) + +func ccopy*(a: var Limbs, b: Limbs, ctl: CTBool[Word]) = + ## Constant-time conditional copy + ## If ctl is true: b is copied into a + ## if ctl is false: b is not copied and a is untouched + ## Time and memory accesses are the same whether a copy occurs or not + # TODO: on x86, we use inline assembly for CMOV + # the codegen is a bit inefficient as the condition `ctl` + # is tested for each limb. + for i in 0 ..< a.len: + ctl.ccopy(a[i], b[i]) + +func add*(a: var Limbs, b: Limbs): Carry = + ## Limbs addition + ## Returns the carry + result = Carry(0) + for i in 0 ..< a.len: + addC(result, a[i], a[i], b[i], result) + +func cadd*(a: var Limbs, b: Limbs, ctl: CTBool[Word]): Carry = + ## Limbs conditional addition + ## Returns the carry + ## + ## if ctl is true: a <- a + b + ## if ctl is false: a <- a + ## The carry is always computed whether ctl is true or false + ## + ## Time and memory accesses are the same whether a copy occurs or not + result = Carry(0) + var sum: Word + for i in 0 ..< a.len: + addC(result, sum, a[i], b[i], result) + ctl.ccopy(a[i], sum) + +func sum*(r: var Limbs, a, b: Limbs): Carry = + ## Sum `a` and `b` into `r` + ## `r` is initialized/overwritten + ## + ## Returns the carry + result = Carry(0) + for i in 0 ..< a.len: + addC(result, r[i], a[i], b[i], result) + +func sub*(a: var Limbs, b: Limbs): Borrow = + ## Limbs substraction + ## Returns the borrow + result = Borrow(0) + for i in 0 ..< a.len: + subB(result, a[i], a[i], b[i], result) + +func csub*(a: var Limbs, b: Limbs, ctl: CTBool[Word]): Borrow = + ## Limbs conditional substraction + ## Returns the borrow + ## + ## if ctl is true: a <- a - b + ## if ctl is false: a <- a + ## The borrow is always computed whether ctl is true or false + ## + ## Time and memory accesses are the same whether a copy occurs or not + result = Borrow(0) + var diff: Word + for i in 0 ..< a.len: + subB(result, diff, a[i], b[i], result) + ctl.ccopy(a[i], diff) + +func diff*(r: var Limbs, a, b: Limbs): Borrow = + ## Diff `a` and `b` into `r` + ## `r` is initialized/overwritten + ## + ## Returns the borrow + result = Borrow(0) + for i in 0 ..< a.len: + subB(result, r[i], a[i], b[i], result) + +func `<`*(a, b: Limbs): CTBool[Word] = + ## Returns true if a < b + ## Comparison is constant-time + var diff: Word + var borrow: Borrow + for i in 0 ..< a.len: + subB(borrow, diff, a[i], b[i], borrow) + + result = (CTBool[Word])(borrow) + +func `<=`*(a, b: Limbs): CTBool[Word] = + ## Returns true if a <= b + ## Comparison is constant-time + not(b < a) + +{.pop.} # inline + +# ############################################################ +# +# Modular BigInt +# +# ############################################################ +# +# To avoid code-size explosion due to monomorphization +# and given that reductions are not in hot path in Constantine +# we use type-erased procedures, instead of instantiating +# one per number of limbs combination + +# Type-erasure +# ------------------------------------------------------------ + +type + LimbsView = ptr UncheckedArray[Word] + ## Type-erased fixed-precision limbs + ## + ## This type mirrors the Limb type and is used + ## for some low-level computation API + ## This design + ## - avoids code bloat due to generic monomorphization + ## otherwise limbs routines would have an instantiation for + ## each number of words. + ## + ## Accesses should be done via BigIntViewConst / BigIntViewConst + ## to have the compiler check for mutability + + # "Indirection" to enforce pointer types deep immutability + LimbsViewConst = distinct LimbsView + ## Immutable view into the limbs of a BigInt + LimbsViewMut = distinct LimbsView + ## Mutable view into a BigInt + LimbsViewAny = LimbsViewConst or LimbsViewMut + +# Deep Mutability safety +# ------------------------------------------------------------ + +template view(a: Limbs): LimbsViewConst = + ## Returns a borrowed type-erased immutable view to a bigint + LimbsViewConst(cast[LimbsView](a.unsafeAddr)) + +template view(a: var Limbs): LimbsViewMut = + ## Returns a borrowed type-erased mutable view to a mutable bigint + LimbsViewMut(cast[LimbsView](a.addr)) + +template `[]`*(v: LimbsViewConst, limbIdx: int): Word = + LimbsView(v)[limbIdx] + +template `[]`*(v: LimbsViewMut, limbIdx: int): var Word = + LimbsView(v)[limbIdx] + +template `[]=`*(v: LimbsViewMut, limbIdx: int, val: Word) = + LimbsView(v)[limbIdx] = val + +# Type-erased add-sub +# ------------------------------------------------------------ + +func cadd(a: LimbsViewMut, b: LimbsViewAny, ctl: CTBool[Word], len: int): Carry = + ## Type-erased conditional addition + ## Returns the carry + ## + ## if ctl is true: a <- a + b + ## if ctl is false: a <- a + ## The carry is always computed whether ctl is true or false + ## + ## Time and memory accesses are the same whether a copy occurs or not + result = Carry(0) + var sum: Word + for i in 0 ..< len: + addC(result, sum, a[i], b[i], result) + ctl.ccopy(a[i], sum) + +func csub(a: LimbsViewMut, b: LimbsViewAny, ctl: CTBool[Word], len: int): Borrow = + ## Type-erased conditional addition + ## Returns the borrow + ## + ## if ctl is true: a <- a - b + ## if ctl is false: a <- a + ## The borrow is always computed whether ctl is true or false + ## + ## Time and memory accesses are the same whether a copy occurs or not + result = Borrow(0) + var diff: Word + for i in 0 ..< len: + subB(result, diff, a[i], b[i], result) + ctl.ccopy(a[i], diff) + +# Modular reduction +# ------------------------------------------------------------ + +func numWordsFromBits(bits: int): int {.inline.} = + const divShiftor = log2(uint32(WordBitWidth)) + result = (bits + WordBitWidth - 1) shr divShiftor + +func shlAddMod_estimate(a: LimbsViewMut, aLen: int, + c: Word, M: LimbsViewConst, mBits: int + ): tuple[neg, tooBig: CTBool[Word]] = + ## Estimate a <- a shl 2^w + c (mod M) + ## + ## with w the base word width, usually 32 on 32-bit platforms and 64 on 64-bit platforms + ## + ## Updates ``a`` and returns ``neg`` and ``tooBig`` + ## If ``neg``, the estimate in ``a`` is negative and ``M`` must be added to it. + ## If ``tooBig``, the estimate in ``a`` overflowed and ``M`` must be substracted from it. + + # Aliases + # ---------------------------------------------------------------------- + let MLen = numWordsFromBits(mBits) + + # Captures aLen and MLen + template `[]`(v: untyped, limbIdxFromEnd: BackwardsIndex): Word {.dirty.}= + v[`v Len` - limbIdxFromEnd.int] + + # ---------------------------------------------------------------------- + # Assuming 64-bit words + let hi = a[^1] # Save the high word to detect carries + let R = mBits and (WordBitWidth - 1) # R = mBits mod 64 + + var a0, a1, m0: Word + if R == 0: # If the number of mBits is a multiple of 64 + a0 = a[^1] # + moveMem(a[1].addr, a[0].addr, (aLen-1) * Word.sizeof) # we can just shift words + a[0] = c # and replace the first one by c + a1 = a[^1] + m0 = M[^1] + else: # Else: need to deal with partial word shifts at the edge. + a0 = (a[^1] shl (WordBitWidth-R)) or (a[^2] shr R) + moveMem(a[1].addr, a[0].addr, (aLen-1) * Word.sizeof) + a[0] = c + a1 = (a[^1] shl (WordBitWidth-R)) or (a[^2] shr R) + m0 = (M[^1] shl (WordBitWidth-R)) or (M[^2] shr R) + + # m0 has its high bit set. (a0, a1)/p0 fits in a limb. + # Get a quotient q, at most we will be 2 iterations off + # from the true quotient + var q, r: Word + unsafeDiv2n1n(q, r, a0, a1, m0) # Estimate quotient + q = mux( # If n_hi == divisor + a0 == m0, MaxWord, # Quotient == MaxWord (0b1111...1111) + mux( + q.isZero, Zero, # elif q == 0, true quotient = 0 + q - One # else instead of being of by 0, 1 or 2 + ) # we returning q-1 to be off by -1, 0 or 1 + ) + + # Now substract a*2^64 - q*p + var carry = Zero + var over_p = CtTrue # Track if quotient greater than the modulus + + for i in 0 ..< MLen: + var qp_lo: Word + + block: # q*p + # q * p + carry (doubleword) carry from previous limb + muladd1(carry, qp_lo, q, M[i], Word carry) + + block: # a*2^64 - q*p + var borrow: Borrow + subB(borrow, a[i], a[i], qp_lo, Borrow(0)) + carry += Word(borrow) # Adjust if borrow + + over_p = mux( + a[i] == M[i], over_p, + a[i] > M[i] + ) + + # Fix quotient, the true quotient is either q-1, q or q+1 + # + # if carry < q or carry == q and over_p we must do "a -= p" + # if carry > hi (negative result) we must do "a += p" + + result.neg = Word(carry) > hi + result.tooBig = not(result.neg) and (over_p or (Word(carry) < hi)) + +func shlAddMod(a: LimbsViewMut, aLen: int, + c: Word, M: LimbsViewConst, mBits: int) = + ## Fused modular left-shift + add + ## Shift input `a` by a word and add `c` modulo `M` + ## + ## With a word W = 2^WordBitSize and a modulus M + ## Does a <- a * W + c (mod M) + ## + ## The modulus `M` most-significant bit at `mBits` MUST be set. + if mBits <= WordBitWidth: + # If M fits in a single limb + + # We normalize M with R so that the MSB is set + # And normalize (a * 2^64 + c) by R as well to maintain the result + # This ensures that (a0, a1)/p0 fits in a limb. + let R = mBits and (WordBitWidth - 1) + + # (hi, lo) = a * 2^64 + c + let hi = (a[0] shl (WordBitWidth-R)) or (c shr R) + let lo = c shl (WordBitWidth-R) + let m0 = M[0] shl (WordBitWidth-R) + + var q, r: Word + unsafeDiv2n1n(q, r, hi, lo, m0) # (hi, lo) mod M + + a[0] = r shr (WordBitWidth-R) + + else: + ## Multiple limbs + let (neg, tooBig) = shlAddMod_estimate(a, aLen, c, M, mBits) + discard a.cadd(M, ctl = neg, aLen) + discard a.csub(M, ctl = tooBig, aLen) + +func reduce(r: LimbsViewMut, + a: LimbsViewAny, aBits: int, + M: LimbsViewConst, mBits: int) = + ## Reduce `a` modulo `M` and store the result in `r` + let aLen = numWordsFromBits(aBits) + let mLen = numWordsFromBits(mBits) + let rLen = mLen + + if aBits < mBits: + # if a uses less bits than the modulus, + # it is guaranteed < modulus. + # This relies on the precondition that the modulus uses all declared bits + copyMem(r[0].addr, a[0].unsafeAddr, aLen * sizeof(Word)) + for i in aLen ..< mLen: + r[i] = Zero + else: + # a length i at least equal to the modulus. + # we can copy modulus.limbs-1 words + # and modular shift-left-add the rest + let aOffset = aLen - mLen + copyMem(r[0].addr, a[aOffset+1].unsafeAddr, (mLen-1) * sizeof(Word)) + r[rLen - 1] = Zero + # Now shift-left the copied words while adding the new word modulo M + for i in countdown(aOffset, 0): + shlAddMod(r, rLen, a[i], M, mBits) + +func reduce*[aLen, mLen](r: var Limbs[mLen], + a: Limbs[aLen], aBits: static int, + M: Limbs[mLen], mBits: static int + ) {.inline.} = + ## Reduce `a` modulo `M` and store the result in `r` + ## + ## Warning âš : At the moment this is NOT constant-time + ## as it relies on hardware division. + # This is implemented via type-erased indirection to avoid + # a significant amount of code duplication if instantiated for + # varying bitwidth. + reduce(r.view(), a.view(), aBits, M.view(), mBits) diff --git a/constantine/arithmetic/montgomery.nim b/constantine/arithmetic/montgomery.nim new file mode 100644 index 0000000..4bf4100 --- /dev/null +++ b/constantine/arithmetic/montgomery.nim @@ -0,0 +1,473 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy AndrĂ©-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at 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. + +import + ../config/common, + ../primitives, + ./limbs, + macros + +# ############################################################ +# +# Multiprecision Montgomery Arithmetic +# +# ############################################################ +# +# Note: Montgomery multiplications and squarings are the biggest bottlenecks +# of an elliptic curve library, asymptotically 100% of the costly algorithms: +# - field exponentiation +# - field inversion +# - extension towers multiplication, squarings, inversion +# - elliptic curve point addition +# - elliptic curve point doubling +# - elliptic curve point multiplication +# - pairing Miller Loop +# - pairing final exponentiation +# are bottlenecked by Montgomery multiplications or squarings +# +# Unfortunately, the fastest implementation of Montgomery Multiplication +# on x86 is impossible without resorting to assembly (probably 15~30% faster) +# +# It requires implementing 2 parallel pipelines of carry-chains (via instruction-level parallelism) +# of MULX, ADCX and ADOX instructions, according to Intel paper: +# https://www.intel.cn/content/dam/www/public/us/en/documents/white-papers/ia-large-integer-arithmetic-paper.pdf +# and the code generation of MCL +# https://github.com/herumi/mcl +# +# A generic implementation would require implementing a mini-compiler as macro +# significantly sacrificing code readability, portability, auditability and maintainability. +# +# This would however save significant hardware or cloud resources. +# An example inline assembly compiler for add-with-carry is available in +# primitives/research/addcarry_subborrow_compiler.nim +# +# Instead we follow the optimized high-level implementation of Goff +# which skips a significant amount of additions for moduli +# that have their the most significant bit unset. + +# Loop unroller +# ------------------------------------------------------------ + +proc replaceNodes(ast: NimNode, what: NimNode, by: NimNode): NimNode = + # Replace "what" ident node by "by" + proc inspect(node: NimNode): NimNode = + case node.kind: + of {nnkIdent, nnkSym}: + if node.eqIdent(what): + return by + return node + of nnkEmpty: + return node + of nnkLiterals: + return node + else: + var rTree = node.kind.newTree() + for child in node: + rTree.add inspect(child) + return rTree + result = inspect(ast) + +macro staticFor(idx: untyped{nkIdent}, start, stopEx: static int, body: untyped): untyped = + result = newStmtList() + for i in start ..< stopEx: + result.add nnkBlockStmt.newTree( + ident("unrolledIter_" & $idx & $i), + body.replaceNodes(idx, newLit i) + ) + +# Implementation +# ------------------------------------------------------------ +# Note: the low-level implementations should not use static parameter +# the code generated is already big enough for curve with different +# limb sizes, we want to use the same codepath when limbs lenght are compatible. + +func montyMul_CIOS_nocarry_unrolled(r: var Limbs, a, b, M: Limbs, m0ninv: BaseType) = + ## Montgomery Multiplication using Coarse Grained Operand Scanning (CIOS) + ## and no-carry optimization. + ## This requires the most significant word of the Modulus + ## M[^1] < high(Word) shr 1 (i.e. less than 0b01111...1111) + ## https://hackmd.io/@zkteam/modular_multiplication + + # We want all the computation to be kept in registers + # hence we use a temporary `t`, hoping that the compiler does it. + var t: typeof(M) # zero-init + const N = t.len + staticFor i, 0, N: + # (A, t[0]) <- a[0] * b[i] + t[0] + # m <- (t[0] * m0ninv) mod 2^w + # (C, _) <- m * M[0] + t[0] + var A: Word + muladd1(A, t[0], a[0], b[i], t[0]) + let m = t[0] * Word(m0ninv) + var C, lo: Word + muladd1(C, lo, m, M[0], t[0]) + + staticFor j, 1, N: + # (A, t[j]) <- a[j] * b[i] + A + t[j] + # (C, t[j-1]) <- m * M[j] + C + t[j] + muladd2(A, t[j], a[j], b[i], A, t[j]) + muladd2(C, t[j-1], m, M[j], C, t[j]) + + t[N-1] = C + A + + discard t.csub(M, not(t < M)) + r = t + +func montyMul_CIOS(r: var Limbs, a, b, M: Limbs, m0ninv: BaseType) = + ## Montgomery Multiplication using Coarse Grained Operand Scanning (CIOS) + # - Analyzing and Comparing Montgomery Multiplication Algorithms + # Cetin Kaya Koc and Tolga Acar and Burton S. Kaliski Jr. + # http://pdfs.semanticscholar.org/5e39/41ff482ec3ee41dc53c3298f0be085c69483.pdf + # + # - Montgomery Arithmetic from a Software Perspective\ + # Joppe W. Bos and Peter L. Montgomery, 2017\ + # https://eprint.iacr.org/2017/1057 + + # We want all the computation to be kept in registers + # hence we use a temporary `t`, hoping that the compiler does it. + var t: typeof(M) # zero-init + const N = t.len + # Extra words to handle up to 2 carries t[N] and t[N+1] + var tN: Word + var tNp1: Carry + + staticFor i, 0, N: + var C = Zero + + # Multiplication + staticFor j, 0, N: + # (C, t[j]) <- a[j] * b[i] + t[j] + C + muladd2(C, t[j], a[j], b[i], t[j], C) + addC(tNp1, tN, tN, C, Carry(0)) + + # Reduction + # m <- (t[0] * m0ninv) mod 2^w + # (C, _) <- m * M[0] + t[0] + var lo: Word + C = Zero + let m = t[0] * Word(m0ninv) + muladd1(C, lo, m, M[0], t[0]) + staticFor j, 1, N: + # (C, t[j]) <- a[j] * b[i] + t[j] + C + muladd2(C, t[j-1], m, M[j], t[j], C) + + # (C,t[N-1]) <- t[N] + C + # (_, t[N]) <- t[N+1] + C + var carry: Carry + addC(carry, t[N-1], tN, C, Carry(0)) + addC(carry, tN, Word(tNp1), Zero, carry) + + # t[N+1] can only be non-zero in the intermediate computation + # since it is immediately reduce to t[N] at the end of each "i" iteration + # However if t[N] is non-zero we have t > M + discard t.csub(M, tN.isNonZero() or not(t < M)) # TODO: (t >= M) is unnecessary for prime in the form (2^64)^w + r = t + +# Exported API +# ------------------------------------------------------------ + +func montyMul*( + r: var Limbs, a, b, M: Limbs, + m0ninv: static BaseType, canUseNoCarryMontyMul: static bool) {.inline.} = + ## Compute r <- a*b (mod M) in the Montgomery domain + ## `m0ninv` = -1/M (mod Word). Our words are 2^32 or 2^64 + ## + ## This resets r to zero before processing. Use {.noInit.} + ## to avoid duplicating with Nim zero-init policy + ## The result `r` buffer size MUST be at least the size of `M` buffer + ## + ## + ## Assuming 64-bit words, the magic constant should be: + ## + ## - µ ≡ -1/M[0] (mod 2^64) for a general multiplication + ## This can be precomputed with `negInvModWord` + ## - 1 for conversion from Montgomery to canonical representation + ## The library implements a faster `redc` primitive for that use-case + ## - R^2 (mod M) for conversion from canonical to Montgomery representation + ## + # i.e. c'R <- a'R b'R * R^-1 (mod M) in the natural domain + # as in the Montgomery domain all numbers are scaled by R + + # Nim doesn't like static Word, so we pass static BaseType up to here + # Then we passe them as Word again for the final processing. + + # Many curve moduli are "Montgomery-friendly" which means that m0inv is 1 + # This saves N basic type multiplication and potentially many register mov + # as well as unless using "mulx" instruction, x86 "mul" requires very specific registers. + # Compilers should be able to constant-propagate, but this prevents reusing code + # for example between secp256k1 (friendly) and BN254 (non-friendly). + # Here, as "montyMul" is inlined at the call site, the compiler shouldn't constant fold, saving size. + # Inlining the implementation instead (and no-inline this "montyMul" proc) would allow constant propagation + # of Montgomery-friendly m0ninv if the compiler deems it interesting, + # or we use `when m0ninv == 1` and enforce the inlining. + when canUseNoCarryMontyMul: + montyMul_CIOS_nocarry_unrolled(r, a, b, M, m0ninv) + else: + montyMul_CIOS(r, a, b, M, m0ninv) + +func montySquare*(r: var Limbs, a, M: Limbs, + m0ninv: static BaseType, canUseNoCarryMontyMul: static bool) {.inline.} = + ## Compute r <- a^2 (mod M) in the Montgomery domain + ## `negInvModWord` = -1/M (mod Word). Our words are 2^31 or 2^63 + montyMul(r, a, a, M, m0ninv, canUseNoCarryMontyMul) + +func redc*(r: var Limbs, a, one, M: Limbs, + m0ninv: static BaseType, canUseNoCarryMontyMul: static bool) {.inline.} = + ## Transform a bigint ``a`` from it's Montgomery N-residue representation (mod N) + ## to the regular natural representation (mod N) + ## + ## with W = M.len + ## and R = (2^WordBitSize)^W + ## + ## Does "a * R^-1 (mod M)" + ## + ## This is called a Montgomery Reduction + ## The Montgomery Magic Constant is µ = -1/N mod M + ## is used internally and can be precomputed with negInvModWord(Curve) + # References: + # - https://eprint.iacr.org/2017/1057.pdf (Montgomery) + # page: Radix-r interleaved multiplication algorithm + # - https://en.wikipedia.org/wiki/Montgomery_modular_multiplication#Montgomery_arithmetic_on_multiprecision_(variable-radix)_integers + # - http://langevin.univ-tln.fr/cours/MLC/extra/montgomery.pdf + # Montgomery original paper + # + montyMul(r, a, one, M, m0ninv, canUseNoCarryMontyMul) + +func montyResidue*(r: var Limbs, a, M, r2modM: Limbs, + m0ninv: static BaseType, canUseNoCarryMontyMul: static bool) {.inline.} = + ## Transform a bigint ``a`` from it's natural representation (mod N) + ## to a the Montgomery n-residue representation + ## + ## Montgomery-Multiplication - based + ## + ## with W = M.len + ## and R = (2^WordBitSize)^W + ## + ## Does "a * R (mod M)" + ## + ## `a`: The source BigInt in the natural representation. `a` in [0, N) range + ## `M`: The field modulus. M must be odd. + ## `r2modM`: 2^WordBitSize mod `M`. Can be precomputed with `r2mod` function + ## + ## Important: `r` is overwritten + ## The result `r` buffer size MUST be at least the size of `M` buffer + # Reference: https://eprint.iacr.org/2017/1057.pdf + montyMul(r, a, r2ModM, M, m0ninv, canUseNoCarryMontyMul) + +# Montgomery Modular Exponentiation +# ------------------------------------------ +# We use fixed-window based exponentiation +# that is constant-time: i.e. the number of multiplications +# does not depend on the number of set bits in the exponents +# those are always done and conditionally copied. +# +# The exponent MUST NOT be private data (until audited otherwise) +# - Power attack on RSA, https://www.di.ens.fr/~fouque/pub/ches06.pdf +# - Flush-and-reload on Sliding window exponentiation: https://tutcris.tut.fi/portal/files/8966761/p1639_pereida_garcia.pdf +# - Sliding right into disaster, https://eprint.iacr.org/2017/627.pdf +# - Fixed window leak: https://www.scirp.org/pdf/JCC_2019102810331929.pdf +# - Constructing sliding-windows leak, https://easychair.org/publications/open/fBNC +# +# For pairing curves, this is the case since exponentiation is only +# used for inversion via the Little Fermat theorem. +# For RSA, some exponentiations uses private exponents. +# +# Note: +# - Implementation closely follows Thomas Pornin's BearSSL +# - Apache Milagro Crypto has an alternative implementation +# that is more straightforward however: +# - the exponent hamming weight is used as loop bounds +# - the base^k is stored at each index of a temp table of size k +# - the base^k to use is indexed by the hamming weight +# of the exponent, leaking this to cache attacks +# - in contrast BearSSL touches the whole table to +# hide the actual selection + +template checkPowScratchSpaceLen(len: int) = + ## Checks that there is a minimum of scratchspace to hold the temporaries + debug: + assert len >= 2, "Internal Error: the scratchspace for powmod should be equal or greater than 2" + +func getWindowLen(bufLen: int): uint = + ## Compute the maximum window size that fits in the scratchspace buffer + checkPowScratchSpaceLen(bufLen) + result = 5 + while (1 shl result) + 1 > bufLen: + dec result + +func montyPowPrologue( + a: var Limbs, M, one: Limbs, + m0ninv: static BaseType, + scratchspace: var openarray[Limbs], + canUseNoCarryMontyMul: static bool + ): uint = + ## Setup the scratchspace + ## Returns the fixed-window size for exponentiation with window optimization. + result = scratchspace.len.getWindowLen() + # Precompute window content, special case for window = 1 + # (i.e scratchspace has only space for 2 temporaries) + # The content scratchspace[2+k] is set at a^k + # with scratchspace[0] untouched + if result == 1: + scratchspace[1] = a + else: + scratchspace[2] = a + for k in 2 ..< 1 shl result: + scratchspace[k+1].montyMul(scratchspace[k], a, M, m0ninv, canUseNoCarryMontyMul) + + # Set a to one + a = one + +func montyPowSquarings( + a: var Limbs, + exponent: openarray[byte], + M: Limbs, + negInvModWord: static BaseType, + tmp: var Limbs, + window: uint, + acc, acc_len: var uint, + e: var int, + canUseNoCarryMontyMul: static bool + ): tuple[k, bits: uint] {.inline.}= + ## Squaring step of exponentiation by squaring + ## Get the next k bits in range [1, window) + ## Square k times + ## Returns the number of squarings done and the corresponding bits + ## + ## Updates iteration variables and accumulators + # Due to the high number of parameters, + # forcing this inline actually reduces the code size + + # Get the next bits + var k = window + if acc_len < window: + if e < exponent.len: + acc = (acc shl 8) or exponent[e].uint + inc e + acc_len += 8 + else: # Drained all exponent bits + k = acc_len + + let bits = (acc shr (acc_len - k)) and ((1'u32 shl k) - 1) + acc_len -= k + + # We have k bits and can do k squaring + for i in 0 ..< k: + tmp.montySquare(a, M, negInvModWord, canUseNoCarryMontyMul) + a = tmp + + return (k, bits) + +func montyPow*( + a: var Limbs, + exponent: openarray[byte], + M, one: Limbs, + negInvModWord: static BaseType, + scratchspace: var openarray[Limbs], + canUseNoCarryMontyMul: static bool + ) = + ## Modular exponentiation r = a^exponent mod M + ## in the Montgomery domain + ## + ## This uses fixed-window optimization if possible + ## + ## - On input ``a`` is the base, on ``output`` a = a^exponent (mod M) + ## ``a`` is in the Montgomery domain + ## - ``exponent`` is the exponent in big-endian canonical format (octet-string) + ## Use ``exportRawUint`` for conversion + ## - ``M`` is the modulus + ## - ``one`` is 1 (mod M) in montgomery representation + ## - ``negInvModWord`` is the montgomery magic constant "-1/M[0] mod 2^WordBitSize" + ## - ``scratchspace`` with k the window bitsize of size up to 5 + ## This is a buffer that can hold between 2^k + 1 big-ints + ## A window of of 1-bit (no window optimization) requires only 2 big-ints + ## + ## Note that the best window size require benchmarking and is a tradeoff between + ## - performance + ## - stack usage + ## - precomputation + ## + ## For example BLS12-381 window size of 5 is 30% faster than no window, + ## but windows of size 2, 3, 4 bring no performance benefit, only increased stack space. + ## A window of size 5 requires (2^5 + 1)*(381 + 7)/8 = 33 * 48 bytes = 1584 bytes + ## of scratchspace (on the stack). + + let window = montyPowPrologue(a, M, one, negInvModWord, scratchspace, canUseNoCarryMontyMul) + + # We process bits with from most to least significant. + # At each loop iteration with have acc_len bits in acc. + # To maintain constant-time the number of iterations + # or the number of operations or memory accesses should be the same + # regardless of acc & acc_len + var + acc, acc_len: uint + e = 0 + while acc_len > 0 or e < exponent.len: + let (k, bits) = montyPowSquarings( + a, exponent, M, negInvModWord, + scratchspace[0], window, + acc, acc_len, e, + canUseNoCarryMontyMul + ) + + # Window lookup: we set scratchspace[1] to the lookup value. + # If the window length is 1, then it's already set. + if window > 1: + # otherwise we need a constant-time lookup + # in particular we need the same memory accesses, we can't + # just index the openarray with the bits to avoid cache attacks. + for i in 1 ..< 1 shl k: + let ctl = Word(i) == Word(bits) + scratchspace[1].ccopy(scratchspace[1+i], ctl) + + # Multiply with the looked-up value + # we keep the product only if the exponent bits are not all zero + scratchspace[0].montyMul(a, scratchspace[1], M, negInvModWord, canUseNoCarryMontyMul) + a.ccopy(scratchspace[0], Word(bits).isNonZero()) + +func montyPowUnsafeExponent*( + a: var Limbs, + exponent: openarray[byte], + M, one: Limbs, + negInvModWord: static BaseType, + scratchspace: var openarray[Limbs], + canUseNoCarryMontyMul: static bool + ) = + ## Modular exponentiation r = a^exponent mod M + ## in the Montgomery domain + ## + ## Warning ⚠️ : + ## This is an optimization for public exponent + ## Otherwise bits of the exponent can be retrieved with: + ## - memory access analysis + ## - power analysis + ## - timing analysis + + # TODO: scratchspace[1] is unused when window > 1 + + let window = montyPowPrologue(a, M, one, negInvModWord, scratchspace, canUseNoCarryMontyMul) + + var + acc, acc_len: uint + e = 0 + while acc_len > 0 or e < exponent.len: + let (k, bits) = montyPowSquarings( + a, exponent, M, negInvModWord, + scratchspace[0], window, + acc, acc_len, e, + canUseNoCarryMontyMul + ) + + ## Warning ⚠️: Exposes the exponent bits + if bits != 0: + if window > 1: + scratchspace[0].montyMul(a, scratchspace[1+bits], M, negInvModWord, canUseNoCarryMontyMul) + else: + # scratchspace[1] holds the original `a` + scratchspace[0].montyMul(a, scratchspace[1], M, negInvModWord, canUseNoCarryMontyMul) + a = scratchspace[0] diff --git a/constantine/arithmetic/precomputed.nim b/constantine/arithmetic/precomputed.nim index 4af0a75..150db9d 100644 --- a/constantine/arithmetic/precomputed.nim +++ b/constantine/arithmetic/precomputed.nim @@ -7,7 +7,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - ./bigints_checked, + ./bigints, ../primitives/constant_time, ../config/common, ../io/io_bigints @@ -22,37 +22,77 @@ import # ############################################################ # # Those primitives are intended to be compile-time only -# They are generic over the bitsize: enabling them at runtime -# would create a copy for each bitsize used (monomorphization) -# leading to code-bloat. -# Thos are NOT compile-time, using CTBool seems to confuse the VM +# Those are NOT tagged compile-time, using CTBool seems to confuse the VM # We don't use distinct types here, they confuse the VM -# Similarly, isMsbSet causes trouble with distinct type in the VM +# Similarly, using addC / subB confuses the VM -func isMsbSet(x: BaseType): bool = - const msb_pos = BaseType.sizeof * 8 - 1 - bool(x shr msb_pos) +# As we choose to use the full 32/64 bits of the integers and there is no carry flag +# in the compile-time VM we need a portable (and slow) "adc" and "sbb". +# Hopefully compilation time stays decent. + +const + HalfWidth = WordBitWidth shr 1 + HalfBase = (BaseType(1) shl HalfWidth) + HalfMask = HalfBase - 1 + +func split(n: BaseType): tuple[hi, lo: BaseType] = + result.hi = n shr HalfWidth + result.lo = n and HalfMask + +func merge(hi, lo: BaseType): BaseType = + (hi shl HalfWidth) or lo + +func addC(cOut, sum: var BaseType, a, b, cIn: BaseType) = + # Add with carry, fallback for the Compile-Time VM + # (CarryOut, Sum) <- a + b + CarryIn + let (aHi, aLo) = split(a) + let (bHi, bLo) = split(b) + let tLo = aLo + bLo + cIn + let (cLo, rLo) = split(tLo) + let tHi = aHi + bHi + cLo + let (cHi, rHi) = split(tHi) + cOut = cHi + sum = merge(rHi, rLo) + +func subB(bOut, diff: var BaseType, a, b, bIn: BaseType) = + # Substract with borrow, fallback for the Compile-Time VM + # (BorrowOut, Sum) <- a - b - BorrowIn + let (aHi, aLo) = split(a) + let (bHi, bLo) = split(b) + let tLo = HalfBase + aLo - bLo - bIn + let (noBorrowLo, rLo) = split(tLo) + let tHi = HalfBase + aHi - bHi - BaseType(noBorrowLo == 0) + let (noBorrowHi, rHi) = split(tHi) + bOut = BaseType(noBorrowHi == 0) + diff = merge(rHi, rLo) func dbl(a: var BigInt): bool = ## In-place multiprecision double ## a -> 2a + var carry, sum: BaseType for i in 0 ..< a.limbs.len: - var z = BaseType(a.limbs[i]) * 2 + BaseType(result) - result = z.isMsbSet() - a.limbs[i] = mask(Word(z)) + let ai = BaseType(a.limbs[i]) + addC(carry, sum, ai, ai, carry) + a.limbs[i] = Word(sum) -func sub(a: var BigInt, b: BigInt, ctl: bool): bool = + result = bool(carry) + +func csub(a: var BigInt, b: BigInt, ctl: bool): bool = ## In-place optional substraction ## ## It is NOT constant-time and is intended ## only for compile-time precomputation ## of non-secret data. + var borrow, diff: BaseType for i in 0 ..< a.limbs.len: - let new_a = BaseType(a.limbs[i]) - BaseType(b.limbs[i]) - BaseType(result) - result = new_a.isMsbSet() - a.limbs[i] = if ctl: new_a.Word.mask() - else: a.limbs[i] + let ai = BaseType(a.limbs[i]) + let bi = BaseType(b.limbs[i]) + subB(borrow, diff, ai, bi, borrow) + if ctl: + a.limbs[i] = Word(diff) + + result = bool(borrow) func doubleMod(a: var BigInt, M: BigInt) = ## In-place modular double @@ -62,8 +102,8 @@ func doubleMod(a: var BigInt, M: BigInt) = ## only for compile-time precomputation ## of non-secret data. var ctl = dbl(a) - ctl = ctl or not sub(a, M, false) - discard sub(a, M, ctl) + ctl = ctl or not a.csub(M, false) + discard csub(a, M, ctl) # ############################################################ # @@ -75,11 +115,19 @@ func checkOddModulus(M: BigInt) = doAssert bool(BaseType(M.limbs[0]) and 1), "Internal Error: the modulus must be odd to use the Montgomery representation." func checkValidModulus(M: BigInt) = - const expectedMsb = M.bits-1 - WordBitSize * (M.limbs.len - 1) + const expectedMsb = M.bits-1 - WordBitWidth * (M.limbs.len - 1) let msb = log2(BaseType(M.limbs[^1])) doAssert msb == expectedMsb, "Internal Error: the modulus must use all declared bits and only those" +func useNoCarryMontyMul*(M: BigInt): bool = + ## Returns if the modulus is compatible + ## with the no-carry Montgomery Multiplication + ## from https://hackmd.io/@zkteam/modular_multiplication + # Indirection needed because static object are buggy + # https://github.com/nim-lang/Nim/issues/9679 + BaseType(M.limbs[^1]) < high(BaseType) shr 1 + func negInvModWord*(M: BigInt): BaseType = ## Returns the Montgomery domain magic constant for the input modulus: ## @@ -88,9 +136,9 @@ func negInvModWord*(M: BigInt): BaseType = ## M[0] is the least significant limb of M ## M must be odd and greater than 2. ## - ## Assuming 63-bit words: + ## Assuming 64-bit words: ## - ## µ ≡ -1/M[0] (mod 2^63) + ## µ ≡ -1/M[0] (mod 2^64) # We use BaseType for return value because static distinct type # confuses Nim semchecks [UPSTREAM BUG] @@ -108,17 +156,17 @@ func negInvModWord*(M: BigInt): BaseType = # - http://marc-b-reynolds.github.io/math/2017/09/18/ModInverse.html # For Montgomery magic number, we are in a special case - # where a = M and m = 2^WordBitsize. + # where a = M and m = 2^WordBitWidth. # For a and m to be coprimes, a must be odd. # We have the following relation # ax ≡ 1 (mod 2^k) <=> ax(2 - ax) ≡ 1 (mod 2^(2k)) # # To get -1/M0 mod LimbSize - # we can either negate the resulting x of `ax(2 - ax) ≡ 1 (mod 2^(2k))` - # or do ax(2 + ax) ≡ 1 (mod 2^(2k)) + # we can negate the result x of `ax(2 - ax) ≡ 1 (mod 2^(2k))` + # or if k is odd: do ax(2 + ax) ≡ 1 (mod 2^(2k)) # - # To get the the modular inverse of 2^k' with arbitrary k' (like k=63 in our case) + # To get the the modular inverse of 2^k' with arbitrary k' # we can do modInv(a, 2^64) mod 2^63 as mentionned in Koc paper. checkOddModulus(M) @@ -126,21 +174,21 @@ func negInvModWord*(M: BigInt): BaseType = let M0 = BaseType(M.limbs[0]) - k = log2(uint32(WordPhysBitSize)) + k = log2(WordBitWidth.uint32) result = M0 # Start from an inverse of M0 modulo 2, M0 is odd and it's own inverse for _ in 0 ..< k: # at each iteration we get the inverse mod(2^2k) - result *= 2 + M0 * result # x' = x(2 + ax) (`+` to avoid negating at the end) + result *= 2 - M0 * result # x' = x(2 - ax) - # Our actual word size is 2^63 not 2^64 - result = result and BaseType(MaxWord) + # negate to obtain the negative inverse + result = not(result) + 1 func r_powmod(n: static int, M: BigInt): BigInt = ## Returns the Montgomery domain magic constant for the input modulus: ## - ## R ≡ R (mod M) with R = (2^WordBitSize)^numWords + ## R ≡ R (mod M) with R = (2^WordBitWidth)^numWords ## or - ## R² ≡ R² (mod M) with R = (2^WordBitSize)^numWords + ## R² ≡ R² (mod M) with R = (2^WordBitWidth)^numWords ## ## Assuming a field modulus of size 256-bit with 63-bit words, we require 5 words ## R² ≡ ((2^63)^5)^2 (mod M) = 2^630 (mod M) @@ -161,22 +209,20 @@ func r_powmod(n: static int, M: BigInt): BigInt = checkOddModulus(M) checkValidModulus(M) - result.setInternalBitLength() - const w = M.limbs.len - msb = M.bits-1 - WordBitSize * (w - 1) - start = (w-1)*WordBitSize + msb - stop = n*WordBitSize*w + msb = M.bits-1 - WordBitWidth * (w - 1) + start = (w-1)*WordBitWidth + msb + stop = n*WordBitWidth*w - result.limbs[^1] = Word(1 shl msb) # C0 = 2^(wn-1), the power of 2 immediatly less than the modulus + result.limbs[^1] = Word(BaseType(1) shl msb) # C0 = 2^(wn-1), the power of 2 immediatly less than the modulus for _ in start ..< stop: result.doubleMod(M) func r2mod*(M: BigInt): BigInt = ## Returns the Montgomery domain magic constant for the input modulus: ## - ## R² ≡ R² (mod M) with R = (2^WordBitSize)^numWords + ## R² ≡ R² (mod M) with R = (2^WordBitWidth)^numWords ## ## Assuming a field modulus of size 256-bit with 63-bit words, we require 5 words ## R² ≡ ((2^63)^5)^2 (mod M) = 2^630 (mod M) @@ -195,6 +241,6 @@ func primeMinus2_BE*[bits: static int]( ## For use to precompute modular inverse exponent. var tmp = P - discard tmp.sub(BigInt[bits].fromRawUint([byte 2], bigEndian), true) + discard tmp.csub(BigInt[bits].fromRawUint([byte 2], bigEndian), true) result.exportRawUint(tmp, bigEndian) diff --git a/constantine/config/common.nim b/constantine/config/common.nim index 66b0372..d726e28 100644 --- a/constantine/config/common.nim +++ b/constantine/config/common.nim @@ -12,9 +12,9 @@ # # ############################################################ -import ../primitives/constant_time +import ../primitives -when sizeof(int) == 8: +when sizeof(int) == 8 and not defined(Constantine32): type BaseType* = uint64 ## Physical BigInt for conversion in "normal integers" @@ -29,23 +29,15 @@ type ## A logical BigInt word is of size physical MachineWord-1 const - ExcessBits = 1 - WordPhysBitSize* = sizeof(Word) * 8 - WordBitSize* = WordPhysBitSize - ExcessBits + WordBitWidth* = sizeof(Word) * 8 + ## Logical word size CtTrue* = ctrue(Word) CtFalse* = cfalse(Word) Zero* = Word(0) One* = Word(1) - MaxWord* = (not Zero) shr (WordPhysBitSize - WordBitSize) - ## This represents 0x7F_FF_FF_FF__FF_FF_FF_FF - ## also 0b0111...1111 - ## This biggest representable number in our limbs. - ## i.e. The most significant bit is never set at the end of each function - -template mask*(w: Word): Word = - w and MaxWord + MaxWord* = Word(high(BaseType)) # ############################################################ # diff --git a/constantine/config/curves.nim b/constantine/config/curves.nim index 6a1424e..a627454 100644 --- a/constantine/config/curves.nim +++ b/constantine/config/curves.nim @@ -11,7 +11,7 @@ import macros, # Internal ./curves_parser, ./common, - ../arithmetic/[precomputed, bigints_checked] + ../arithmetic/[precomputed, bigints] # ############################################################ @@ -109,6 +109,17 @@ macro genMontyMagics(T: typed): untyped = let E = T.getImpl[2] for i in 1 ..< E.len: let curve = E[i] + # const MyCurve_CanUseNoCarryMontyMul = useNoCarryMontyMul(MyCurve_Modulus) + result.add newConstStmt( + ident($curve & "_CanUseNoCarryMontyMul"), newCall( + bindSym"useNoCarryMontyMul", + nnkDotExpr.newTree( + bindSym($curve & "_Modulus"), + ident"mres" + ) + ) + ) + # const MyCurve_R2modP = r2mod(MyCurve_Modulus) result.add newConstStmt( ident($curve & "_R2modP"), newCall( @@ -154,6 +165,11 @@ macro genMontyMagics(T: typed): untyped = genMontyMagics(Curve) +macro canUseNoCarryMontyMul*(C: static Curve): untyped = + ## Returns true if the Modulus is compatible with a fast + ## Montgomery multiplication that avoids many carries + result = bindSym($C & "_CanUseNoCarryMontyMul") + macro getR2modP*(C: static Curve): untyped = ## Get the Montgomery "R^2 mod P" constant associated to a curve field modulus result = bindSym($C & "_R2modP") @@ -192,7 +208,7 @@ macro debugConsts(): untyped = echo "Curve ", `curveName`,':' echo " Field Modulus: ", `modulus` echo " Montgomery R² (mod P): ", `r2modp` - echo " Montgomery -1/P[0] (mod 2^", WordBitSize, "): ", `negInvModWord` + echo " Montgomery -1/P[0] (mod 2^", WordBitWidth, "): ", `negInvModWord` result.add quote do: echo "----------------------------------------------------------------------------" diff --git a/constantine/config/curves_parser.nim b/constantine/config/curves_parser.nim index c0bbcb7..6254ab4 100644 --- a/constantine/config/curves_parser.nim +++ b/constantine/config/curves_parser.nim @@ -10,7 +10,7 @@ import # Standard library macros, # Internal - ../io/io_bigints, ../arithmetic/bigints_checked + ../io/io_bigints, ../arithmetic/bigints # Macro to parse declarative curves configuration. diff --git a/constantine/io/io_bigints.nim b/constantine/io/io_bigints.nim index dc85fd7..ecdefd5 100644 --- a/constantine/io/io_bigints.nim +++ b/constantine/io/io_bigints.nim @@ -12,7 +12,7 @@ import ../primitives/constant_time, - ../arithmetic/bigints_checked, + ../arithmetic/bigints, ../config/common # ############################################################ @@ -21,6 +21,10 @@ import # # ############################################################ +# Note: the parsing/serialization routines were initially developed +# with an internal representation that used 31 bits out of a uint32 +# or 63-bits out of an uint64 + # TODO: tag/remove exceptions raised. func fromRawUintLE( @@ -49,10 +53,10 @@ func fromRawUintLE( acc_len += 8 # We count bit by bit # if full, dump - if acc_len >= WordBitSize: - dst.limbs[dst_idx] = mask(acc) + if acc_len >= WordBitWidth: + dst.limbs[dst_idx] = acc inc dst_idx - acc_len -= WordBitSize + acc_len -= WordBitWidth acc = src_byte shr (8 - acc_len) if dst_idx < dst.limbs.len: @@ -86,10 +90,10 @@ func fromRawUintBE( acc_len += 8 # We count bit by bit # if full, dump - if acc_len >= WordBitSize: - dst.limbs[dst_idx] = mask(acc) + if acc_len >= WordBitWidth: + dst.limbs[dst_idx] = acc inc dst_idx - acc_len -= WordBitSize + acc_len -= WordBitWidth acc = src_byte shr (8 - acc_len) if dst_idx < dst.limbs.len: @@ -113,7 +117,6 @@ func fromRawUint*( dst.fromRawUintLE(src) else: dst.fromRawUintBE(src) - dst.setInternalBitLength() func fromRawUint*( T: type BigInt, @@ -187,14 +190,17 @@ func exportRawUintLE( inc src_idx if acc_len == 0: - # Edge case, we need to refill the buffer to output 64-bit - # as we can only read 63-bit per word + # We need to refill the buffer to output 64-bit acc = w - acc_len = WordBitSize + acc_len = WordBitWidth else: - let lo = (w shl acc_len) or acc - dec acc_len - acc = w shr (WordBitSize - acc_len) + when WordBitWidth == sizeof(Word) * 8: + let lo = acc + acc = w + else: # If using 63-bit (or less) out of uint64 + let lo = (w shl acc_len) or acc + dec acc_len + acc = w shr (WordBitWidth - acc_len) if tail >= sizeof(Word): # Unrolled copy @@ -237,14 +243,17 @@ func exportRawUintBE( inc src_idx if acc_len == 0: - # Edge case, we need to refill the buffer to output 64-bit - # as we can only read 63-bit per word + # We need to refill the buffer to output 64-bit acc = w - acc_len = WordBitSize + acc_len = WordBitWidth else: - let lo = (w shl acc_len) or acc - dec acc_len - acc = w shr (WordBitSize - acc_len) + when WordBitWidth == sizeof(Word) * 8: + let lo = acc + acc = w + else: # If using 63-bit (or less) out of uint64 + let lo = (w shl acc_len) or acc + dec acc_len + acc = w shr (WordBitWidth - acc_len) if tail >= sizeof(Word): # Unrolled copy diff --git a/constantine/io/io_fields.nim b/constantine/io/io_fields.nim index b9973fe..dbc1c43 100644 --- a/constantine/io/io_fields.nim +++ b/constantine/io/io_fields.nim @@ -9,7 +9,7 @@ import ./io_bigints, ../config/curves, - ../arithmetic/[bigints_checked, finite_fields] + ../arithmetic/[bigints, finite_fields] # No exceptions allowed {.push raises: [].} diff --git a/constantine/primitives.nim b/constantine/primitives.nim new file mode 100644 index 0000000..d311f84 --- /dev/null +++ b/constantine/primitives.nim @@ -0,0 +1,21 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy AndrĂ©-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at 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. + +import + primitives/constant_time_types, + primitives/constant_time, + primitives/multiplexers, + primitives/addcarry_subborrow, + primitives/extended_precision + +export + constant_time_types, + constant_time, + multiplexers, + addcarry_subborrow, + extended_precision diff --git a/constantine/primitives/README.md b/constantine/primitives/README.md index c4374f8..921ff6b 100644 --- a/constantine/primitives/README.md +++ b/constantine/primitives/README.md @@ -6,3 +6,86 @@ This folder holds: to have the compiler enforce proper usage - extended precision multiplication and division primitives - assembly primitives +- intrinsics + +## Security + +âš : **Hardware assumptions** + + Constantine assumes that multiplication is implemented + constant-time in hardware. + + If this is not the case, + you SHOULD **strongly reconsider** your hardware choice or + reimplement multiplication with constant-time guarantees + (at the cost of speed and code-size) + +âš : Currently division and modulo operations are `unsafe` + and uses hardware division. + No known CPU implements division in constant-time. + A constant-time alternative will be provided. + +While extremely slow, division and modulo are only used +on random or user inputs to constrain them to the prime field +of the elliptic curves. +Constantine internals are built to avoid costly constant-time divisions. + +## Performance and code size + +It is recommended to prefer Clang, MSVC or ICC over GCC if possible. +GCC code is significantly slower and bigger for multiprecision arithmetic +even when using dedicated intrinsics. + +See https://gcc.godbolt.org/z/2h768y +```C +#include +#include + +void add256(uint64_t a[4], uint64_t b[4]){ + uint8_t carry = 0; + for (int i = 0; i < 4; ++i) + carry = _addcarry_u64(carry, a[i], b[i], &a[i]); +} +``` + +GCC +```asm +add256: + movq (%rsi), %rax + addq (%rdi), %rax + setc %dl + movq %rax, (%rdi) + movq 8(%rdi), %rax + addb $-1, %dl + adcq 8(%rsi), %rax + setc %dl + movq %rax, 8(%rdi) + movq 16(%rdi), %rax + addb $-1, %dl + adcq 16(%rsi), %rax + setc %dl + movq %rax, 16(%rdi) + movq 24(%rsi), %rax + addb $-1, %dl + adcq %rax, 24(%rdi) + ret +``` + +Clang +```asm +add256: + movq (%rsi), %rax + addq %rax, (%rdi) + movq 8(%rsi), %rax + adcq %rax, 8(%rdi) + movq 16(%rsi), %rax + adcq %rax, 16(%rdi) + movq 24(%rsi), %rax + adcq %rax, 24(%rdi) + retq +``` + +### Inline assembly + +Using inline assembly will sacrifice code readability, portability, auditability and maintainability. +That said the performance might be worth it. diff --git a/constantine/primitives/addcarry_subborrow.nim b/constantine/primitives/addcarry_subborrow.nim new file mode 100644 index 0000000..03bec55 --- /dev/null +++ b/constantine/primitives/addcarry_subborrow.nim @@ -0,0 +1,168 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy AndrĂ©-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at 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. + +import ./constant_time_types + +# ############################################################ +# +# Add-with-carry and Sub-with-borrow +# +# ############################################################ +# +# This file implements add-with-carry and sub-with-borrow +# +# It is currently (Mar 2020) impossible to have the compiler +# generate optimal code in a generic way. +# +# On x86, addcarry_u64 intrinsic will generate optimal code +# except for GCC. +# +# On other CPU architectures inline assembly might be desirable. +# A compiler proof-of-concept is available in the "research" folder. +# +# See https://gcc.godbolt.org/z/2h768y +# ```C +# #include +# #include +# +# void add256(uint64_t a[4], uint64_t b[4]){ +# uint8_t carry = 0; +# for (int i = 0; i < 4; ++i) +# carry = _addcarry_u64(carry, a[i], b[i], &a[i]); +# } +# ``` +# +# GCC +# ```asm +# add256: +# movq (%rsi), %rax +# addq (%rdi), %rax +# setc %dl +# movq %rax, (%rdi) +# movq 8(%rdi), %rax +# addb $-1, %dl +# adcq 8(%rsi), %rax +# setc %dl +# movq %rax, 8(%rdi) +# movq 16(%rdi), %rax +# addb $-1, %dl +# adcq 16(%rsi), %rax +# setc %dl +# movq %rax, 16(%rdi) +# movq 24(%rsi), %rax +# addb $-1, %dl +# adcq %rax, 24(%rdi) +# ret +# ``` +# +# Clang +# ```asm +# add256: +# movq (%rsi), %rax +# addq %rax, (%rdi) +# movq 8(%rsi), %rax +# adcq %rax, 8(%rdi) +# movq 16(%rsi), %rax +# adcq %rax, 16(%rdi) +# movq 24(%rsi), %rax +# adcq %rax, 24(%rdi) +# retq +# ``` + +# ############################################################ +# +# Intrinsics +# +# ############################################################ + +# Note: GCC before 2017 had incorrect codegen in some cases: +# - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81300 + +when X86: + when defined(windows): + {.pragma: intrinsics, header:"", nodecl.} + else: + {.pragma: intrinsics, header:"", nodecl.} + + func addcarry_u32(carryIn: Carry, a, b: Ct[uint32], sum: var Ct[uint32]): Carry {.importc: "_addcarry_u32", intrinsics.} + func subborrow_u32(borrowIn: Borrow, a, b: Ct[uint32], diff: var Ct[uint32]): Borrow {.importc: "_subborrow_u32", intrinsics.} + + func addcarry_u64(carryIn: Carry, a, b: Ct[uint64], sum: var Ct[uint64]): Carry {.importc: "_addcarry_u64", intrinsics.} + func subborrow_u64(borrowIn: Borrow, a, b: Ct[uint64], diff: var Ct[uint64]): Borrow {.importc: "_subborrow_u64", intrinsics.} + +# ############################################################ +# +# Public +# +# ############################################################ + +func addC*(cOut: var Carry, sum: var Ct[uint32], a, b: Ct[uint32], cIn: Carry) {.inline.} = + ## Addition with carry + ## (CarryOut, Sum) <- a + b + CarryIn + when X86: + cOut = addcarry_u32(cIn, a, b, sum) + else: + let dblPrec = uint64(cIn) + uint64(a) + uint64(b) + sum = (Ct[uint32])(dblPrec) + cOut = Carry(dblPrec shr 32) + +func subB*(bOut: var Borrow, diff: var Ct[uint32], a, b: Ct[uint32], bIn: Borrow) {.inline.} = + ## Substraction with borrow + ## (BorrowOut, Diff) <- a - b - borrowIn + when X86: + bOut = subborrow_u32(bIn, a, b, diff) + else: + let dblPrec = uint64(a) - uint64(b) - uint64(bIn) + diff = (Ct[uint32])(dblPrec) + # On borrow the high word will be 0b1111...1111 and needs to be masked + bOut = Borrow((dblPrec shr 32) and 1) + +func addC*(cOut: var Carry, sum: var Ct[uint64], a, b: Ct[uint64], cIn: Carry) {.inline.} = + ## Addition with carry + ## (CarryOut, Sum) <- a + b + CarryIn + when X86: + cOut = addcarry_u64(cIn, a, b, sum) + else: + block: + static: + doAssert GCC_Compatible + doAssert sizeof(int) == 8 + + var dblPrec {.noInit.}: uint128 + {.emit:[dblPrec, " = (unsigned __int128)", a," + (unsigned __int128)", b, " + (unsigned __int128)",cIn,";"].} + + # Don't forget to dereference the var param in C mode + when defined(cpp): + {.emit:[cOut, " = (NU64)(", dblPrec," >> ", 64'u64, ");"].} + {.emit:[sum, " = (NU64)", dblPrec,";"].} + else: + {.emit:["*",cOut, " = (NU64)(", dblPrec," >> ", 64'u64, ");"].} + {.emit:["*",sum, " = (NU64)", dblPrec,";"].} + +func subB*(bOut: var Borrow, diff: var Ct[uint64], a, b: Ct[uint64], bIn: Borrow) {.inline.} = + ## Substraction with borrow + ## (BorrowOut, Diff) <- a - b - borrowIn + when X86: + bOut = subborrow_u64(bIn, a, b, diff) + else: + block: + static: + doAssert GCC_Compatible + doAssert sizeof(int) == 8 + + var dblPrec {.noInit.}: uint128 + {.emit:[dblPrec, " = (unsigned __int128)", a," - (unsigned __int128)", b, " - (unsigned __int128)",bIn,";"].} + + # Don't forget to dereference the var param in C mode + # On borrow the high word will be 0b1111...1111 and needs to be masked + when defined(cpp): + {.emit:[bOut, " = (NU64)(", dblPrec," >> ", 64'u64, ") & 1;"].} + {.emit:[diff, " = (NU64)", dblPrec,";"].} + else: + {.emit:["*",bOut, " = (NU64)(", dblPrec," >> ", 64'u64, ") & 1;"].} + {.emit:["*",diff, " = (NU64)", dblPrec,";"].} diff --git a/constantine/primitives/constant_time.nim b/constantine/primitives/constant_time.nim index 41bd118..66586ab 100644 --- a/constantine/primitives/constant_time.nim +++ b/constantine/primitives/constant_time.nim @@ -6,27 +6,7 @@ # * Apache v2 license (license terms in the root directory or at 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. -# ############################################################ -# -# Constant-time primitives -# -# ############################################################ -type - BaseUint* = SomeUnsignedInt or byte - - Ct*[T: BaseUint] = distinct T - - CTBool*[T: Ct] = distinct T # range[T(0)..T(1)] - ## To avoid the compiler replacing bitwise boolean operations - ## by conditional branches, we don't use booleans. - ## We use an int to prevent compiler "optimization" and introduction of branches - # Note, we could use "range" but then the codegen - # uses machine-sized signed integer types. - # signed types and machine-dependent words are undesired - # - we don't want compiler optimizing signed "undefined behavior" - # - Basic functions like BIgInt add/sub - # return and/or accept CTBool, we don't want them - # to require unnecessarily 8 bytes instead of 4 bytes +import ./constant_time_types # ############################################################ # @@ -227,79 +207,6 @@ template `<=`*[T: Ct](x, y: T): CTBool[T] = template `xor`*[T: Ct](x, y: CTBool[T]): CTBool[T] = CTBool[T](noteq(T(x), T(y))) -func mux*[T](ctl: CTBool[T], x, y: T): T {.inline.}= - ## Multiplexer / selector - ## Returns x if ctl is true - ## else returns y - ## So equivalent to ctl? x: y - # - # TODO verify assembly generated - # Alternatives: - # - https://cryptocoding.net/index.php/Coding_rules - # - https://www.cl.cam.ac.uk/~rja14/Papers/whatyouc.pdf - when defined(amd64) or defined(i386): - when sizeof(T) == 8: - var muxed = x - asm """ - testq %[ctl], %[ctl] - cmovzq %[y], %[muxed] - : [muxed] "+r" (`muxed`) - : [ctl] "r" (`ctl`), [y] "r" (`y`) - : "cc" - """ - muxed - elif sizeof(T) == 4: - var muxed = x - asm """ - testl %[ctl], %[ctl] - cmovzl %[y], %[muxed] - : [muxed] "+r" (`muxed`) - : [ctl] "r" (`ctl`), [y] "r" (`y`) - : "cc" - """ - muxed - else: - {.error: "Unsupported word size".} - else: - let # Templates duplicate input params code - x_Mux = x - y_Mux = y - y_Mux xor (-T(ctl) and (x_Mux xor y_Mux)) - -func mux*[T: CTBool](ctl: CTBool, x, y: T): T {.inline.}= - ## Multiplexer / selector - ## Returns x if ctl is true - ## else returns y - ## So equivalent to ctl? x: y - when defined(amd64) or defined(i386): - when sizeof(T) == 8: - var muxed = x - asm """ - testq %[ctl], %[ctl] - cmovzq %[y], %[muxed] - : [muxed] "+r" (`muxed`) - : [ctl] "r" (`ctl`), [y] "r" (`y`) - : "cc" - """ - muxed - elif sizeof(T) == 4: - var muxed = x - asm """ - testl %[ctl], %[ctl] - cmovzl %[y], %[muxed] - : [muxed] "+r" (`muxed`) - : [ctl] "r" (`ctl`), [y] "r" (`y`) - : "cc" - """ - muxed - else: - {.error: "Unsupported word size".} - else: - let # Templates duplicate input params code - x_Mux = x - y_Mux = y - T(T.T(y_Mux) xor (-T.T(ctl) and T.T(x_Mux xor y_Mux))) - # ############################################################ # # Workaround system.nim `!=` template diff --git a/constantine/primitives/constant_time_types.nim b/constantine/primitives/constant_time_types.nim new file mode 100644 index 0000000..2cab28f --- /dev/null +++ b/constantine/primitives/constant_time_types.nim @@ -0,0 +1,41 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy AndrĂ©-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at 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. + +type + BaseUint* = SomeUnsignedInt or byte + + Ct*[T: BaseUint] = distinct T + + CTBool*[T: Ct] = distinct T # range[T(0)..T(1)] + ## To avoid the compiler replacing bitwise boolean operations + ## by conditional branches, we don't use booleans. + ## We use an int to prevent compiler "optimization" and introduction of branches + # Note, we could use "range" but then the codegen + # uses machine-sized signed integer types. + # signed types and machine-dependent words are undesired + # - we don't want compiler optimizing signed "undefined behavior" + # - Basic functions like BigInt add/sub + # return and/or accept CTBool, we don't want them + # to require unnecessarily 8 bytes instead of 4 bytes + # + # Also Nim adds tests everywhere a range type is used which is great + # except in a crypto library: + # - We don't want exceptions + # - Nim will be helpful and return the offending value, which might be secret data + # - This will hint the underlying C compiler about the value range + # and seeing 0/1 it might want to use branches again. + + Carry* = Ct[uint8] # distinct range[0'u8 .. 1] + Borrow* = Ct[uint8] # distinct range[0'u8 .. 1] + +const GCC_Compatible* = defined(gcc) or defined(clang) or defined(llvm_gcc) +const X86* = defined(amd64) or defined(i386) + +when sizeof(int) == 8 and GCC_Compatible: + type + uint128*{.importc: "unsigned __int128".} = object diff --git a/constantine/primitives/extended_precision.nim b/constantine/primitives/extended_precision.nim index 963909c..87fe11a 100644 --- a/constantine/primitives/extended_precision.nim +++ b/constantine/primitives/extended_precision.nim @@ -8,11 +8,11 @@ # ############################################################ # -# Unsafe constant-time primitives with specific restrictions +# Extended precision primitives # # ############################################################ -import ./constant_time +import ./constant_time_types # ############################################################ # @@ -37,37 +37,30 @@ func unsafeDiv2n1n*(q, r: var Ct[uint32], n_hi, n_lo, d: Ct[uint32]) {.inline.}= q = (Ct[uint32])(dividend div divisor) r = (Ct[uint32])(dividend mod divisor) -func unsafeFMA*(hi, lo: var Ct[uint32], a, b, c: Ct[uint32]) {.inline.} = +func muladd1*(hi, lo: var Ct[uint32], a, b, c: Ct[uint32]) {.inline.} = ## Extended precision multiplication + addition - ## This is constant-time on most hardware except some specific one like Cortex M0 ## (hi, lo) <- a*b + c - block: - # Note: since a and b use 31-bit, - # the result is 62-bit and carrying cannot overflow - let dblPrec = uint64(a) * uint64(b) + uint64(c) - hi = Ct[uint32](dblPrec shr 31) - lo = Ct[uint32](dblPrec) and Ct[uint32](1'u32 shl 31 - 1) + ## + ## Note: 0xFFFFFFFF² -> (hi: 0xFFFFFFFE, lo: 0x00000001) + ## so adding any c cannot overflow + ## + ## This is constant-time on most hardware + ## See: https://www.bearssl.org/ctmul.html + let dblPrec = uint64(a) * uint64(b) + uint64(c) + lo = (Ct[uint32])(dblPrec) + hi = (Ct[uint32])(dblPrec shr 32) -func unsafeFMA2*(hi, lo: var Ct[uint32], a1, b1, a2, b2, c1, c2: Ct[uint32]) {.inline.}= - ## (hi, lo) <- a1 * b1 + a2 * b2 + c1 + c2 - block: - # TODO: Can this overflow? - let dblPrec = uint64(a1) * uint64(b1) + - uint64(a2) * uint64(b2) + - uint64(c1) + - uint64(c2) - hi = Ct[uint32](dblPrec shr 31) - lo = Ct[uint32](dblPrec) and Ct[uint32](1'u32 shl 31 - 1) - -func unsafeFMA2_hi*(hi: var Ct[uint32], a1, b1, a2, b2, c1: Ct[uint32]) {.inline.}= - ## Returns the high word of the sum of extended precision multiply-adds - ## (hi, _) <- a1 * b1 + a2 * b2 + c - block: - # TODO: Can this overflow? - let dblPrec = uint64(a1) * uint64(b1) + - uint64(a2) * uint64(b2) + - uint64(c1) - hi = Ct[uint32](dblPrec shr 31) +func muladd2*(hi, lo: var Ct[uint32], a, b, c1, c2: Ct[uint32]) {.inline.}= + ## Extended precision multiplication + addition + addition + ## This is constant-time on most hardware except some specific one like Cortex M0 + ## (hi, lo) <- a*b + c1 + c2 + ## + ## Note: 0xFFFFFFFF² -> (hi: 0xFFFFFFFE, lo: 0x00000001) + ## so adding 0xFFFFFFFF leads to (hi: 0xFFFFFFFF, lo: 0x00000000) + ## and we have enough space to add again 0xFFFFFFFF without overflowing + let dblPrec = uint64(a) * uint64(b) + uint64(c1) + uint64(c2) + lo = (Ct[uint32])(dblPrec) + hi = (Ct[uint32])(dblPrec shr 32) # ############################################################ # @@ -76,191 +69,14 @@ func unsafeFMA2_hi*(hi: var Ct[uint32], a1, b1, a2, b2, c1: Ct[uint32]) {.inline # ############################################################ when sizeof(int) == 8: - const GccCompatible = defined(gcc) or defined(clang) or defined(llvm_gcc) + when defined(vcc): + from ./extended_precision_x86_64_msvc import unsafeDiv2n1n, muladd1, muladd2 + elif GCCCompatible: + # TODO: constant-time div2n1n + when X86: + from ./extended_precision_x86_64_gcc import unsafeDiv2n1n + from ./extended_precision_64bit_uint128 import muladd1, muladd2 + else: + from ./extended_precision_64bit_uint128 import unsafeDiv2n1n, muladd1, muladd2 - when GccCompatible: - type - uint128*{.importc: "unsigned __int128".} = object - - func unsafeDiv2n1n*(q, r: var Ct[uint64], n_hi, n_lo, d: Ct[uint64]) {.inline.}= - ## Division uint128 by uint64 - ## Warning ⚠️ : - ## - if n_hi == d, quotient does not fit in an uint64 and will throw SIGFPE - ## - if n_hi > d result is undefined - {.warning: "unsafeDiv2n1n is not constant-time at the moment on most hardware".} - - # TODO !!! - Replace by constant-time, portable, non-assembly version - # -> use uint128? Compiler might add unwanted branches - - # DIV r/m64 - # Divide RDX:RAX (n_hi:n_lo) by r/m64 - # - # Inputs - # - numerator high word in RDX, - # - numerator low word in RAX, - # - divisor as r/m parameter (register or memory at the compiler discretion) - # Result - # - Quotient in RAX - # - Remainder in RDX - - # 1. name the register/memory "divisor" - # 2. don't forget to dereference the var hidden pointer - # 3. - - # 4. no clobbered registers beside explectly used RAX and RDX - when defined(amd64): - when defined(cpp): - asm """ - divq %[divisor] - : "=a" (`q`), "=d" (`r`) - : "d" (`n_hi`), "a" (`n_lo`), [divisor] "rm" (`d`) - : - """ - else: - asm """ - divq %[divisor] - : "=a" (`*q`), "=d" (`*r`) - : "d" (`n_hi`), "a" (`n_lo`), [divisor] "rm" (`d`) - : - """ - else: - var dblPrec {.noInit.}: uint128 - {.emit:[dblPrec, " = (unsigned __int128)", n_hi," << 64 | (unsigned __int128)",n_lo,";"].} - - # Don't forget to dereference the var param in C mode - when defined(cpp): - {.emit:[q, " = (NU64)(", dblPrec," / ", d, ");"].} - {.emit:[r, " = (NU64)(", dblPrec," % ", d, ");"].} - else: - {.emit:["*",q, " = (NU64)(", dblPrec," / ", d, ");"].} - {.emit:["*",r, " = (NU64)(", dblPrec," % ", d, ");"].} - - func unsafeFMA*(hi, lo: var Ct[uint64], a, b, c: Ct[uint64]) {.inline.}= - ## Extended precision multiplication + addition - ## This is constant-time on most hardware except some specific one like Cortex M0 - ## (hi, lo) <- a*b + c - block: - # Note: since a and b use 63-bit, - # the result is 126-bit and carrying cannot overflow - var dblPrec {.noInit.}: uint128 - {.emit:[dblPrec, " = (unsigned __int128)", a," * (unsigned __int128)", b, " + (unsigned __int128)",c,";"].} - - # Don't forget to dereference the var param in C mode - when defined(cpp): - {.emit:[hi, " = (NU64)(", dblPrec," >> ", 63'u64, ");"].} - {.emit:[lo, " = (NU64)", dblPrec," & ", 1'u64 shl 63 - 1, ";"].} - else: - {.emit:["*",hi, " = (NU64)(", dblPrec," >> ", 63'u64, ");"].} - {.emit:["*",lo, " = (NU64)", dblPrec," & ", 1'u64 shl 63 - 1, ";"].} - - func unsafeFMA2*(hi, lo: var Ct[uint64], a1, b1, a2, b2, c1, c2: Ct[uint64]) {.inline.}= - ## (hi, lo) <- a1 * b1 + a2 * b2 + c1 + c2 - block: - # TODO: Can this overflow? - var dblPrec: uint128 - {.emit:[dblPrec, " = (unsigned __int128)", a1," * (unsigned __int128)", b1, - " + (unsigned __int128)", a2," * (unsigned __int128)", b2, - " + (unsigned __int128)", c1, - " + (unsigned __int128)", c2, ";"].} - # Don't forget to dereference the var param in C mode - when defined(cpp): - {.emit:[hi, " = (NU64)(", dblPrec," >> ", 63'u64, ");"].} - {.emit:[lo, " = (NU64)", dblPrec," & ", (1'u64 shl 63 - 1), ";"].} - else: - {.emit:["*",hi, " = (NU64)(", dblPrec," >> ", 63'u64, ");"].} - {.emit:["*",lo, " = (NU64)", dblPrec," & ", (1'u64 shl 63 - 1), ";"].} - - func unsafeFMA2_hi*(hi: var Ct[uint64], a1, b1, a2, b2, c: Ct[uint64]) {.inline.}= - ## Returns the high word of the sum of extended precision multiply-adds - ## (hi, _) <- a1 * b1 + a2 * b2 + c - block: - var dblPrec: uint128 - {.emit:[dblPrec, " = (unsigned __int128)", a1," * (unsigned __int128)", b1, - " + (unsigned __int128)", a2," * (unsigned __int128)", b2, - " + (unsigned __int128)", c, ";"].} - # Don't forget to dereference the var param in C mode - when defined(cpp): - {.emit:[hi, " = (NU64)(", dblPrec," >> ", 63'u64, ");"].} - else: - {.emit:["*",hi, " = (NU64)(", dblPrec," >> ", 63'u64, ");"].} - - elif defined(vcc): - func udiv128(highDividend, lowDividend, divisor: uint64, remainder: var uint64): uint64 {.importc:"_udiv128", header: "", nodecl.} - ## Division 128 by 64, Microsoft only, 64-bit only, - ## returns quotient as return value remainder as var parameter - ## Warning ⚠️ : - ## - if n_hi == d, quotient does not fit in an uint64 and will throw SIGFPE - ## - if n_hi > d result is undefined - - func unsafeDiv2n1n*(q, r: var Ct[uint64], n_hi, n_lo, d: Ct[uint64]) {.inline.}= - ## Division uint128 by uint64 - ## Warning ⚠️ : - ## - if n_hi == d, quotient does not fit in an uint64 and will throw SIGFPE - ## - if n_hi > d result is undefined - {.warning: "unsafeDiv2n1n is not constant-time at the moment on most hardware".} - - # TODO !!! - Replace by constant-time, portable, non-assembly version - # -> use uint128? Compiler might add unwanted branches - q = udiv128(n_hi, n_lo, d, r) - - func addcarry_u64(carryIn: cuchar, a, b: uint64, sum: var uint64): cuchar {.importc:"_addcarry_u64", header:"", nodecl.} - ## (CarryOut, Sum) <-- a + b - ## Available on MSVC and ICC (Clang and GCC have very bad codegen, use uint128 instead) - ## Return value is the carry-out - - func umul128(a, b: uint64, hi: var uint64): uint64 {.importc:"_umul128", header:"", nodecl.} - ## (hi, lo) <-- a * b - ## Return value is the low word - - func unsafeFMA*(hi, lo: var Ct[uint64], a, b, c: Ct[uint64]) {.inline.}= - ## Extended precision multiplication + addition - ## This is constant-time on most hardware except some specific one like Cortex M0 - ## (hi, lo) <- a*b + c - var carry: cuchar - var hi, lo: uint64 - lo = umul128(uint64(a), uint64(b), hi) - carry = addcarry_u64(cuchar(0), lo, uint64(c), lo) - discard addcarry_u64(carry, hi, 0, hi) - - func unsafeFMA2*(hi, lo: var Ct[uint64], a1, b1, a2, b2, c1, c2: Ct[uint64]) {.inline.}= - ## (hi, lo) <- a1 * b1 + a2 * b2 + c1 + c2 - var f1_lo, f1_hi, f2_lo, f2_hi: uint64 - var carry: cuchar - - f1_lo = umul128(uint64(a1), uint64(b1), f1_hi) - f2_lo = umul128(uint64(a2), uint64(b2), f2_hi) - - # On CPU with ADX: we can use addcarryx_u64 (adcx/adox) to have - # separate carry chains that can be processed in parallel by CPU - - # Carry chain 1 - carry = addcarry_u64(cuchar(0), f1_lo, uint64(c1), f1_lo) - discard addcarry_u64(carry, f1_hi, 0, f1_hi) - - # Carry chain 2 - carry = addcarry_u64(cuchar(0), f2_lo, uint64(c2), f2_lo) - discard addcarry_u64(carry, f2_hi, 0, f2_hi) - - # Merge - carry = addcarry_u64(cuchar(0), f1_lo, f2_lo, lo) - discard addcarry_u64(carry, f1_hi, f2_hi, hi) - - func unsafeFMA2_hi*(hi: var Ct[uint64], a1, b1, a2, b2, c: Ct[uint64]) {.inline.}= - ## Returns the high word of the sum of extended precision multiply-adds - ## (hi, _) <- a1 * b1 + a2 * b2 + c - - var f1_lo, f1_hi, f2_lo, f2_hi: uint64 - var carry: cuchar - - f1_lo = umul128(uint64(a1), uint64(b1), f1_hi) - f2_lo = umul128(uint64(a2), uint64(b2), f2_hi) - - carry = addcarry_u64(cuchar(0), f1_lo, uint64(c), f1_lo) - discard addcarry_u64(carry, f1_hi, 0, f1_hi) - - # Merge - var lo: uint64 - carry = addcarry_u64(cuchar(0), f1_lo, f2_lo, lo) - discard addcarry_u64(carry, f1_hi, f2_hi, hi) - - else: - {.error: "Compiler not implemented".} + export unsafeDiv2n1n, muladd1, muladd2 diff --git a/constantine/primitives/extended_precision_64bit_uint128.nim b/constantine/primitives/extended_precision_64bit_uint128.nim new file mode 100644 index 0000000..7e1e70f --- /dev/null +++ b/constantine/primitives/extended_precision_64bit_uint128.nim @@ -0,0 +1,81 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy AndrĂ©-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at 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. + +import ./constant_time_types + +# ############################################################ +# +# Extended precision primitives on GCC & Clang (all CPU archs) +# +# ############################################################ + +static: + doAssert GCC_Compatible + doAssert sizeof(int) == 8 + +func unsafeDiv2n1n*(q, r: var Ct[uint64], n_hi, n_lo, d: Ct[uint64]) {.inline.}= + ## Division uint128 by uint64 + ## Warning ⚠️ : + ## - if n_hi == d, quotient does not fit in an uint64 and will throw SIGFPE on some platforms + ## - if n_hi > d result is undefined + {.warning: "unsafeDiv2n1n is not constant-time at the moment on most hardware".} + + var dblPrec {.noInit.}: uint128 + {.emit:[dblPrec, " = (unsigned __int128)", n_hi," << 64 | (unsigned __int128)",n_lo,";"].} + + # Don't forget to dereference the var param in C mode + when defined(cpp): + {.emit:[q, " = (NU64)(", dblPrec," / ", d, ");"].} + {.emit:[r, " = (NU64)(", dblPrec," % ", d, ");"].} + else: + {.emit:["*",q, " = (NU64)(", dblPrec," / ", d, ");"].} + {.emit:["*",r, " = (NU64)(", dblPrec," % ", d, ");"].} + +func muladd1*(hi, lo: var Ct[uint64], a, b, c: Ct[uint64]) {.inline.} = + ## Extended precision multiplication + addition + ## (hi, lo) <- a*b + c + ## + ## Note: 0xFFFFFFFF_FFFFFFFF² -> (hi: 0xFFFFFFFFFFFFFFFE, lo: 0x0000000000000001) + ## so adding any c cannot overflow + ## + ## This is constant-time on most hardware + ## See: https://www.bearssl.org/ctmul.html + block: + var dblPrec {.noInit.}: uint128 + {.emit:[dblPrec, " = (unsigned __int128)", a," * (unsigned __int128)", b, " + (unsigned __int128)",c,";"].} + + # Don't forget to dereference the var param in C mode + when defined(cpp): + {.emit:[hi, " = (NU64)(", dblPrec," >> ", 64'u64, ");"].} + {.emit:[lo, " = (NU64)", dblPrec,";"].} + else: + {.emit:["*",hi, " = (NU64)(", dblPrec," >> ", 64'u64, ");"].} + {.emit:["*",lo, " = (NU64)", dblPrec,";"].} + +func muladd2*(hi, lo: var Ct[uint64], a, b, c1, c2: Ct[uint64]) {.inline.}= + ## Extended precision multiplication + addition + addition + ## This is constant-time on most hardware except some specific one like Cortex M0 + ## (hi, lo) <- a*b + c1 + c2 + ## + ## Note: 0xFFFFFFFF_FFFFFFFF² -> (hi: 0xFFFFFFFFFFFFFFFE, lo: 0x0000000000000001) + ## so adding 0xFFFFFFFFFFFFFFFF leads to (hi: 0xFFFFFFFFFFFFFFFF, lo: 0x0000000000000000) + ## and we have enough space to add again 0xFFFFFFFFFFFFFFFF without overflowing + block: + var dblPrec {.noInit.}: uint128 + {.emit:[ + dblPrec, " = (unsigned __int128)", a," * (unsigned __int128)", b, + " + (unsigned __int128)",c1," + (unsigned __int128)",c2,";" + ].} + + # Don't forget to dereference the var param in C mode + when defined(cpp): + {.emit:[hi, " = (NU64)(", dblPrec," >> ", 64'u64, ");"].} + {.emit:[lo, " = (NU64)", dblPrec,";"].} + else: + {.emit:["*",hi, " = (NU64)(", dblPrec," >> ", 64'u64, ");"].} + {.emit:["*",lo, " = (NU64)", dblPrec,";"].} diff --git a/constantine/primitives/extended_precision_x86_64_gcc.nim b/constantine/primitives/extended_precision_x86_64_gcc.nim new file mode 100644 index 0000000..3a4ec61 --- /dev/null +++ b/constantine/primitives/extended_precision_x86_64_gcc.nim @@ -0,0 +1,60 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy AndrĂ©-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at 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. + +import ./constant_time_types + +# ############################################################ +# +# Extended precision primitives for X86-64 on GCC & Clang +# +# ############################################################ + +static: + doAssert(defined(gcc) or defined(clang) or defined(llvm_gcc)) + doAssert sizeof(int) == 8 + doAssert X86 + +func unsafeDiv2n1n*(q, r: var Ct[uint64], n_hi, n_lo, d: Ct[uint64]) {.inline.}= + ## Division uint128 by uint64 + ## Warning ⚠️ : + ## - if n_hi == d, quotient does not fit in an uint64 and will throw SIGFPE + ## - if n_hi > d result is undefined + {.warning: "unsafeDiv2n1n is not constant-time at the moment on most hardware".} + + # TODO !!! - Replace by constant-time, portable, non-assembly version + # -> use uint128? Compiler might add unwanted branches + + # DIV r/m64 + # Divide RDX:RAX (n_hi:n_lo) by r/m64 + # + # Inputs + # - numerator high word in RDX, + # - numerator low word in RAX, + # - divisor as r/m parameter (register or memory at the compiler discretion) + # Result + # - Quotient in RAX + # - Remainder in RDX + + # 1. name the register/memory "divisor" + # 2. don't forget to dereference the var hidden pointer + # 3. - + # 4. no clobbered registers beside explicitly used RAX and RDX + when defined(cpp): + asm """ + divq %[divisor] + : "=a" (`q`), "=d" (`r`) + : "d" (`n_hi`), "a" (`n_lo`), [divisor] "rm" (`d`) + : + """ + else: + asm """ + divq %[divisor] + : "=a" (`*q`), "=d" (`*r`) + : "d" (`n_hi`), "a" (`n_lo`), [divisor] "rm" (`d`) + : + """ diff --git a/constantine/primitives/extended_precision_x86_64_msvc.nim b/constantine/primitives/extended_precision_x86_64_msvc.nim new file mode 100644 index 0000000..2be7d34 --- /dev/null +++ b/constantine/primitives/extended_precision_x86_64_msvc.nim @@ -0,0 +1,78 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy AndrĂ©-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at 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. + +import + ./constant_time_types, + ./addcarry_subborrow + +# ############################################################ +# +# Extended precision primitives for X86-64 on MSVC +# +# ############################################################ + +static: + doAssert defined(vcc) + doAssert sizeof(int) == 8 + doAssert X86 + +func udiv128(highDividend, lowDividend, divisor: Ct[uint64], remainder: var Ct[uint64]): Ct[uint64] {.importc:"_udiv128", header: "", nodecl.} + ## Division 128 by 64, Microsoft only, 64-bit only, + ## returns quotient as return value remainder as var parameter + ## Warning ⚠️ : + ## - if n_hi == d, quotient does not fit in an uint64 and will throw SIGFPE + ## - if n_hi > d result is undefined + +func umul128(a, b: Ct[uint64], hi: var Ct[uint64]): Ct[uint64] {.importc:"_umul128", header:"", nodecl.} + ## (hi, lo) <-- a * b + ## Return value is the low word + +func unsafeDiv2n1n*(q, r: var Ct[uint64], n_hi, n_lo, d: Ct[uint64]) {.inline.}= + ## Division uint128 by uint64 + ## Warning ⚠️ : + ## - if n_hi == d, quotient does not fit in an uint64 and will throw SIGFPE + ## - if n_hi > d result is undefined + {.warning: "unsafeDiv2n1n is not constant-time at the moment on most hardware".} + + # TODO !!! - Replace by constant-time, portable, non-assembly version + # -> use uint128? Compiler might add unwanted branches + q = udiv128(n_hi, n_lo, d, r) + +func muladd1*(hi, lo: var Ct[uint64], a, b, c: Ct[uint64]) {.inline.} = + ## Extended precision multiplication + addition + ## (hi, lo) <- a*b + c + ## + ## Note: 0xFFFFFFFF_FFFFFFFF² -> (hi: 0xFFFFFFFFFFFFFFFE, lo: 0x0000000000000001) + ## so adding any c cannot overflow + ## + ## This is constant-time on most hardware + ## See: https://www.bearssl.org/ctmul.html + var carry: Carry + lo = umul128(a, b, hi) + addC(carry, lo, lo, c, Carry(0)) + addC(carry, hi, hi, 0, carry) + +func muladd2*(hi, lo: var Ct[uint64], a, b, c1, c2: Ct[uint64]) {.inline.}= + ## Extended precision multiplication + addition + addition + ## This is constant-time on most hardware except some specific one like Cortex M0 + ## (hi, lo) <- a*b + c1 + c2 + ## + ## Note: 0xFFFFFFFF_FFFFFFFF² -> (hi: 0xFFFFFFFFFFFFFFFE, lo: 0x0000000000000001) + ## so adding 0xFFFFFFFFFFFFFFFF leads to (hi: 0xFFFFFFFFFFFFFFFF, lo: 0x0000000000000000) + ## and we have enough space to add again 0xFFFFFFFFFFFFFFFF without overflowing + # For speed this could be implemented with parallel pipelined carry chains + # via MULX + ADCX + ADOX + var carry1, carry2: Carry + + lo = umul128(a, b, hi) + # Carry chain 1 + addC(carry1, lo, lo, c1, Carry(0)) + addC(carry1, hi, hi, 0, carry1) + # Carry chain 2 + addC(carry2, lo, lo, c2, Carry(0)) + addC(carry2, hi, hi, 0, carry2) diff --git a/constantine/primitives/multiplexers.nim b/constantine/primitives/multiplexers.nim new file mode 100644 index 0000000..077bc28 --- /dev/null +++ b/constantine/primitives/multiplexers.nim @@ -0,0 +1,161 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy AndrĂ©-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at 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. + +import ./constant_time_types + +# ############################################################ +# +# Constant-time multiplexers/selectors/conditional moves +# +# ############################################################ + +# For efficiency, those are implemented in inline assembly if possible +# API: +# - mux(CTBool, Word, Word) +# - mux(CTBool, CTBool, CTBool) +# - ccopy(CTBool, var Word, Word) +# +# Those prevents the compiler from introducing branches and leaking secret data: +# - https://www.cl.cam.ac.uk/~rja14/Papers/whatyouc.pdf +# - https://github.com/veorq/cryptocoding + +# Generic implementation +# ------------------------------------------------------------ + +func mux_fallback[T](ctl: CTBool[T], x, y: T): T {.inline.}= + ## result = if ctl: x else: y + ## This is a constant-time operation + y xor (-T(ctl) and (x xor y)) + +func mux_fallback[T: CTBool](ctl: CTBool, x, y: T): T {.inline.}= + ## result = if ctl: x else: y + ## This is a constant-time operation + T(T.T(y) xor (-T.T(ctl) and T.T(x xor y))) + +func ccopy_fallback[T](ctl: CTBool[T], x: var T, y: T) {.inline.}= + ## Conditional copy + ## Copy ``y`` into ``x`` if ``ctl`` is true + x = ctl.mux_fallback(y, x) + +# x86 and x86-64 +# ------------------------------------------------------------ + +template mux_x86_impl() {.dirty.} = + static: doAssert(X86) + static: doAssert(GCC_Compatible) + + when sizeof(T) == 8: + var muxed = x + asm """ + testq %[ctl], %[ctl] + cmovzq %[y], %[muxed] + : [muxed] "+r" (`muxed`) + : [ctl] "r" (`ctl`), [y] "r" (`y`) + : "cc" + """ + muxed + elif sizeof(T) == 4: + var muxed = x + asm """ + testl %[ctl], %[ctl] + cmovzl %[y], %[muxed] + : [muxed] "+r" (`muxed`) + : [ctl] "r" (`ctl`), [y] "r" (`y`) + : "cc" + """ + muxed + else: + {.error: "Unsupported word size".} + +func mux_x86[T](ctl: CTBool[T], x, y: T): T {.inline.}= + ## Multiplexer / selector + ## Returns x if ctl is true + ## else returns y + ## So equivalent to ctl? x: y + mux_x86_impl() + +func mux_x86[T: CTBool](ctl: CTBool, x, y: T): T {.inline.}= + ## Multiplexer / selector + ## Returns x if ctl is true + ## else returns y + ## So equivalent to ctl? x: y + mux_x86_impl() + +func ccopy_x86[T](ctl: CTBool[T], x: var T, y: T) {.inline.}= + ## Conditional copy + ## Copy ``y`` into ``x`` if ``ctl`` is true + static: doAssert(X86) + static: doAssert(GCC_Compatible) + + when sizeof(T) == 8: + when defined(cpp): + asm """ + testq %[ctl], %[ctl] + cmovnzq %[y], %[x] + : [x] "+r" (`x`) + : [ctl] "r" (`ctl`), [y] "r" (`y`) + : "cc" + """ + else: + asm """ + testq %[ctl], %[ctl] + cmovnzq %[y], %[x] + : [x] "+r" (`*x`) + : [ctl] "r" (`ctl`), [y] "r" (`y`) + : "cc" + """ + elif sizeof(T) == 4: + when defined(cpp): + asm """ + testl %[ctl], %[ctl] + cmovnzl %[y], %[x] + : [x] "+r" (`x`) + : [ctl] "r" (`ctl`), [y] "r" (`y`) + : "cc" + """ + else: + asm """ + testl %[ctl], %[ctl] + cmovnzl %[y], %[x] + : [x] "+r" (`*x`) + : [ctl] "r" (`ctl`), [y] "r" (`y`) + : "cc" + """ + else: + {.error: "Unsupported word size".} + +# Public functions +# ------------------------------------------------------------ + +func mux*[T](ctl: CTBool[T], x, y: T): T {.inline.}= + ## Multiplexer / selector + ## Returns x if ctl is true + ## else returns y + ## So equivalent to ctl? x: y + when X86 and GCC_Compatible: + mux_x86(ctl, x, y) + else: + mux_fallback(ctl, x, y) + +func mux*[T: CTBool](ctl: CTBool, x, y: T): T {.inline.}= + ## Multiplexer / selector + ## Returns x if ctl is true + ## else returns y + ## So equivalent to ctl? x: y + when X86 and GCC_Compatible: + mux_x86(ctl, x, y) + else: + mux_fallback(ctl, x, y) + +func ccopy*[T](ctl: CTBool[T], x: var T, y: T) {.inline.}= + ## Conditional copy + ## Copy ``y`` into ``x`` if ``ctl`` is true + when X86 and GCC_Compatible: + ccopy_x86(ctl, x, y) + else: + ccopy_fallback(ctl, x, y) diff --git a/constantine/primitives/research/README.md b/constantine/primitives/research/README.md new file mode 100644 index 0000000..32cc175 --- /dev/null +++ b/constantine/primitives/research/README.md @@ -0,0 +1,45 @@ +# Compiler for generic inline assembly code-generation + +This folder holds alternative implementations of primitives +that uses inline assembly. + +This avoids the pitfalls of traditional compiler bad code generation +for multiprecision arithmetic (see GCC https://gcc.godbolt.org/z/2h768y) +or unsupported features like handling 2 carry chains for +multiplication using MULX/ADOX/ADCX. + +To be generic over multiple curves, +for example BN254 requires 4 words and BLS12-381 requires 6 words of size 64 bits, +the compilers is implemented as a set of macros that generate inline assembly. + +âš âš âš  Warning! Warning! Warning! + +This is a significant sacrifice of code readability, portability, auditability and maintainability in favor of performance. + +This combines 2 of the most notorious ways to obfuscate your code: +* metaprogramming and macros +* inline assembly + +Adventurers beware: not for the faint of heart. + +This is unfinished, untested, unused, unfuzzed and just a proof-of-concept at the moment.* + +_* I take no responsibility if this smashes your stack, eats your cat, hides a skeleton in your closet, warps a pink elephant in the room, summons untold eldritch horrors or causes the heat death of the universe. You have been warned._ + +_The road to debugging hell is paved with metaprogrammed assembly optimizations._ + +_For my defence, OpenSSL assembly is generated by a Perl script and neither Perl nor the generated Assembly are type-checked by a dependently-typed compiler._ + +## References + +Multiprecision (Montgomery) Multiplication & Squaring in Assembly + +- Intel MULX/ADCX/ADOX Table 2 p13: https://www.intel.cn/content/dam/www/public/us/en/documents/white-papers/ia-large-integer-arithmetic-paper.pdf +- Squaring: https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/large-integer-squaring-ia-paper.pdf +- https://eprint.iacr.org/eprint-bin/getfile.pl?entry=2017/558&version=20170608:200345&file=558.pdf +- https://github.com/intel/ipp-crypto +- https://github.com/herumi/mcl + +Experimentations in Nim + +- https://github.com/mratsim/finite-fields diff --git a/constantine/primitives/research/addcarry_subborrow_compiler.nim b/constantine/primitives/research/addcarry_subborrow_compiler.nim new file mode 100644 index 0000000..63d2523 --- /dev/null +++ b/constantine/primitives/research/addcarry_subborrow_compiler.nim @@ -0,0 +1,133 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy AndrĂ©-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at 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. + +# ############################################################ +# +# Add-with-carry and Sub-with-borrow +# +# ############################################################ +# +# This is a proof-of-concept optimal add-with-carry +# compiler implemented as Nim macros. +# +# This overcome the bad GCC codegen aven with addcary_u64 intrinsic. + +import macros + +func wordsRequired(bits: int): int {.compileTime.} = + ## Compute the number of limbs required + ## from the announced bit length + (bits + 64 - 1) div 64 + +type + BigInt[bits: static int] {.byref.} = object + ## BigInt + ## Enforce-passing by reference otherwise uint128 are passed by stack + ## which causes issue with the inline assembly + limbs: array[bits.wordsRequired, uint64] + +macro addCarryGen_u64(a, b: untyped, bits: static int): untyped = + var asmStmt = (block: + " movq %[b], %[tmp]\n" & + " addq %[tmp], %[a]\n" + ) + + let maxByteOffset = bits div 8 + const wsize = sizeof(uint64) + + when defined(gcc): + for byteOffset in countup(wsize, maxByteOffset-1, wsize): + asmStmt.add (block: + "\n" & + # movq 8+%[b], %[tmp] + " movq " & $byteOffset & "+%[b], %[tmp]\n" & + # adcq %[tmp], 8+%[a] + " adcq %[tmp], " & $byteOffset & "+%[a]\n" + ) + elif defined(clang): + # https://lists.llvm.org/pipermail/llvm-dev/2017-August/116202.html + for byteOffset in countup(wsize, maxByteOffset-1, wsize): + asmStmt.add (block: + "\n" & + # movq 8+%[b], %[tmp] + " movq " & $byteOffset & "%[b], %[tmp]\n" & + # adcq %[tmp], 8+%[a] + " adcq %[tmp], " & $byteOffset & "%[a]\n" + ) + + let tmp = ident("tmp") + asmStmt.add (block: + ": [tmp] \"+r\" (`" & $tmp & "`), [a] \"+m\" (`" & $a & "->limbs[0]`)\n" & + ": [b] \"m\"(`" & $b & "->limbs[0]`)\n" & + ": \"cc\"" + ) + + result = newStmtList() + result.add quote do: + var `tmp`{.noinit.}: uint64 + + result.add nnkAsmStmt.newTree( + newEmptyNode(), + newLit asmStmt + ) + + echo result.toStrLit + +func `+=`(a: var BigInt, b: BigInt) {.noinline.}= + # Depending on inline or noinline + # the generated ASM addressing must be tweaked for Clang + # https://lists.llvm.org/pipermail/llvm-dev/2017-August/116202.html + addCarryGen_u64(a, b, BigInt.bits) + +# ############################################# +when isMainModule: + import random + proc rand(T: typedesc[BigInt]): T = + for i in 0 ..< result.limbs.len: + result.limbs[i] = uint64(rand(high(int))) + + proc main() = + block: + let a = BigInt[128](limbs: [high(uint64), 0]) + let b = BigInt[128](limbs: [1'u64, 0]) + + echo "a: ", a + echo "b: ", b + echo "------------------------------------------------------" + + var a1 = a + a1 += b + echo a1 + echo "======================================================" + + block: + let a = rand(BigInt[256]) + let b = rand(BigInt[256]) + + echo "a: ", a + echo "b: ", b + echo "------------------------------------------------------" + + var a1 = a + a1 += b + echo a1 + echo "======================================================" + + block: + let a = rand(BigInt[384]) + let b = rand(BigInt[384]) + + echo "a: ", a + echo "b: ", b + echo "------------------------------------------------------" + + var a1 = a + a1 += b + echo a1 + + main() diff --git a/constantine/tower_field_extensions/abelian_groups.nim b/constantine/tower_field_extensions/abelian_groups.nim index d827f8b..0ee83b7 100644 --- a/constantine/tower_field_extensions/abelian_groups.nim +++ b/constantine/tower_field_extensions/abelian_groups.nim @@ -9,7 +9,7 @@ import ../arithmetic/finite_fields, ../config/common, - ../primitives/constant_time + ../primitives # ############################################################ # diff --git a/tests/prng.nim b/tests/prng.nim index 37c4967..43d68af 100644 --- a/tests/prng.nim +++ b/tests/prng.nim @@ -7,7 +7,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - ../constantine/arithmetic/bigints_checked, + ../constantine/arithmetic/bigints, ../constantine/config/[common, curves] # ############################################################ @@ -86,13 +86,12 @@ func random[T](rng: var RngState, a: var T, C: static Curve) {.noInit.}= when T is BigInt: var reduced, unreduced{.noInit.}: T - unreduced.setInternalBitLength() for i in 0 ..< unreduced.limbs.len: unreduced.limbs[i] = Word(rng.next()) # Note: a simple modulo will be biaised but it's simple and "fast" reduced.reduce(unreduced, C.Mod.mres) - a.montyResidue(reduced, C.Mod.mres, C.getR2modP(), C.getNegInvModWord()) + a.montyResidue(reduced, C.Mod.mres, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul()) else: for field in fields(a): diff --git a/tests/test_bigints.nim b/tests/test_bigints.nim index 591b720..c747ce3 100644 --- a/tests/test_bigints.nim +++ b/tests/test_bigints.nim @@ -8,9 +8,9 @@ import unittest, ../constantine/io/io_bigints, - ../constantine/arithmetic/bigints_checked, + ../constantine/arithmetic/bigints, ../constantine/config/common, - ../constantine/primitives/constant_time + ../constantine/primitives proc main() = suite "isZero": diff --git a/tests/test_bigints_multimod.nim b/tests/test_bigints_multimod.nim index ff6553c..0a9fd12 100644 --- a/tests/test_bigints_multimod.nim +++ b/tests/test_bigints_multimod.nim @@ -11,8 +11,8 @@ import unittest, # Third-party ../constantine/io/io_bigints, - ../constantine/arithmetic/[bigints_raw, bigints_checked], - ../constantine/primitives/constant_time + ../constantine/arithmetic/bigints, + ../constantine/primitives proc main() = suite "Bigints - Multiprecision modulo": @@ -89,4 +89,52 @@ proc main() = check: bool(r == expected) + test "bitsize 1882 mod bitsize 312": + let a = BigInt[1882].fromHex("0x3feb1d432a950d856fc121c5057671cf81bf9d283a30b69128e84d57900aba486136b9e93f96293dbf7e280b8a641d970748b27ba0986411c7359f32f37447e34ae9e9189336269326fb62fd4d0891bf2383548e8ada92517cf5001e449dd5b4c6501b361636c13f3d5db5ed40f7048f8b1b8db65e9a34a08992e19527ded175fd6b4c4559c25c384691f0567ad27cf5df2b4192d94dc3bf596216067fd02a3790c048bc4bff16e70f84c395ff1243d4b92b514d0c22fc35a82611b77137f09ec8bc31df58fbea2b532ef38ed9078bd2982893326833a20daf2792bdf1ac75ca80e2ffd063f49bb173e7b100") + let m = BigInt[312].fromHex("0x8bad37615c65cb40b592525aeb19de0b8a3f9db87f3c77050a77050ebe81712d78253cdc0eafec") + + let expected = BigInt[312].fromHex("0x1d79fa2f576827a70b38b303036884b346fc52941b2df0863e8f635c467ea1aec04520e6feb614") + + var r: BigInt[312] + r.reduce(a, m) + + check: + bool(r == expected) + + test "bitsize 5276 mod bitsize 337": + let a = BigInt[5276].fromHex("0xdcc610304e437c91df568effa736e9ec472d921d2e32f0123f59f8a0e7a639a84db3d6e91c4ce9164e2183aeb9efdfdf5b179b1e5b8074602193b9ba0f5cf547ce31c6c6d33317c40fdcc66090d13034a8ed82b1244cd9e82ec43b08a4a8cd7aaa4937b72b19b01c942427db3e630e70f6823f36a4d0db17b0515ab1582672f613c22f43b2743929d92a924b2d7529a08fa2950ac90fd529207d3dd55a65f80f77715b340755f545424375a1f6dfe3eea1309365036d924226297ecd1296c5938a7b18fe36c3126f54161818ff8e29d69c25b7a47a47061f6e76b6ffbe0c2dbcbf83f49b0bd24cb6f2de460e6c6540e15e23e23573a04dff7d18f88e266a1e36627181dd18a9a182182b1c4e1ec8123b916d18a82139c6b2f5cc7206681b21ec3b14f4da44337892a90db21e070c8799a5cd7e81c03b901ade08021401d6a4cd27bef1e1215c65c2e8abadf44cc455383b37c12fe1f25774bbb0552ca54699c8d38cd88b56ff80c130734dbd231f8f2d15e62effe7bfedde43c4d06f06115befbafabcb1128b3c80f8c6395696f28b6d32c12cc74ba7fcef95e97bd854c98716b6c079d971199a4d3fa4f6d7f901f5370b3f0a4fa6dddff81820ca012bb821560b86701d25a3c99f0daae5824bc5d4731c1e5e879b94bb0a5a862ac79d22fc42d20d3d8963a49997627d4d246088a21531e58174e55eed8007c7e05bece76c64a368c42a7e178b0ba0ce3b54f1d9a568755c71f3518e5d10caa2eda8edd74f13c41b70c6ff0a75f6b821b38cb6148acf6890fc79d508cfa741c8514498b81aaf1698420bf844742d325afe8fce3e85c1d2aefc6bc254e3628f19116643a538c6657a937d62069dfe7217a9e9138e8a12f9857c9eb671c2adb3b3129d0653eb62296bdcfe51335b966e39838a4b18fce380af1f00") + let m = BigInt[337].fromHex("0x016255c2e37f1b1405f9f195040e80778b896b23a1487a40ece792894025590800bcadc343fcef4e2d01b8") + + let expected = BigInt[337].fromHex("0x66bb36adf84b9024f97100688cc66be2f412fd91e9bae3623e810dcae86166a52bdb4c889fa0e5d128d8") + + var r: BigInt[337] + r.reduce(a, m) + + check: + bool(r == expected) + + test "bitsize 4793 mod bitsize 190": + let a = BigInt[4793].fromHex("0x20924645cabc04f0213ba42961e10dedd2c6bd0c9625d04949037b15f9546001551651049038285b441824ef5540a174da0d5ab5f6f07750b9d6ea21a8dd127b467cd1ff0d547d7c86705402bfb8efee231c8385d14666d4e5fdd4e4e6c230ed61b631a6387c57823578139db306c1687bb950985e608c2792694e895e97039c0c155c79b3d595b391f5f8217feeebc20b093658d3e7612449ef575da3d0cde0d3726c58ca9302952deaee8b44a31029086db65838767c60b63f68f9c207ca128574ff9023fc29de264c8e4df20b7764064f9228a2481d5936cc840e107f73b04fcf31f8060c38ea5fb9c8f165e4bbdd1c7b8f0cfb950be57d87678a0a3d45eb1ccbef1a977e881de4f4f95ef0e144a0486ca47084a565242a2baab7a5383e85d51c466d7b03e1f06285bfe04cbb4b90e829a50af103ab8a812cfdad100344b3ae0ab3b96e26a0d97cf16d1910212471f9b3f5e3d0133360387ca3a52682d68447e7ac454e321bc5381a24ff5348baad68d3609a7dfa2118275f2620cf30b1ebb21d98b1d783b45c2acf4a9a9b1cfeba21b2fe1d93fda3234ee90bdba1b23e3a514c7e2189f7bf07236397e1efc5cb5b3a3e748ba130272d880b9d74fc6c2386f19c9e51093ce885ad60493a3d4d0c84154e6fb6d4bb222207eb9f3a2136cebe883a5a89b95eba5363c113f330636d00dda40f3445afb651a56a1d00e5d3815b3c06f123e5eb6b8ce5621ab8f05765fe803e94a12998c249cb1e84c9c4785c8631454283e0471149bc541eebc691b3231e4969b433b9c8195db915cb3baef8db7b3ab0dff2aa7f284e5b86e8055ab95bf45086a216138000") + let m = BigInt[190].fromHex("0x3cf10d948e00a135ab10a6d073b8289e8465d5798d06891c") + + let expected = BigInt[190].fromHex("0x1154587f8cfac96bc146790bc49262ad32e1560a0bf734a4") + + var r: BigInt[190] + r.reduce(a, m) + + check: + bool(r == expected) + + test "bitsize 5240 mod bitsize 3072": + let a = BigInt[5240].fromHex("0xfd76093ad413df93dddded94a16c17ffbdd1d8ce9377f5940af54293410603fd381822bb9cec0074005f68dd7bc33f879fb0613b9cd0bac50c27e5e40a0c3948e5b4a07e6c9b1795f2f60647d67b9fd5025d82deffcbb62209a921eb766f7a2335d1b6ca0a4877c948d5cf68aaa2d5bdbea3f991eb027b91d03c91712d739cf6522279007add5febb85fe8f5ef661641fc2dc36b37e709d77f3a0f016f1b421527d2a28c9e8734f243a81e985a26e6ec5650ae81f10381869a9a78e5dac5e6d65783c40f8c232398425e96da2c6d94290ee6463580c826c609691a860f8ceb233a072fca384a9a74d15beaad7df8f1dcde437a02db6218b0c0bca43f9f936bdede271b730b098cf4dd97a84a10feb7eb04841ac01e4728a12a1f96b88ac91bef33818095777893813635cc918480f255a45bf9ebc740e3992877879d4ee64b1aad22439dc9872e5ff13a25dcb32669dab77e05982adbfb06073d5b9bd2b0dcdc7a515296b13e7251d3fb6ca132492f66d312e2610011284b6a2f2a1c26a873959ff5935ddd1e229d4ec7cdf3fa1ce1f55bc549481adfee5ec8aa8b4eddad88c74298d50c2d310be6c21e92067bf8b5ae2330f750f60122251d1fcf84c58a3abee3bed8715dda1c016eb58672faed0a5c678806028195586a349702eeff0738d14ac9a2ced66a50e894cf0a3d546608e6666443d5ea4bc6e7078ae356257ce12fc3d6cf84fe52f13fe27ad89038d041698ed615c856d326d2f1fc5cc916a5176d44a965cf5247b81b901212eb3f35912e82d68b28fe438b3f9cb43362794a53976dcc6ec21aa097813e47f7cb02af3e2bdd9f4323e3ffb577a800cdb5fe925b832263db0332a235c6d8de3df97d8a963c8062f1d39aa302f8db2e2ac292fb6f66d4e2e074e8ce4c77ca0a311bcd3455") + let m = BigInt[3072].fromHex("0x82a3b927471854fe51e4246c36e83a110ab5fec30a5f26fca0316b8ab42784bc17015ef9d0217769e695b92bfe4f30ffcfef881179edc6623dcaf305ee4424da00d8c49a873535e095ac64a8cc66767c26ffa7f2f1acdaedd82b09b62d297f951cd3af83e7023b4eafea8056fc9e4f53f03eb9ee93613d58219214f8d884f51d4e09b336a4bf53fb29a4394fc9b8d4004f4ab04cdfda43441e63846e3dbd02c46ab521b85a16d4a063c33be63e88c9b3fec486f9eda4958a167cb4dd64dd44c7047e4f1372e6ce6f29bbc4a6cc0f498c0428dbc35daaa81abedd937e602ce3eb38666f0ccd603955949e068dd005e2e2bf6d423fd183fcbf61c504eeffa589c3482251b1191e7d71b8e31fc05979b4ebb6ab57ce810d6e34144a8417ab2ca45709b3841bb08cbf38658d2f4129adee121933369deb238db2f74df4490ea5486685554cc4dac015f4d09ded70a4fc808b080142eb7c865fe8e89046f3c0de448f1442258d2cd565dfd457cfb49ab0c0a735196e6cb06a962f29e53060576327b8") + + let expected = BigInt[3072].fromHex("0x75be9187192dccf08bedcb06c7fba60830840cb8a5c3a5895e63ffd78073f2f7e0ccc72ae2f91c2be9fe51e48373bf4426e6e1babb9bc5374747a0e24b982a27359cf403a6bb900800b6dd52b309788df7f599f3db6f5b5ba5fbe88b8d03ab32fbe8d75dbbad0178f70dc4dfbc39008e5c8a4975f08060f4af1718e1a8811b0b73daabf67bf971c1fa79d678e3e2bf878a844004d1ab5b11a2c3e4fa8abbbe15b75a4a15a4c0eecd128ad7b13571545a967cac88d1b1e88c3b09723849c54adede6b36dd21000f41bc404083bf01902d2d3591c2e51fe0cc26d691cbc9ba6ea3137bd977745cc8761c828f7d54899841701faeca7ff5fc975968693284c2dcaf68e9852a67b5782810834f2eed0ba8e69d18c2a9d8aa1d81528110f0156610febe5ee2db65add65006a9f91370828e356c7751fa50bb49f43b408cd2f4767a43bc57888afe01d2a85d457c68a3eb60de713b79c318b92cb1b2837cf78f9e6e5ec0091d2810a34a1c75400190f8582a8b42f436b799db088689f8187b6db8530d") + + var r: BigInt[3072] + r.reduce(a, m) + + check: + bool(r == expected) + main() diff --git a/tests/test_bigints_vs_gmp.nim b/tests/test_bigints_vs_gmp.nim index f11840b..2bfe2b4 100644 --- a/tests/test_bigints_vs_gmp.nim +++ b/tests/test_bigints_vs_gmp.nim @@ -13,7 +13,7 @@ import gmp, stew/byteutils, # Internal ../constantine/io/io_bigints, - ../constantine/arithmetic/[bigints_raw, bigints_checked], + ../constantine/arithmetic/bigints, ../constantine/primitives/constant_time # We test up to 1024-bit, more is really slow @@ -113,16 +113,16 @@ proc main() = var aW, mW: csize # Word written by GMP - discard mpz_export(aBuf[0].addr, aW.addr, GMP_LeastSignificantWordFirst, 1, GMP_WordNativeEndian, 0, a) - discard mpz_export(mBuf[0].addr, mW.addr, GMP_LeastSignificantWordFirst, 1, GMP_WordNativeEndian, 0, m) + discard mpz_export(aBuf[0].addr, aW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, a) + discard mpz_export(mBuf[0].addr, mW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, m) # Since the modulus is using all bits, it's we can test for exact amount copy - doAssert aLen >= aW, "Expected at most " & $aLen & " bytes but wrote " & $aW & " for " & toHex(aBuf) & " (little-endian)" - doAssert mLen == mW, "Expected " & $mLen & " bytes but wrote " & $mW & " for " & toHex(mBuf) & " (little-endian)" + doAssert aLen >= aW, "Expected at most " & $aLen & " bytes but wrote " & $aW & " for " & toHex(aBuf) & " (big-endian)" + doAssert mLen == mW, "Expected " & $mLen & " bytes but wrote " & $mW & " for " & toHex(mBuf) & " (big-endian)" # Build the bigint - let aTest = BigInt[aBits].fromRawUint(aBuf, littleEndian) - let mTest = BigInt[mBits].fromRawUint(mBuf, littleEndian) + let aTest = BigInt[aBits].fromRawUint(aBuf.toOpenArray(0, aW-1), bigEndian) + let mTest = BigInt[mBits].fromRawUint(mBuf.toOpenArray(0, mW-1), bigEndian) ######################################################### # Modulus @@ -135,15 +135,16 @@ proc main() = # Check var rGMP: array[mLen, byte] var rW: csize # Word written by GMP - discard mpz_export(rGMP[0].addr, rW.addr, GMP_LeastSignificantWordFirst, 1, GMP_WordNativeEndian, 0, r) + discard mpz_export(rGMP[0].addr, rW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, r) var rConstantine: array[mLen, byte] - exportRawUint(rConstantine, rTest, littleEndian) + exportRawUint(rConstantine, rTest, bigEndian) # echo "rGMP: ", rGMP.toHex() # echo "rConstantine: ", rConstantine.toHex() - doAssert rGMP == rConstantine, block: + # Note: in bigEndian, GMP aligns left while constantine aligns right + doAssert rGMP.toOpenArray(0, rW-1) == rConstantine.toOpenArray(mLen-rW, mLen-1), block: # Reexport as bigEndian for debugging discard mpz_export(aBuf[0].addr, aW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, a) discard mpz_export(mBuf[0].addr, mW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, m) @@ -152,6 +153,7 @@ proc main() = " m (" & align($mBits, 4) & "-bit): " & mBuf.toHex & "\n" & "failed:" & "\n" & " GMP: " & rGMP.toHex() & "\n" & - " Constantine: " & rConstantine.toHex() + " Constantine: " & rConstantine.toHex() & "\n" & + "(Note that GMP aligns bytes left while constantine aligns bytes right)" main() diff --git a/tests/test_finite_fields_powinv.nim b/tests/test_finite_fields_powinv.nim index 122e87b..20430f8 100644 --- a/tests/test_finite_fields_powinv.nim +++ b/tests/test_finite_fields_powinv.nim @@ -7,7 +7,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import unittest, - ../constantine/arithmetic/[bigints_checked, finite_fields], + ../constantine/arithmetic/[bigints, finite_fields], ../constantine/io/io_fields, ../constantine/config/curves diff --git a/tests/test_finite_fields_vs_gmp.nim b/tests/test_finite_fields_vs_gmp.nim index 426affa..0c29b0d 100644 --- a/tests/test_finite_fields_vs_gmp.nim +++ b/tests/test_finite_fields_vs_gmp.nim @@ -13,7 +13,7 @@ import gmp, stew/byteutils, # Internal ../constantine/io/[io_bigints, io_fields], - ../constantine/arithmetic/[finite_fields, bigints_checked], + ../constantine/arithmetic/[finite_fields, bigints], ../constantine/primitives/constant_time, ../constantine/config/curves @@ -100,7 +100,7 @@ proc binary_epilogue[C: static Curve, N: static int]( " b: " & bBuf.toHex & "\n" & "failed:" & "\n" & " GMP: " & rGMP.toHex() & "\n" & - " Constantine: " & rConstantine.toHex() & + " Constantine: " & rConstantine.toHex() & "\n" & "(Note that GMP aligns bytes left while constantine aligns bytes right)" # ############################################################ diff --git a/tests/test_fp2.nim b/tests/test_fp2.nim index 7dfae9f..86e96c3 100644 --- a/tests/test_fp2.nim +++ b/tests/test_fp2.nim @@ -12,7 +12,7 @@ import # Internals ../constantine/tower_field_extensions/[abelian_groups, fp2_complex], ../constantine/config/[common, curves], - ../constantine/arithmetic/bigints_checked, + ../constantine/arithmetic/bigints, # Test utilities ./prng @@ -45,7 +45,7 @@ suite "đť”˝p2 = đť”˝p[đť‘–] (irreducible polynomial x²+1)": O var r: typeof(C.Mod.mres) - r.redc(oneFp2.c0.mres, C.Mod.mres, C.getNegInvModWord()) + r.redc(oneFp2.c0.mres, C.Mod.mres, C.getNegInvModWord(), canUseNoCarryMontyMul = false) check: bool(r == oneBig) diff --git a/tests/test_io_bigints.nim b/tests/test_io_bigints.nim index 0ae021d..f5a87e1 100644 --- a/tests/test_io_bigints.nim +++ b/tests/test_io_bigints.nim @@ -9,7 +9,7 @@ import unittest, random, ../constantine/io/io_bigints, ../constantine/config/common, - ../constantine/arithmetic/bigints_checked + ../constantine/arithmetic/bigints randomize(0xDEADBEEF) # Random seed for reproducibility type T = BaseType @@ -24,7 +24,6 @@ proc main() = check: T(big.limbs[0]) == 0 - T(big.limbs[1]) == 0 test "Parsing and dumping round-trip on uint64": block: @@ -85,4 +84,11 @@ proc main() = check: p == hex + test "Round trip on 3072-bit integer": + const n = "0x75be9187192dccf08bedcb06c7fba60830840cb8a5c3a5895e63ffd78073f2f7e0ccc72ae2f91c2be9fe51e48373bf4426e6e1babb9bc5374747a0e24b982a27359cf403a6bb900800b6dd52b309788df7f599f3db6f5b5ba5fbe88b8d03ab32fbe8d75dbbad0178f70dc4dfbc39008e5c8a4975f08060f4af1718e1a8811b0b73daabf67bf971c1fa79d678e3e2bf878a844004d1ab5b11a2c3e4fa8abbbe15b75a4a15a4c0eecd128ad7b13571545a967cac88d1b1e88c3b09723849c54adede6b36dd21000f41bc404083bf01902d2d3591c2e51fe0cc26d691cbc9ba6ea3137bd977745cc8761c828f7d54899841701faeca7ff5fc975968693284c2dcaf68e9852a67b5782810834f2eed0ba8e69d18c2a9d8aa1d81528110f0156610febe5ee2db65add65006a9f91370828e356c7751fa50bb49f43b408cd2f4767a43bc57888afe01d2a85d457c68a3eb60de713b79c318b92cb1b2837cf78f9e6e5ec0091d2810a34a1c75400190f8582a8b42f436b799db088689f8187b6db8530d" + let x = BigInt[3072].fromHex(n) + let h = x.toHex(bigEndian) + + check: n == h + main() diff --git a/tests/test_io_fields.nim b/tests/test_io_fields.nim index 1e06d7c..177f488 100644 --- a/tests/test_io_fields.nim +++ b/tests/test_io_fields.nim @@ -10,7 +10,7 @@ import unittest, random, ../constantine/io/[io_bigints, io_fields], ../constantine/config/curves, ../constantine/config/common, - ../constantine/arithmetic/[bigints_checked, finite_fields] + ../constantine/arithmetic/[bigints, finite_fields] randomize(0xDEADBEEF) # Random seed for reproducibility type T = BaseType @@ -18,6 +18,30 @@ type T = BaseType proc main() = suite "IO - Finite fields": test "Parsing and serializing round-trip on uint64": + # 101 --------------------------------- + block: + # "Little-endian" - 0 + let x = BaseType(0) + let x_bytes = cast[array[sizeof(BaseType), byte]](x) + var f: Fp[Fake101] + f.fromUint(x) + + var r_bytes: array[sizeof(BaseType), byte] + exportRawUint(r_bytes, f, littleEndian) + check: x_bytes == r_bytes + + block: + # "Little-endian" - 1 + let x = BaseType(1) + let x_bytes = cast[array[sizeof(BaseType), byte]](x) + var f: Fp[Fake101] + f.fromUint(x) + + var r_bytes: array[sizeof(BaseType), byte] + exportRawUint(r_bytes, f, littleEndian) + check: x_bytes == r_bytes + + # Mersenne 61 --------------------------------- block: # "Little-endian" - 0 let x = 0'u64 @@ -103,4 +127,20 @@ proc main() = check: p == hex + test "Round trip on prime field of NIST P256 (secp256r1) curve": + block: # 2^126 + const p = "0x0000000000000000000000000000000040000000000000000000000000000000" + let x = Fp[P256].fromBig BigInt[256].fromHex(p) + let hex = x.toHex(bigEndian) + + check: p == hex + + test "Round trip on prime field of BLS12_381 curve": + block: # 2^126 + const p = "0x000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000" + let x = Fp[BLS12_381].fromBig BigInt[381].fromHex(p) + let hex = x.toHex(bigEndian) + + check: p == hex + main() diff --git a/tests/test_primitives.nim b/tests/test_primitives.nim index ee8ef31..a0892c5 100644 --- a/tests/test_primitives.nim +++ b/tests/test_primitives.nim @@ -7,7 +7,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import unittest, random, math, - ../constantine/primitives/constant_time + ../constantine/primitives # Random seed for reproducibility randomize(0xDEADBEEF)