Add support for SIKE/p503 post-quantum KEM

Based on Microsoft's implementation available on github:
Source: https://github.com/Microsoft/PQCrypto-SIDH
Commit: 77044b76181eb61c744ac8eb7ddc7a8fe72f6919

Following changes has been applied

* In intel assembly, use MOV instead of MOVQ:
  Intel instruction reference in the Intel Software Developer's Manual
  volume 2A, the MOVQ has 4 forms. None of them mentions moving
  literal to GPR, hence "movq $rax, 0x0" is wrong. Instead, on 64bit
  system, MOV can be used.

* Some variables were wrongly zero-initialized (as per C99 spec).

* Rewrite x86_64 assembly to AT&T format.

* Move assembly for x86_64 and aarch64 to perlasm.

* Changes to aarch64 assembly, to avoid using x18 platform register.
  Assembly also correctly constructs linked list of stack-frames as
  described in AAPCS64, 5.2.3.

* Move constant values to .RODATA segment, as keeping them in .TEXT
  segment is not compatible with XOM.

* Fixes issue in arm64 code related to the fact that compiler doesn't
  reserve enough space for the linker to relocate address of a global
  variable when used by 'ldr' instructions. Solution is to use 'adrp'
  followed by 'add' instruction. Relocations for 'adrp' and 'add'
  instructions is generated by prefixing the label with :pg_hi21:
  and :lo12: respectively.

* Enable MULX and ADX. Code from MS doesn't support PIC. MULX can't
  reference global variable directly. Instead RIP-relative addressing
  can be used. This improves performance around 10%-13% on SkyLake

* Check if CPU supports BMI2 and ADOX instruction at runtime. On AMD64
  optimized implementation of montgomery multiplication and reduction
  have 2 implementations - faster one takes advantage of BMI2
  instruction set introduced in Haswell and ADOX introduced in
  Broadwell. Thanks to OPENSSL_ia32cap_P it can be decided at runtime
  which implementation to choose. As CPU configuration is static by
  nature, branch predictor will be correct most of the time and hence
  this check very often has no cost.

* Reuse some utilities from boringssl instead of reimplementing them.
  This includes things like:
  * definition of a limb size (use crypto_word_t instead of digit_t)
  * use functions for checking in constant time if value is 0 and/or
    less then
  * #define's used for conditional compilation

* Use SSE2 for conditional swap on vector registers. Improves
  performance a little bit.

* Fix f2elm_t definition. Code imported from MSR defines f2elm_t type as
  a array of arrays. This decays to a pointer to an array (when passing
  as an argument). In C, one can't assign const pointer to an array with
  non-const pointer to an array. Seems it violates 6.7.3/8 from C99
  (same for C11). This problem occures in GCC 6, only when -pedantic
  flag is specified and it occures always in GCC 4.9 (debian jessie).

* Fix definition of eval_3_isog. Second argument in eval_3_isog mustn't be
  const. Similar reason as above.

* Use HMAC-SHA256 instead of cSHAKE-256 to avoid upstreaming cSHAKE
  and SHA3 code.

* Add speed and unit tests for SIKE.

Some speed results:

Skylake (64-bit):

Did 408 SIKE/P503 generate operations in 1002573us (407.0 ops/sec)
Did 275 SIKE/P503 encap operations in 1070570us (256.9 ops/sec)
Did 264 SIKE/P503 decap operations in 1098955us (240.2 ops/sec)

Skylake (32-bit):

Did 9 SIKE/P503 generate operations in 1051620us (8.6 ops/sec)
Did 5 SIKE/P503 encap operations in 1038251us (4.8 ops/sec)
Did 5 SIKE/P503 decap operations in 1103617us (4.5 ops/sec)

Change-Id: I22f0bb1f9edff314a35cd74b48e8c4962568e330
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/35204
Reviewed-by: Adam Langley <alangley@gmail.com>
diff --git a/LICENSE b/LICENSE
index 49c41fa..2f4dfcd 100644
--- a/LICENSE
+++ b/LICENSE
@@ -181,6 +181,29 @@
 SOFTWARE.
 
 
+The code in third_party/sike also carries the MIT license:
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE
+
+
 Licenses for support code
 -------------------------
 
diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt
index 5cdfa40..1c505bc 100644
--- a/crypto/CMakeLists.txt
+++ b/crypto/CMakeLists.txt
@@ -115,6 +115,7 @@
 
     chacha/chacha-armv8.${ASM_EXT}
     test/trampoline-armv8.${ASM_EXT}
+    third_party/sike/asm/fp-armv8.${ASM_EXT}
   )
 endif()
 
@@ -136,6 +137,7 @@
     cipher_extra/chacha20_poly1305_x86_64.${ASM_EXT}
     hrss/asm/poly_rq_mul.S
     test/trampoline-x86_64.${ASM_EXT}
+    third_party/sike/asm/fp-x86_64.${ASM_EXT}
   )
 endif()
 
@@ -145,6 +147,8 @@
 perlasm(chacha/chacha-x86_64.${ASM_EXT} chacha/asm/chacha-x86_64.pl)
 perlasm(cipher_extra/aes128gcmsiv-x86_64.${ASM_EXT} cipher_extra/asm/aes128gcmsiv-x86_64.pl)
 perlasm(cipher_extra/chacha20_poly1305_x86_64.${ASM_EXT} cipher_extra/asm/chacha20_poly1305_x86_64.pl)
+perlasm(third_party/sike/asm/fp-x86_64.${ASM_EXT} ../third_party/sike/asm/fp-x86_64.pl)
+perlasm(third_party/sike/asm/fp-armv8.${ASM_EXT} ../third_party/sike/asm/fp-armv8.pl)
 perlasm(test/trampoline-armv4.${ASM_EXT} test/asm/trampoline-armv4.pl)
 perlasm(test/trampoline-armv8.${ASM_EXT} test/asm/trampoline-armv8.pl)
 perlasm(test/trampoline-x86.${ASM_EXT} test/asm/trampoline-x86.pl)
@@ -404,6 +408,11 @@
   x509v3/v3_sxnet.c
   x509v3/v3_utl.c
   ../third_party/fiat/curve25519.c
+  ../third_party/sike/fpx.c
+  ../third_party/sike/isogeny.c
+  ../third_party/sike/P503.c
+  ../third_party/sike/sike.c
+  ../third_party/sike/asm/fp_generic.c
 
   $<TARGET_OBJECTS:fipsmodule>
 
@@ -489,6 +498,7 @@
   x509/x509_time_test.cc
   x509v3/tab_test.cc
   x509v3/v3name_test.cc
+  ../third_party/sike/sike_test.cc
 
   $<TARGET_OBJECTS:crypto_test_data>
   $<TARGET_OBJECTS:boringssl_gtest_main>
diff --git a/third_party/sike/LICENSE b/third_party/sike/LICENSE
new file mode 100644
index 0000000..5cf7c8d
--- /dev/null
+++ b/third_party/sike/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE
diff --git a/third_party/sike/P503.c b/third_party/sike/P503.c
new file mode 100644
index 0000000..aecf623
--- /dev/null
+++ b/third_party/sike/P503.c
@@ -0,0 +1,70 @@
+/********************************************************************************************
+* SIDH: an efficient supersingular isogeny cryptography library
+*
+* Abstract: supersingular isogeny parameters and generation of functions for P503
+*********************************************************************************************/
+
+#include "utils.h"
+
+// Parameters for isogeny system "SIKEp503"
+const struct params_t p503 = {
+    .prime = {
+        0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xABFFFFFFFFFFFFFF,
+        0x13085BDA2211E7A0, 0x1B9BF6C87B7E7DAF, 0x6045C6BDDA77A4D0, 0x004066F541811E1E
+    },
+    .prime_p1 = {
+        0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0xAC00000000000000,
+        0x13085BDA2211E7A0, 0x1B9BF6C87B7E7DAF, 0x6045C6BDDA77A4D0, 0x004066F541811E1E
+    },
+    .prime_x2 = {
+        0xFFFFFFFFFFFFFFFE, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0x57FFFFFFFFFFFFFF,
+        0x2610B7B44423CF41, 0x3737ED90F6FCFB5E, 0xC08B8D7BB4EF49A0, 0x0080CDEA83023C3C
+    },
+    .A_gen = {
+        0xE7EF4AA786D855AF, 0xED5758F03EB34D3B, 0x09AE172535A86AA9, 0x237B9CC07D622723,
+        0xE3A284CBA4E7932D, 0x27481D9176C5E63F, 0x6A323FF55C6E71BF, 0x002ECC31A6FB8773,   // XPA0
+        0x64D02E4E90A620B8, 0xDAB8128537D4B9F1, 0x4BADF77B8A228F98, 0x0F5DBDF9D1FB7D1B,
+        0xBEC4DB288E1A0DCC, 0xE76A8665E80675DB, 0x6D6F252E12929463, 0x003188BD1463FACC,   // XPA1
+        0xB79D41025DE85D56, 0x0B867DA9DF169686, 0x740E5368021C827D, 0x20615D72157BF25C,
+        0xFF1590013C9B9F5B, 0xC884DCADE8C16CEA, 0xEBD05E53BF724E01, 0x0032FEF8FDA5748C,   // XQA0
+        0x12E2E849AA0A8006, 0x41CF47008635A1E8, 0x9CD720A70798AED7, 0x42A820B42FCF04CF,
+        0x7BF9BAD32AAE88B1, 0xF619127A54090BBE, 0x1CB10D8F56408EAA, 0x001D6B54C3C0EDEB,   // XRA0
+        0x34DB54931CBAAC36, 0x420A18CB8DD5F0C4, 0x32008C1A48C0F44D, 0x3B3BA772B1CFD44D,
+        0xA74B058FDAF13515, 0x095FC9CA7EEC17B4, 0x448E829D28F120F8, 0x00261EC3ED16A489    // XRA1
+    },
+    .B_gen = {
+        0x7EDE37F4FA0BC727, 0xF7F8EC5C8598941C, 0xD15519B516B5F5C8, 0xF6D5AC9B87A36282,
+        0x7B19F105B30E952E, 0x13BD8B2025B4EBEE, 0x7B96D27F4EC579A2, 0x00140850CAB7E5DE,   // XPB0
+        0x7764909DAE7B7B2D, 0x578ABB16284911AB, 0x76E2BFD146A6BF4D, 0x4824044B23AA02F0,
+        0x1105048912A321F3, 0xB8A2E482CF0F10C1, 0x42FF7D0BE2152085, 0x0018E599C5223352,   // XPB1
+        0x4256C520FB388820, 0x744FD7C3BAAF0A13, 0x4B6A2DDDB12CBCB8, 0xE46826E27F427DF8,
+        0xFE4A663CD505A61B, 0xD6B3A1BAF025C695, 0x7C3BB62B8FCC00BD, 0x003AFDDE4A35746C,   // XQB0
+        0x75601CD1E6C0DFCB, 0x1A9007239B58F93E, 0xC1F1BE80C62107AC, 0x7F513B898F29FF08,
+        0xEA0BEDFF43E1F7B2, 0x2C6D94018CBAE6D0, 0x3A430D31BCD84672, 0x000D26892ECCFE83,   // XRB0
+        0x1119D62AEA3007A1, 0xE3702AA4E04BAE1B, 0x9AB96F7D59F990E7, 0xF58440E8B43319C0,
+        0xAF8134BEE1489775, 0xE7F7774E905192AA, 0xF54AE09308E98039, 0x001EF7A041A86112    // XRB1
+    },
+    .mont_R2 = {
+        0x5289A0CF641D011F, 0x9B88257189FED2B9, 0xA3B365D58DC8F17A, 0x5BC57AB6EFF168EC,
+        0x9E51998BD84D4423, 0xBF8999CBAC3B5695, 0x46E9127BCE14CDB6, 0x003F6CFCE8B81771
+    },
+    .mont_one = {
+        0x00000000000003F9, 0x0000000000000000, 0x0000000000000000, 0xB400000000000000,
+        0x63CB1A6EA6DED2B4, 0x51689D8D667EB37D, 0x8ACD77C71AB24142, 0x0026FBAEC60F5953
+    },
+    .A_strat = {
+        61, 32, 16, 8, 4, 2, 1, 1, 2, 1, 1, 4, 2, 1, 1, 2, 1, 1, 8, 4, 2, 1, 1, 2, 1, 1,
+        4, 2, 1, 1, 2, 1, 1, 16, 8, 4, 2, 1, 1, 2, 1, 1, 4, 2, 1, 1, 2, 1, 1, 8, 4, 2, 1,
+        1, 2, 1, 1, 4, 2, 1, 1, 2, 1, 1, 29, 16, 8, 4, 2, 1, 1, 2, 1, 1, 4, 2, 1, 1, 2, 1,
+        1, 8, 4, 2, 1, 1, 2, 1, 1, 4, 2, 1, 1, 2, 1, 1, 13, 8, 4, 2, 1, 1, 2, 1, 1, 4, 2,
+        1, 1, 2, 1, 1, 5, 4, 2, 1, 1, 2, 1, 1, 2, 1, 1, 1
+    },
+    .B_strat = {
+        71, 38, 21, 13, 8, 4, 2, 1, 1, 2, 1, 1, 4, 2, 1, 1, 2, 1, 1, 5, 4, 2, 1, 1, 2, 1,
+        1, 2, 1, 1, 1, 9, 5, 3, 2, 1, 1, 1, 1, 2, 1, 1, 1, 4, 2, 1, 1, 1, 2, 1, 1, 17, 9,
+        5, 3, 2, 1, 1, 1, 1, 2, 1, 1, 1, 4, 2, 1, 1, 1, 2, 1, 1, 8, 4, 2, 1, 1, 1, 2, 1,
+        1, 4, 2, 1, 1, 2, 1, 1, 33, 17, 9, 5, 3, 2, 1, 1, 1, 1, 2, 1, 1, 1, 4, 2, 1, 1, 1,
+        2, 1, 1, 8, 4, 2, 1, 1, 1, 2, 1, 1, 4, 2, 1, 1, 2, 1, 1, 16, 8, 4, 2, 1, 1, 1, 2,
+        1, 1, 4, 2, 1, 1, 2, 1, 1, 8, 4, 2, 1, 1, 2, 1, 1, 4, 2, 1, 1, 2, 1, 1
+    }
+};
diff --git a/third_party/sike/asm/fp-armv8.pl b/third_party/sike/asm/fp-armv8.pl
new file mode 100644
index 0000000..a1728d1
--- /dev/null
+++ b/third_party/sike/asm/fp-armv8.pl
@@ -0,0 +1,968 @@
+#! /usr/bin/env perl
+#
+# April 2019
+#
+# Abstract: field arithmetic in aarch64 assembly for SIDH/p503
+
+$flavour = shift;
+$output  = shift;
+if ($flavour =~ /\./) { $output = $flavour; undef $flavour; }
+
+$0 =~ m/(.*[\/\\])[^\/\\]+$/; $dir=$1;
+( $xlate="${dir}arm-xlate.pl" and -f $xlate ) or
+( $xlate="${dir}../../../crypto/perlasm/arm-xlate.pl" and -f $xlate) or
+die "can't locate arm-xlate.pl";
+
+open OUT,"| \"$^X\" \"$xlate\" $flavour \"$output\"";
+*STDOUT=*OUT;
+
+$PREFIX="sike";
+
+$code.=<<___;
+.section  .rodata
+
+.Lp503p1_nz_s8:
+    .quad  0x085BDA2211E7A0AC, 0x9BF6C87B7E7DAF13
+    .quad  0x45C6BDDA77A4D01B, 0x4066F541811E1E60
+
+.Lp503x2:
+    .quad  0xFFFFFFFFFFFFFFFE, 0xFFFFFFFFFFFFFFFF
+    .quad  0x57FFFFFFFFFFFFFF, 0x2610B7B44423CF41
+    .quad  0x3737ED90F6FCFB5E, 0xC08B8D7BB4EF49A0
+    .quad  0x0080CDEA83023C3C
+
+.text
+___
+
+# C[0-2] = A[0] * B[0-1]
+sub mul64x128_comba_cut {
+    my ($A0,$B0,$B1,$C0,$C1,$C2,$T0,$T1)=@_;
+    my $body=<<___;
+        mul     $T1, $A0, $B0
+        umulh   $B0, $A0, $B0
+        adds    $C0, $C0, $C2
+        adc     $C1, $C1, xzr
+
+        mul     $T0, $A0, $B1
+        umulh   $B1, $A0, $B1
+        adds    $C0, $C0, $T1
+        adcs    $C1, $C1, $B0
+        adc     $C2, xzr, xzr
+
+        adds    $C1, $C1, $T0
+        adc     $C2, $C2, $B1
+___
+    return $body;
+}
+
+sub mul256_karatsuba_comba {
+    my ($M,$A0,$A1,$A2,$A3,$B0,$B1,$B2,$B3,$C0,$C1,$C2,$C3,$C4,$C5,$C6,$C7,$T0,$T1)=@_;
+    # (AH+AL) x (BH+BL), low part
+    my $mul_low=&mul64x128_comba_cut($A1, $C6, $T1, $C3, $C4, $C5, $C7, $A0);
+    # AL x BL
+    my $mul_albl=&mul64x128_comba_cut($A1, $B0, $B1, $C1, $T1, $C7, $C6, $A0);
+    # AH x BH
+    my $mul_ahbh=&mul64x128_comba_cut($A3, $B2, $B3, $A1, $C6, $B0, $B1, $A2);
+    my $body=<<___;
+        // A0-A1 <- AH + AL, T0 <- mask
+        adds    $A0, $A0, $A2
+        adcs    $A1, $A1, $A3
+        adc     $T0, xzr, xzr
+
+        // C6, T1 <- BH + BL, C7 <- mask
+        adds    $C6, $B0, $B2
+        adcs    $T1, $B1, $B3
+        adc     $C7, xzr, xzr
+
+        // C0-C1 <- masked (BH + BL)
+        sub     $C2, xzr, $T0
+        sub     $C3, xzr, $C7
+        and     $C0, $C6, $C2
+        and     $C1, $T1, $C2
+
+        // C4-C5 <- masked (AH + AL), T0 <- combined carry
+        and     $C4, $A0, $C3
+        and     $C5, $A1, $C3
+        mul     $C2, $A0, $C6
+        mul     $C3, $A0, $T1
+        and     $T0, $T0, $C7
+
+        // C0-C1, T0 <- (AH+AL) x (BH+BL), part 1
+        adds    $C0, $C4, $C0
+        umulh   $C4, $A0, $T1
+        adcs    $C1, $C5, $C1
+        umulh   $C5, $A0, $C6
+        adc     $T0, $T0, xzr
+
+        // C2-C5 <- (AH+AL) x (BH+BL), low part
+        $mul_low
+        ldp     $A0, $A1, [$M,#0]
+
+        // C2-C5, T0 <- (AH+AL) x (BH+BL), final part
+        adds    $C4, $C0, $C4
+        umulh   $C7, $A0, $B0
+        umulh   $T1, $A0, $B1
+        adcs    $C5, $C1, $C5
+        mul     $C0, $A0, $B0
+        mul     $C1, $A0, $B1
+        adc     $T0, $T0, xzr
+
+        // C0-C1, T1, C7 <- AL x BL
+        $mul_albl
+
+        // C2-C5, T0 <- (AH+AL) x (BH+BL) - ALxBL
+        mul     $A0, $A2, $B2
+        umulh   $B0, $A2, $B2
+        subs    $C2, $C2, $C0
+        sbcs    $C3, $C3, $C1
+        sbcs    $C4, $C4, $T1
+        mul     $A1, $A2, $B3
+        umulh   $C6, $A2, $B3
+        sbcs    $C5, $C5, $C7
+        sbc     $T0, $T0, xzr
+
+        // A0, A1, C6, B0 <- AH x BH
+        $mul_ahbh
+
+        // C2-C5, T0 <- (AH+AL) x (BH+BL) - ALxBL - AHxBH
+        subs    $C2, $C2, $A0
+        sbcs    $C3, $C3, $A1
+        sbcs    $C4, $C4, $C6
+        sbcs    $C5, $C5, $B0
+        sbc     $T0, $T0, xzr
+
+        adds    $C2, $C2, $T1
+        adcs    $C3, $C3, $C7
+        adcs    $C4, $C4, $A0
+        adcs    $C5, $C5, $A1
+        adcs    $C6, $T0, $C6
+        adc     $C7, $B0, xzr
+___
+    return $body;
+}
+
+# 512-bit integer multiplication using Karatsuba (two levels),
+# Comba (lower level).
+# Operation: c [x2] = a [x0] * b [x1]
+sub mul {
+    # (AH+AL) x (BH+BL), low part
+    my $mul_kc_low=&mul256_karatsuba_comba(
+        "x2",                                           # M0
+        "x3","x4","x5","x6",                            # A0-A3
+        "x11","x12","x13","x14",                        # B0-B3
+        "x8","x9","x10","x20","x21","x22","x23","x24",  # C0-C7
+        "x25","x26");                                   # TMP
+    # AL x BL
+    my $mul_albl=&mul256_karatsuba_comba(
+        "x0",                                           # M0
+        "x3","x4","x5","x6",                            # A0-A3
+        "x11","x12","x13","x14",                        # B0-B3
+        "x21","x22","x23","x24","x25","x26","x27","x28",# C0-C7
+        "x8","x9");                                     # TMP
+    # AH x BH
+    my $mul_ahbh=&mul256_karatsuba_comba(
+        "x0",                                           # M0
+        "x3","x4","x5","x6",                            # A0-A3
+        "x11","x12","x13","x14",                        # B0-B3
+        "x21","x22","x23","x24","x25","x26","x27","x28",# C0-C7
+        "x8","x9");                                     # TMP
+
+    my $body=<<___;
+        .global ${PREFIX}_mpmul
+        .align 4
+        ${PREFIX}_mpmul:
+        stp     x29, x30, [sp,#-96]!
+        add     x29, sp, #0
+        stp     x19, x20, [sp,#16]
+        stp     x21, x22, [sp,#32]
+        stp     x23, x24, [sp,#48]
+        stp     x25, x26, [sp,#64]
+        stp     x27, x28, [sp,#80]
+
+        ldp     x3, x4, [x0]
+        ldp     x5, x6, [x0,#16]
+        ldp     x7, x8, [x0,#32]
+        ldp     x9, x10, [x0,#48]
+        ldp     x11, x12, [x1,#0]
+        ldp     x13, x14, [x1,#16]
+        ldp     x15, x16, [x1,#32]
+        ldp     x17, x19, [x1,#48]
+
+        // x3-x7 <- AH + AL, x7 <- carry
+        adds    x3, x3, x7
+        adcs    x4, x4, x8
+        adcs    x5, x5, x9
+        adcs    x6, x6, x10
+        adc     x7, xzr, xzr
+
+        // x11-x14 <- BH + BL, x8 <- carry
+        adds    x11, x11, x15
+        adcs    x12, x12, x16
+        adcs    x13, x13, x17
+        adcs    x14, x14, x19
+        adc     x8, xzr, xzr
+
+        // x9 <- combined carry
+        and      x9, x7, x8
+        // x7-x8 <- mask
+        sub      x7, xzr, x7
+        sub      x8, xzr, x8
+
+
+        // x15-x19 <- masked (BH + BL)
+        and     x15, x11, x7
+        and     x16, x12, x7
+        and     x17, x13, x7
+        and     x19, x14, x7
+
+        // x20-x23 <- masked (AH + AL)
+        and     x20, x3, x8
+        and     x21, x4, x8
+        and     x22, x5, x8
+        and     x23, x6, x8
+
+        // x15-x19, x7 <- masked (AH+AL) + masked (BH+BL), step 1
+        adds    x15, x15, x20
+        adcs    x16, x16, x21
+        adcs    x17, x17, x22
+        adcs    x19, x19, x23
+        adc     x7, x9, xzr
+
+        // x8-x10,x20-x24 <- (AH+AL) x (BH+BL), low part
+        stp     x3, x4, [x2,#0]
+        $mul_kc_low
+
+        // x15-x19, x7 <- (AH+AL) x (BH+BL), final step
+        adds    x15, x15, x21
+        adcs    x16, x16, x22
+        adcs    x17, x17, x23
+        adcs    x19, x19, x24
+        adc     x7, x7, xzr
+
+        // Load AL
+        ldp     x3, x4, [x0]
+        ldp     x5, x6, [x0,#16]
+        // Load BL
+        ldp     x11, x12, [x1,#0]
+        ldp     x13, x14, [x1,#16]
+
+        // Temporarily store x8,x9 in x2
+        stp     x8,x9, [x2,#0]
+        // x21-x28 <- AL x BL
+        $mul_albl
+        // Restore x8,x9
+        ldp     x8,x9, [x2,#0]
+
+        // x8-x10,x20,x15-x17,x19 <- maskd (AH+AL) x (BH+BL) - ALxBL
+        subs    x8, x8, x21
+        sbcs    x9, x9, x22
+        sbcs    x10, x10, x23
+        sbcs    x20, x20, x24
+        sbcs    x15, x15, x25
+        sbcs    x16, x16, x26
+        sbcs    x17, x17, x27
+        sbcs    x19, x19, x28
+        sbc     x7, x7, xzr
+
+        // Store ALxBL, low
+        stp     x21, x22, [x2]
+        stp     x23, x24, [x2,#16]
+
+        // Load AH
+        ldp     x3, x4, [x0,#32]
+        ldp     x5, x6, [x0,#48]
+        // Load BH
+        ldp     x11, x12, [x1,#32]
+        ldp     x13, x14, [x1,#48]
+
+        adds    x8, x8, x25
+        adcs    x9, x9, x26
+        adcs    x10, x10, x27
+        adcs    x20, x20, x28
+        adc     x1, xzr, xzr
+
+        add     x0, x0, #32
+        // Temporarily store x8,x9 in x2
+        stp     x8,x9, [x2,#32]
+        // x21-x28 <- AH x BH
+        $mul_ahbh
+        // Restore x8,x9
+        ldp     x8,x9, [x2,#32]
+
+        neg     x1, x1
+
+        // x8-x10,x20,x15-x17,x19 <- (AH+AL) x (BH+BL) - ALxBL - AHxBH
+        subs    x8, x8, x21
+        sbcs    x9, x9, x22
+        sbcs    x10, x10, x23
+        sbcs    x20, x20, x24
+        sbcs    x15, x15, x25
+        sbcs    x16, x16, x26
+        sbcs    x17, x17, x27
+        sbcs    x19, x19, x28
+        sbc     x7, x7, xzr
+
+        // Store (AH+AL) x (BH+BL) - ALxBL - AHxBH, low
+        stp     x8, x9, [x2,#32]
+        stp     x10, x20, [x2,#48]
+
+        adds    x1, x1, #1
+        adcs    x15, x15, x21
+        adcs    x16, x16, x22
+        adcs    x17, x17, x23
+        adcs    x19, x19, x24
+        adcs    x25, x7, x25
+        adcs    x26, x26, xzr
+        adcs    x27, x27, xzr
+        adc     x28, x28, xzr
+
+        stp     x15, x16, [x2,#64]
+        stp     x17, x19, [x2,#80]
+        stp     x25, x26, [x2,#96]
+        stp     x27, x28, [x2,#112]
+
+        ldp     x19, x20, [x29,#16]
+        ldp     x21, x22, [x29,#32]
+        ldp     x23, x24, [x29,#48]
+        ldp     x25, x26, [x29,#64]
+        ldp     x27, x28, [x29,#80]
+        ldp     x29, x30, [sp],#96
+        ret
+___
+    return $body;
+}
+$code.=&mul();
+
+# Computes C0-C4 = (A0-A1) * (B0-B3)
+# Inputs remain intact
+sub mul128x256_comba {
+    my ($A0,$A1,$B0,$B1,$B2,$B3,$C0,$C1,$C2,$C3,$C4,$T0,$T1,$T2,$T3)=@_;
+    my $body=<<___;
+        mul     $T0, $A1, $B0
+        umulh   $T1, $A1, $B0
+        adds    $C0, $C0, $C2
+        adc     $C1, $C1, xzr
+
+        mul     $T2, $A0, $B2
+        umulh   $T3, $A0, $B2
+        adds    $C0, $C0, $T0
+        adcs    $C1, $C1, $T1
+        adc     $C2, xzr, xzr
+
+        mul     $T0, $A1, $B1
+        umulh   $T1, $A1, $B1
+        adds    $C1, $C1, $T2
+        adcs    $C2, $C2, $T3
+        adc     $C3, xzr, xzr
+
+        mul     $T2, $A0, $B3
+        umulh   $T3, $A0, $B3
+        adds    $C1, $C1, $T0
+        adcs    $C2, $C2, $T1
+        adc     $C3, $C3, xzr
+
+        mul     $T0, $A1, $B2
+        umulh   $T1, $A1, $B2
+        adds    $C2, $C2, $T2
+        adcs    $C3, $C3, $T3
+        adc     $C4, xzr, xzr
+
+        mul     $T2, $A1, $B3
+        umulh   $T3, $A1, $B3
+        adds    $C2, $C2, $T0
+        adcs    $C3, $C3, $T1
+        adc     $C4, $C4, xzr
+        adds    $C3, $C3, $T2
+        adc     $C4, $C4, $T3
+
+___
+    return $body;
+}
+
+#  Montgomery reduction
+#  Based on method described in Faz-Hernandez et al. https://eprint.iacr.org/2017/1015
+#  Operation: mc [x1] = ma [x0]
+#  NOTE: ma=mc is not allowed
+sub rdc {
+    my $mul01=&mul128x256_comba(
+        "x2","x3",                  # A0-A1
+        "x24","x25","x26","x27",    # B0-B3
+        "x5","x6","x7","x8","x9",   # C0-B4
+        "x1","x10","x11","x19");    # TMP
+    my $mul23=&mul128x256_comba(
+        "x2","x3",                  # A0-A1
+        "x24","x25","x26","x27",    # B0-B3
+        "x5","x6","x7","x8","x9",   # C0-C4
+        "x1","x10","x11","x19");    # TMP
+    my $mul45=&mul128x256_comba(
+        "x12","x13",                # A0-A1
+        "x24","x25","x26","x27",    # B0-B3
+        "x5","x6","x7","x8","x9",   # C0-C4
+        "x1","x10","x11","x19");    # TMP
+    my $mul67=&mul128x256_comba(
+        "x14","x15",                # A0-A1
+        "x24","x25","x26","x27",    # B0-B3
+        "x5","x6","x7","x8","x9",   # C0-C4
+        "x1","x10","x11","x19");    # TMP
+    my $body=<<___;
+    .global ${PREFIX}_fprdc
+    .align 4
+    ${PREFIX}_fprdc:
+        stp     x29, x30, [sp, #-112]!
+        add     x29, sp, #0
+        stp     x19, x20, [sp,#16]
+        stp     x21, x22, [sp,#32]
+        stp     x23, x24, [sp,#48]
+        stp     x25, x26, [sp,#64]
+        stp     x27, x28, [sp,#80]
+        str     x1, [sp,#96]
+
+        ldp     x2, x3, [x0,#0]       // a[0-1]
+
+        // Load the prime constant
+        adrp    x23, :pg_hi21:.Lp503p1_nz_s8
+        add     x23, x23, :lo12:.Lp503p1_nz_s8
+        ldp     x24, x25, [x23, #0]
+        ldp     x26, x27, [x23, #16]
+
+        // a[0-1] x .Lp503p1_nz_s8 --> result: x4:x9
+        mul     x4, x2, x24           // a[0] x .Lp503p1_nz_s8[0]
+        umulh   x7, x2, x24
+        mul     x5, x2, x25           // a[0] x .Lp503p1_nz_s8[1]
+        umulh   x6, x2, x25
+
+        $mul01
+
+        ldp      x2,  x3, [x0,#16]     // a[2]
+        ldp     x12, x13, [x0,#32]
+        ldp     x14, x15, [x0,#48]
+
+        orr     x10, xzr, x9, lsr #8
+        lsl     x9, x9, #56
+        orr     x9, x9, x8, lsr #8
+        lsl     x8, x8, #56
+        orr     x8, x8, x7, lsr #8
+        lsl     x7, x7, #56
+        orr     x7, x7, x6, lsr #8
+        lsl     x6, x6, #56
+        orr     x6, x6, x5, lsr #8
+        lsl     x5, x5, #56
+        orr     x5, x5, x4, lsr #8
+        lsl     x4, x4, #56
+
+        adds     x3, x4,  x3          // a[3]
+        adcs    x12, x5, x12          // a[4]
+        adcs    x13, x6, x13
+        adcs    x14, x7, x14
+        adcs    x15, x8, x15
+        ldp     x16, x17, [x0,#64]
+        ldp     x28, x30, [x0,#80]
+        mul     x4,  x2, x24          // a[2] x .Lp503p1_nz_s8[0]
+        umulh   x7,  x2, x24
+        adcs    x16, x9, x16
+        adcs    x17, x10, x17
+        adcs    x28, xzr, x28
+        adcs    x30, xzr, x30
+        ldp     x20, x21, [x0,#96]
+        ldp     x22, x23, [x0,#112]
+        mul     x5,  x2, x25          // a[2] x .Lp503p1_nz_s8[1]
+        umulh   x6,  x2, x25
+        adcs    x20, xzr, x20
+        adcs    x21, xzr, x21
+        adcs    x22, xzr, x22
+        adc     x23, xzr, x23
+
+        // a[2-3] x .Lp503p1_nz_s8 --> result: x4:x9
+        $mul23
+
+        orr     x10, xzr, x9, lsr #8
+        lsl     x9, x9, #56
+        orr     x9, x9, x8, lsr #8
+        lsl     x8, x8, #56
+        orr     x8, x8, x7, lsr #8
+        lsl     x7, x7, #56
+        orr     x7, x7, x6, lsr #8
+        lsl     x6, x6, #56
+        orr     x6, x6, x5, lsr #8
+        lsl     x5, x5, #56
+        orr     x5, x5, x4, lsr #8
+        lsl     x4, x4, #56
+
+        adds    x13, x4, x13          // a[5]
+        adcs    x14, x5, x14          // a[6]
+        adcs    x15, x6, x15
+        adcs    x16, x7, x16
+        mul     x4, x12, x24          // a[4] x .Lp503p1_nz_s8[0]
+        umulh   x7, x12, x24
+        adcs    x17, x8, x17
+        adcs    x28, x9, x28
+        adcs    x30, x10, x30
+        adcs    x20, xzr, x20
+        mul     x5, x12, x25          // a[4] x .Lp503p1_nz_s8[1]
+        umulh   x6, x12, x25
+        adcs    x21, xzr, x21
+        adcs    x22, xzr, x22
+        adc     x23, xzr, x23
+
+        // a[4-5] x .Lp503p1_nz_s8 --> result: x4:x9
+        $mul45
+
+        orr     x10, xzr, x9, lsr #8
+        lsl     x9, x9, #56
+        orr     x9, x9, x8, lsr #8
+        lsl     x8, x8, #56
+        orr     x8, x8, x7, lsr #8
+        lsl     x7, x7, #56
+        orr     x7, x7, x6, lsr #8
+        lsl     x6, x6, #56
+        orr     x6, x6, x5, lsr #8
+        lsl     x5, x5, #56
+        orr     x5, x5, x4, lsr #8
+        lsl     x4, x4, #56
+
+        adds    x15, x4, x15          // a[7]
+        adcs    x16, x5, x16          // a[8]
+        adcs    x17, x6, x17
+        adcs    x28, x7, x28
+        mul     x4, x14, x24          // a[6] x .Lp503p1_nz_s8[0]
+        umulh   x7, x14, x24
+        adcs    x30, x8, x30
+        adcs    x20, x9, x20
+        adcs    x21, x10, x21
+        mul     x5, x14, x25          // a[6] x .Lp503p1_nz_s8[1]
+        umulh   x6, x14, x25
+        adcs    x22, xzr, x22
+        adc     x23, xzr, x23
+
+        // a[6-7] x .Lp503p1_nz_s8 --> result: x4:x9
+        $mul67
+
+        orr     x10, xzr, x9, lsr #8
+        lsl     x9, x9, #56
+        orr     x9, x9, x8, lsr #8
+        lsl     x8, x8, #56
+        orr     x8, x8, x7, lsr #8
+        lsl     x7, x7, #56
+        orr     x7, x7, x6, lsr #8
+        lsl     x6, x6, #56
+        orr     x6, x6, x5, lsr #8
+        lsl     x5, x5, #56
+        orr     x5, x5, x4, lsr #8
+        lsl     x4, x4, #56
+
+        adds    x17, x4, x17
+        adcs    x28, x5, x28
+        ldr     x1, [sp,#96]
+        adcs    x30, x6, x30
+        adcs    x20, x7, x20
+        stp     x16, x17, [x1,#0]     // Final result
+        stp     x28, x30, [x1,#16]
+        adcs    x21, x8, x21
+        adcs    x22, x9, x22
+        adc     x23, x10, x23
+        stp     x20, x21, [x1,#32]
+        stp     x22, x23, [x1,#48]
+
+        ldp     x19, x20, [x29,#16]
+        ldp     x21, x22, [x29,#32]
+        ldp     x23, x24, [x29,#48]
+        ldp     x25, x26, [x29,#64]
+        ldp     x27, x28, [x29,#80]
+        ldp     x29, x30, [sp],#112
+        ret
+
+___
+}
+
+$code.=&rdc();
+
+
+#  Field addition
+#  Operation: c [x2] = a [x0] + b [x1]
+$code.=<<___;
+    .global ${PREFIX}_fpadd
+    .align 4
+    ${PREFIX}_fpadd:
+        stp     x29,x30, [sp,#-16]!
+        add     x29, sp, #0
+
+        ldp     x3, x4,   [x0,#0]
+        ldp     x5, x6,   [x0,#16]
+        ldp     x11, x12, [x1,#0]
+        ldp     x13, x14, [x1,#16]
+
+        // Add a + b
+        adds    x3, x3, x11
+        adcs    x4, x4, x12
+        adcs    x5, x5, x13
+        adcs    x6, x6, x14
+        ldp     x7, x8,   [x0,#32]
+        ldp     x9, x10,  [x0,#48]
+        ldp     x11, x12, [x1,#32]
+        ldp     x13, x14, [x1,#48]
+        adcs    x7, x7, x11
+        adcs    x8, x8, x12
+        adcs    x9, x9, x13
+        adc     x10, x10, x14
+
+        //  Subtract 2xp503
+        adrp    x17, :pg_hi21:.Lp503x2
+        add     x17, x17, :lo12:.Lp503x2
+        ldp     x11, x12, [x17, #0]
+        ldp     x13, x14, [x17, #16]
+        subs    x3, x3, x11
+        sbcs    x4, x4, x12
+        sbcs    x5, x5, x12
+        sbcs    x6, x6, x13
+        sbcs    x7, x7, x14
+
+        ldp     x15, x16, [x17, #32]
+        ldr     x17,      [x17, #48]
+        sbcs    x8, x8, x15
+        sbcs    x9, x9, x16
+        sbcs    x10, x10, x17
+        sbc     x0, xzr, xzr    // x0 can be reused now
+
+        // Add 2xp503 anded with the mask in x0
+        and     x11, x11, x0
+        and     x12, x12, x0
+        and     x13, x13, x0
+        and     x14, x14, x0
+        and     x15, x15, x0
+        and     x16, x16, x0
+        and     x17, x17, x0
+
+        adds    x3, x3, x11
+        adcs    x4, x4, x12
+        adcs    x5, x5, x12
+        adcs    x6, x6, x13
+        adcs    x7, x7, x14
+        adcs    x8, x8, x15
+        adcs    x9, x9, x16
+        adc     x10, x10, x17
+
+        stp     x3, x4,  [x2,#0]
+        stp     x5, x6,  [x2,#16]
+        stp     x7, x8,  [x2,#32]
+        stp     x9, x10, [x2,#48]
+
+        ldp     x29, x30, [sp],#16
+        ret
+
+___
+
+#  Field subtraction
+#  Operation: c [x2] = a [x0] - b [x1]
+$code.=<<___;
+    .global ${PREFIX}_fpsub
+    .align 4
+    ${PREFIX}_fpsub:
+        stp     x29, x30, [sp,#-16]!
+        add     x29, sp, #0
+
+        ldp     x3, x4,   [x0,#0]
+        ldp     x5, x6,   [x0,#16]
+        ldp     x11, x12, [x1,#0]
+        ldp     x13, x14, [x1,#16]
+
+        // Subtract a - b
+        subs    x3, x3, x11
+        sbcs    x4, x4, x12
+        sbcs    x5, x5, x13
+        sbcs    x6, x6, x14
+        ldp     x7, x8,   [x0,#32]
+        ldp     x11, x12, [x1,#32]
+        sbcs    x7, x7, x11
+        sbcs    x8, x8, x12
+        ldp     x9, x10,  [x0,#48]
+        ldp     x11, x12, [x1,#48]
+        sbcs    x9, x9, x11
+        sbcs    x10, x10, x12
+        sbc     x17, xzr, xzr
+
+        // Add 2xp503 anded with the mask in x17
+        adrp    x16, :pg_hi21:.Lp503x2
+        add     x16, x16, :lo12:.Lp503x2
+
+        // First half
+        ldp     x11, x12, [x16, #0]
+        ldp     x13, x14, [x16, #16]
+        and     x11, x11, x17
+        and     x12, x12, x17
+        and     x13, x13, x17
+        adds    x3, x3, x11
+        adcs    x4, x4, x12
+        adcs    x5, x5, x12
+        adcs    x6, x6, x13
+        stp     x3, x4,  [x2,#0]
+        stp     x5, x6,  [x2,#16]
+
+        // Second half
+        ldp     x11, x12, [x16, #32]
+        ldr     x13,      [x16, #48]
+        and     x14, x14, x17
+        and     x11, x11, x17
+        and     x12, x12, x17
+        and     x13, x13, x17
+        adcs    x7, x7, x14
+        adcs    x8, x8, x11
+        adcs    x9, x9, x12
+        adc     x10, x10, x13
+        stp     x7, x8,  [x2,#32]
+        stp     x9, x10, [x2,#48]
+
+        ldp     x29, x30, [sp],#16
+        ret
+___
+
+# 503-bit multiprecision addition
+# Operation: c [x2] = a [x0] + b [x1]
+$code.=<<___;
+    .global ${PREFIX}_mpadd_asm
+    .align 4
+    ${PREFIX}_mpadd_asm:
+        stp     x29, x30, [sp,#-16]!
+        add     x29, sp, #0
+
+        ldp     x3, x4,   [x0,#0]
+        ldp     x5, x6,   [x0,#16]
+        ldp     x11, x12, [x1,#0]
+        ldp     x13, x14, [x1,#16]
+
+        adds    x3, x3, x11
+        adcs    x4, x4, x12
+        adcs    x5, x5, x13
+        adcs    x6, x6, x14
+        ldp     x7, x8,   [x0,#32]
+        ldp     x9, x10,  [x0,#48]
+        ldp     x11, x12, [x1,#32]
+        ldp     x13, x14, [x1,#48]
+        adcs    x7, x7, x11
+        adcs    x8, x8, x12
+        adcs    x9, x9, x13
+        adc     x10, x10, x14
+
+        stp     x3, x4,   [x2,#0]
+        stp     x5, x6,   [x2,#16]
+        stp     x7, x8,   [x2,#32]
+        stp     x9, x10,  [x2,#48]
+
+        ldp     x29, x30, [sp],#16
+        ret
+___
+
+
+# 2x503-bit multiprecision addition
+# Operation: c [x2] = a [x0] + b [x1]
+$code.=<<___;
+    .global ${PREFIX}_mpadd503x2_asm
+    .align 4
+    ${PREFIX}_mpadd503x2_asm:
+        stp     x29, x30, [sp,#-16]!
+        add     x29, sp, #0
+
+        ldp     x3, x4,   [x0,#0]
+        ldp     x5, x6,   [x0,#16]
+        ldp     x11, x12, [x1,#0]
+        ldp     x13, x14, [x1,#16]
+        adds    x3, x3, x11
+        adcs    x4, x4, x12
+        adcs    x5, x5, x13
+        adcs    x6, x6, x14
+        ldp     x7, x8,   [x0,#32]
+        ldp     x9, x10,  [x0,#48]
+        ldp     x11, x12, [x1,#32]
+        ldp     x13, x14, [x1,#48]
+        adcs    x7, x7, x11
+        adcs    x8, x8, x12
+        adcs    x9, x9, x13
+        adcs    x10, x10, x14
+
+        stp     x3, x4,   [x2,#0]
+        stp     x5, x6,   [x2,#16]
+        stp     x7, x8,   [x2,#32]
+        stp     x9, x10,  [x2,#48]
+
+        ldp     x3, x4,   [x0,#64]
+        ldp     x5, x6,   [x0,#80]
+        ldp     x11, x12, [x1,#64]
+        ldp     x13, x14, [x1,#80]
+        adcs    x3, x3, x11
+        adcs    x4, x4, x12
+        adcs    x5, x5, x13
+        adcs    x6, x6, x14
+        ldp     x7, x8,   [x0,#96]
+        ldp     x9, x10,  [x0,#112]
+        ldp     x11, x12, [x1,#96]
+        ldp     x13, x14, [x1,#112]
+        adcs    x7, x7, x11
+        adcs    x8, x8, x12
+        adcs    x9, x9, x13
+        adc     x10, x10, x14
+
+        stp     x3, x4,   [x2,#64]
+        stp     x5, x6,   [x2,#80]
+        stp     x7, x8,   [x2,#96]
+        stp     x9, x10,  [x2,#112]
+
+        ldp     x29, x30, [sp],#16
+        ret
+___
+
+
+
+# 2x503-bit multiprecision subtraction
+# Operation: c [x2] = a [x0] - b [x1].
+# Returns borrow mask
+$code.=<<___;
+    .global ${PREFIX}_mpsubx2_asm
+    .align 4
+    ${PREFIX}_mpsubx2_asm:
+        stp     x29, x30, [sp,#-16]!
+        add     x29, sp, #0
+
+        ldp     x3, x4,   [x0,#0]
+        ldp     x5, x6,   [x0,#16]
+        ldp     x11, x12, [x1,#0]
+        ldp     x13, x14, [x1,#16]
+        subs    x3, x3, x11
+        sbcs    x4, x4, x12
+        sbcs    x5, x5, x13
+        sbcs    x6, x6, x14
+        ldp     x7, x8,   [x0,#32]
+        ldp     x9, x10,  [x0,#48]
+        ldp     x11, x12, [x1,#32]
+        ldp     x13, x14, [x1,#48]
+        sbcs    x7, x7, x11
+        sbcs    x8, x8, x12
+        sbcs    x9, x9, x13
+        sbcs    x10, x10, x14
+
+        stp     x3, x4,   [x2,#0]
+        stp     x5, x6,   [x2,#16]
+        stp     x7, x8,   [x2,#32]
+        stp     x9, x10,  [x2,#48]
+
+        ldp     x3, x4,   [x0,#64]
+        ldp     x5, x6,   [x0,#80]
+        ldp     x11, x12, [x1,#64]
+        ldp     x13, x14, [x1,#80]
+        sbcs    x3, x3, x11
+        sbcs    x4, x4, x12
+        sbcs    x5, x5, x13
+        sbcs    x6, x6, x14
+        ldp     x7, x8,   [x0,#96]
+        ldp     x9, x10,  [x0,#112]
+        ldp     x11, x12, [x1,#96]
+        ldp     x13, x14, [x1,#112]
+        sbcs    x7, x7, x11
+        sbcs    x8, x8, x12
+        sbcs    x9, x9, x13
+        sbcs    x10, x10, x14
+        sbc     x0, xzr, xzr
+
+        stp     x3, x4,   [x2,#64]
+        stp     x5, x6,   [x2,#80]
+        stp     x7, x8,   [x2,#96]
+        stp     x9, x10,  [x2,#112]
+
+        ldp     x29, x30, [sp],#16
+        ret
+___
+
+
+# Double 2x503-bit multiprecision subtraction
+# Operation: c [x2] = c [x2] - a [x0] - b [x1]
+$code.=<<___;
+    .global ${PREFIX}_mpdblsubx2_asm
+    .align 4
+    ${PREFIX}_mpdblsubx2_asm:
+        stp     x29, x30, [sp, #-64]!
+        add     x29, sp, #0
+
+        stp     x20, x21, [sp, #16]
+        stp     x22, x23, [sp, #32]
+        str     x24,      [sp, #48]
+
+        ldp     x3, x4,   [x2,#0]
+        ldp     x5, x6,   [x2,#16]
+        ldp     x7, x8,   [x2,#32]
+        ldp     x9, x10,  [x2,#48]
+        ldp     x11, x12, [x2,#64]
+        ldp     x13, x14, [x2,#80]
+        ldp     x15, x16, [x2,#96]
+        ldp     x17, x24, [x2,#112]
+
+        ldp     x20, x21, [x0,#0]
+        ldp     x22, x23, [x0,#16]
+        subs    x3, x3, x20
+        sbcs    x4, x4, x21
+        sbcs    x5, x5, x22
+        sbcs    x6, x6, x23
+        ldp     x20, x21, [x0,#32]
+        ldp     x22, x23, [x0,#48]
+        sbcs    x7, x7, x20
+        sbcs    x8, x8, x21
+        sbcs    x9, x9, x22
+        sbcs    x10, x10, x23
+        ldp     x20, x21, [x0,#64]
+        ldp     x22, x23, [x0,#80]
+        sbcs    x11, x11, x20
+        sbcs    x12, x12, x21
+        sbcs    x13, x13, x22
+        sbcs    x14, x14, x23
+        ldp     x20, x21, [x0,#96]
+        ldp     x22, x23, [x0,#112]
+        sbcs    x15, x15, x20
+        sbcs    x16, x16, x21
+        sbcs    x17, x17, x22
+        sbc     x24, x24, x23
+
+        ldp     x20, x21, [x1,#0]
+        ldp     x22, x23, [x1,#16]
+        subs    x3, x3, x20
+        sbcs    x4, x4, x21
+        sbcs    x5, x5, x22
+        sbcs    x6, x6, x23
+        ldp     x20, x21, [x1,#32]
+        ldp     x22, x23, [x1,#48]
+        sbcs    x7, x7, x20
+        sbcs    x8, x8, x21
+        sbcs    x9, x9, x22
+        sbcs    x10, x10, x23
+        ldp     x20, x21, [x1,#64]
+        ldp     x22, x23, [x1,#80]
+        sbcs    x11, x11, x20
+        sbcs    x12, x12, x21
+        sbcs    x13, x13, x22
+        sbcs    x14, x14, x23
+        ldp     x20, x21, [x1,#96]
+        ldp     x22, x23, [x1,#112]
+        sbcs    x15, x15, x20
+        sbcs    x16, x16, x21
+        sbcs    x17, x17, x22
+        sbc     x24, x24, x23
+
+        stp     x3, x4,   [x2,#0]
+        stp     x5, x6,   [x2,#16]
+        stp     x7, x8,   [x2,#32]
+        stp     x9, x10,  [x2,#48]
+        stp     x11, x12, [x2,#64]
+        stp     x13, x14, [x2,#80]
+        stp     x15, x16, [x2,#96]
+        stp     x17, x24, [x2,#112]
+
+        ldp     x20, x21, [x29,#16]
+        ldp     x22, x23, [x29,#32]
+        ldr     x24,      [x29,#48]
+
+        ldp     x29, x30, [sp],#64
+        ret
+___
+
+foreach (split("\n",$code)) {
+  s/\`([^\`]*)\`/eval($1)/ge;
+  print $_,"\n";
+}
+
+close STDOUT;
diff --git a/third_party/sike/asm/fp-x86_64.pl b/third_party/sike/asm/fp-x86_64.pl
new file mode 100755
index 0000000..c093c20
--- /dev/null
+++ b/third_party/sike/asm/fp-x86_64.pl
@@ -0,0 +1,1850 @@
+#! /usr/bin/env perl
+#
+# April 2019
+#
+# Abstract: field arithmetic in x64 assembly for SIDH/p503
+
+$flavour = shift;
+$output  = shift;
+if ($flavour =~ /\./) { $output = $flavour; undef $flavour; }
+
+$0 =~ m/(.*[\/\\])[^\/\\]+$/; $dir=$1;
+( $xlate="${dir}x86_64-xlate.pl" and -f $xlate ) or
+( $xlate="${dir}../../../crypto/perlasm/x86_64-xlate.pl" and -f $xlate) or
+die "can't locate x86_64-xlate.pl";
+
+open OUT,"| \"$^X\" \"$xlate\" $flavour \"$output\"";
+*STDOUT=*OUT;
+
+$PREFIX="sike";
+$bmi2_adx = 1;
+
+$code.=<<___;
+.text
+
+# p503 x 2
+.Lp503x2:
+.quad   0xFFFFFFFFFFFFFFFE
+.quad   0xFFFFFFFFFFFFFFFF
+.quad   0x57FFFFFFFFFFFFFF
+.quad   0x2610B7B44423CF41
+.quad   0x3737ED90F6FCFB5E
+.quad   0xC08B8D7BB4EF49A0
+.quad   0x0080CDEA83023C3C
+
+# p503 + 1
+.Lp503p1:
+.quad   0xAC00000000000000
+.quad   0x13085BDA2211E7A0
+.quad   0x1B9BF6C87B7E7DAF
+.quad   0x6045C6BDDA77A4D0
+.quad   0x004066F541811E1E
+
+.Lp503p1_nz:
+.quad    0xAC00000000000000
+.quad    0x13085BDA2211E7A0
+.quad    0x1B9BF6C87B7E7DAF
+.quad    0x6045C6BDDA77A4D0
+.quad    0x004066F541811E1E
+
+.extern OPENSSL_ia32cap_P
+.hidden OPENSSL_ia32cap_P
+
+___
+
+# Performs schoolbook multiplication of 128-bit with 320-bit
+# number. Uses MULX, ADOX, ADCX instruction.
+sub mul128x320_school {
+  my ($idxM0,$M0,$M1,$T0,$T1,$T2,$T3,$T4,$T5,$T6,$T7,$T8,$T9)=@_;
+  my ($MUL0,$MUL8)=map("$idxM0+$_(%$M0)", (0,8));
+  my $body.=<<___;
+    mov    $MUL0, %rdx
+    mulx   0+$M1, %$T0, %$T1       # T0 <- C0_final
+    mulx   8+$M1, %$T4, %$T2
+
+    xor    %rax, %rax
+    mulx   16+$M1, %$T5, %$T3
+    adox   %$T4, %$T1
+    adox   %$T5, %$T2
+    mulx   24+$M1, %$T7, %$T4
+    adox   %$T7, %$T3
+    mulx   32+$M1, %$T6, %$T5
+    adox   %$T6, %$T4
+    adox   %rax, %$T5
+
+    mov    $MUL8, %rdx
+    mulx   0+$M1, %$T6, %$T7
+    adcx   %$T6, %$T1               # T1 <- C1_final
+    adcx   %$T7, %$T2
+    mulx   8+$M1, %$T8, %$T6
+    adcx   %$T6, %$T3
+    mulx   16+$M1, %$T7, %$T9
+    adcx   %$T9, %$T4
+    mulx   24+$M1, %$T9, %$T6
+    adcx   %$T6, %$T5
+    mulx   32+$M1, %rdx, %$T6
+    adcx   %rax, %$T6
+
+    xor    %rax, %rax
+    adox   %$T8, %$T2
+    adox   %$T7, %$T3
+    adox   %$T9, %$T4
+    adox   %rdx, %$T5
+    adox   %rax, %$T6
+
+___
+  return $body;
+}
+
+# Compute z = x + y (mod p).
+# Operation: c [rdx] = a [rdi] + b [rsi]
+$code.=<<___;
+.globl  ${PREFIX}_fpadd
+.type   ${PREFIX}_fpadd,\@function,3
+${PREFIX}_fpadd:
+.cfi_startproc
+  push %r12
+.cfi_adjust_cfa_offset  8
+.cfi_offset r12, -16
+  push %r13
+.cfi_adjust_cfa_offset  8
+.cfi_offset r13, -24
+  push %r14
+.cfi_adjust_cfa_offset  8
+.cfi_offset r14, -32
+  push %r15
+.cfi_adjust_cfa_offset  8
+.cfi_offset r15, -40
+
+  xor  %rax, %rax
+
+  mov  0x0(%rdi),  %r8
+  mov  0x8(%rdi),  %r9
+  mov 0x10(%rdi), %r10
+  mov 0x18(%rdi), %r11
+  mov 0x20(%rdi), %r12
+  mov 0x28(%rdi), %r13
+  mov 0x30(%rdi), %r14
+  mov 0x38(%rdi), %r15
+
+  add  0x0(%rsi),  %r8
+  adc  0x8(%rsi),  %r9
+  adc 0x10(%rsi), %r10
+  adc 0x18(%rsi), %r11
+  adc 0x20(%rsi), %r12
+  adc 0x28(%rsi), %r13
+  adc 0x30(%rsi), %r14
+  adc 0x38(%rsi), %r15
+
+  mov .Lp503x2(%rip), %rcx;
+  sub %rcx, %r8
+  mov 8+.Lp503x2(%rip), %rcx;
+  sbb %rcx, %r9
+  sbb %rcx, %r10
+  mov 16+.Lp503x2(%rip), %rcx;
+  sbb %rcx, %r11
+  mov 24+.Lp503x2(%rip), %rcx;
+  sbb %rcx, %r12
+  mov 32+.Lp503x2(%rip), %rcx;
+  sbb %rcx, %r13
+  mov 40+.Lp503x2(%rip), %rcx;
+  sbb %rcx, %r14
+  mov 48+.Lp503x2(%rip), %rcx;
+  sbb %rcx, %r15
+  sbb \$0, %rax
+
+  mov .Lp503x2(%rip), %rdi
+  and %rax, %rdi
+  mov 8+.Lp503x2(%rip), %rsi
+  and %rax, %rsi
+  mov 16+.Lp503x2(%rip), %rcx
+  and %rax, %rcx
+
+  add %rdi, %r8
+  mov %r8, 0x0(%rdx)
+  adc %rsi, %r9
+  mov %r9, 0x8(%rdx)
+  adc %rsi, %r10
+  mov %r10, 0x10(%rdx)
+  adc %rcx, %r11
+  mov %r11, 0x18(%rdx)
+
+  setc   %cl
+
+  mov 24+.Lp503x2(%rip), %r8
+  and %rax, %r8
+  mov 32+.Lp503x2(%rip), %r9
+  and %rax, %r9
+  mov 40+.Lp503x2(%rip), %r10
+  and %rax, %r10
+  mov 48+.Lp503x2(%rip), %r11
+  and %rax, %r11
+
+  bt    \$0, %rcx
+
+  adc  %r8, %r12
+  mov %r12, 0x20(%rdx)
+  adc  %r9, %r13
+  mov %r13, 0x28(%rdx)
+  adc %r10, %r14
+  mov %r14, 0x30(%rdx)
+  adc %r11, %r15
+  mov %r15, 0x38(%rdx)
+
+  pop %r15
+.cfi_adjust_cfa_offset  -8
+  pop %r14
+.cfi_adjust_cfa_offset  -8
+  pop %r13
+.cfi_adjust_cfa_offset  -8
+  pop %r12
+.cfi_adjust_cfa_offset  -8
+  ret
+.cfi_endproc
+___
+
+
+
+# Loads data to XMM0 and XMM1 and
+# conditionaly swaps depending on XMM3
+sub cswap_block16() {
+  my $idx = shift;
+  $idx *= 16;
+  ("
+    movdqu   $idx(%rdi), %xmm0
+    movdqu   $idx(%rsi), %xmm1
+    movdqa   %xmm1, %xmm2
+    pxor     %xmm0, %xmm2
+    pand     %xmm3, %xmm2
+    pxor     %xmm2, %xmm0
+    pxor     %xmm2, %xmm1
+    movdqu   %xmm0, $idx(%rdi)
+    movdqu   %xmm1, $idx(%rsi)
+  ");
+}
+
+# Conditionally swaps bits in x and y in constant time.
+# mask indicates bits to be swapped (set bits are swapped)
+# Operation: [rdi] <-> [rsi] if rdx==1
+sub cswap {
+  # P[0].X with Q[0].X
+  foreach ( 0.. 3){$BLOCKS.=eval "&cswap_block16($_)";}
+  # P[0].Z with Q[0].Z
+  foreach ( 4.. 7){$BLOCKS.=eval "&cswap_block16($_)";}
+  # P[1].X with Q[1].X
+  foreach ( 8..11){$BLOCKS.=eval "&cswap_block16($_)";}
+  # P[1].Z with Q[1].Z
+  foreach (12..15){$BLOCKS.=eval "&cswap_block16($_)";}
+
+  my $body =<<___;
+.globl  ${PREFIX}_cswap_asm
+.type   ${PREFIX}_cswap_asm,\@function,3
+${PREFIX}_cswap_asm:
+  # Fill XMM3. After this step first half of XMM3 is
+  # just zeros and second half is whatever in RDX
+  mov   %rdx, %xmm3
+
+  # Copy lower double word everywhere else. So that
+  # XMM3=RDX|RDX. As RDX has either all bits set
+  # or non result will be that XMM3 has also either
+  # all bits set or non of them. 68 = 01000100b
+  pshufd  \$68, %xmm3, %xmm3
+  $BLOCKS
+  ret
+___
+  ($body)
+}
+$code.=&cswap();
+
+# Field subtraction
+# Operation: c [rdx] = a [rdi] - b [rsi]
+$code.=<<___;
+.globl  ${PREFIX}_fpsub
+.type   ${PREFIX}_fpsub,\@function,3
+${PREFIX}_fpsub:
+.cfi_startproc
+  push   %r12
+.cfi_adjust_cfa_offset  8
+.cfi_offset r12, -16
+  push   %r13
+.cfi_adjust_cfa_offset  8
+.cfi_offset r13, -24
+  push   %r14
+.cfi_adjust_cfa_offset  8
+.cfi_offset r14, -32
+  push   %r15
+.cfi_adjust_cfa_offset  8
+.cfi_offset r15, -40
+
+  xor %rax, %rax
+
+  mov  0x0(%rdi), %r8
+  mov  0x8(%rdi), %r9
+  mov 0x10(%rdi), %r10
+  mov 0x18(%rdi), %r11
+  mov 0x20(%rdi), %r12
+  mov 0x28(%rdi), %r13
+  mov 0x30(%rdi), %r14
+  mov 0x38(%rdi), %r15
+
+  sub  0x0(%rsi), %r8
+  sbb  0x8(%rsi), %r9
+  sbb 0x10(%rsi), %r10
+  sbb 0x18(%rsi), %r11
+  sbb 0x20(%rsi), %r12
+  sbb 0x28(%rsi), %r13
+  sbb 0x30(%rsi), %r14
+  sbb 0x38(%rsi), %r15
+  sbb \$0x0, %rax
+
+  mov .Lp503x2(%rip), %rdi
+  and %rax, %rdi
+  mov 0x8+.Lp503x2(%rip), %rsi
+  and %rax, %rsi
+  mov 0x10+.Lp503x2(%rip), %rcx
+  and %rax, %rcx
+
+  add %rdi,        %r8
+  adc %rsi,        %r9
+  adc %rsi,       %r10
+  adc %rcx,       %r11
+  mov %r8,   0x0(%rdx)
+  mov %r9,   0x8(%rdx)
+  mov %r10, 0x10(%rdx)
+  mov %r11, 0x18(%rdx)
+
+  setc %cl
+
+  mov 0x18+.Lp503x2(%rip),  %r8
+  and %rax,  %r8
+  mov 0x20+.Lp503x2(%rip),  %r9
+  and %rax,  %r9
+  mov 0x28+.Lp503x2(%rip), %r10
+  and %rax, %r10
+  mov 0x30+.Lp503x2(%rip), %r11
+  and %rax, %r11
+
+  bt \$0x0, %rcx
+
+  adc %r8, %r12
+  adc %r9, %r13
+  adc %r10, %r14
+  adc %r11, %r15
+  mov %r12, 0x20(%rdx)
+  mov %r13, 0x28(%rdx)
+  mov %r14, 0x30(%rdx)
+  mov %r15, 0x38(%rdx)
+
+  pop %r15
+.cfi_adjust_cfa_offset  -8
+  pop %r14
+.cfi_adjust_cfa_offset  -8
+  pop %r13
+.cfi_adjust_cfa_offset  -8
+  pop %r12
+.cfi_adjust_cfa_offset  -8
+  ret
+.cfi_endproc
+___
+
+#  503-bit multiprecision addition
+#  Operation: c [rdx] = a [rdi] + b [rsi]
+$code.=<<___;
+.globl  ${PREFIX}_mpadd_asm
+.type   ${PREFIX}_mpadd_asm,\@function,3
+${PREFIX}_mpadd_asm:
+.cfi_startproc
+  mov  0x0(%rdi), %r8
+  mov  0x8(%rdi), %r9
+  mov 0x10(%rdi), %r10
+  mov 0x18(%rdi), %r11
+  add  0x0(%rsi), %r8
+  adc  0x8(%rsi), %r9
+  adc 0x10(%rsi), %r10
+  adc 0x18(%rsi), %r11
+  mov %r8,   0x0(%rdx)
+  mov %r9,   0x8(%rdx)
+  mov %r10, 0x10(%rdx)
+  mov %r11, 0x18(%rdx)
+
+  mov 0x20(%rdi), %r8
+  mov 0x28(%rdi), %r9
+  mov 0x30(%rdi), %r10
+  mov 0x38(%rdi), %r11
+  adc 0x20(%rsi), %r8
+  adc 0x28(%rsi), %r9
+  adc 0x30(%rsi), %r10
+  adc 0x38(%rsi), %r11
+  mov %r8,  0x20(%rdx)
+  mov %r9,  0x28(%rdx)
+  mov %r10, 0x30(%rdx)
+  mov %r11, 0x38(%rdx)
+  ret
+.cfi_endproc
+___
+
+#  2x503-bit multiprecision subtraction
+#  Operation: c [rdx] = a [rdi] - b [rsi].
+#  Returns borrow mask
+$code.=<<___;
+.globl  ${PREFIX}_mpsubx2_asm
+.type   ${PREFIX}_mpsubx2_asm,\@function,3
+${PREFIX}_mpsubx2_asm:
+.cfi_startproc
+  xor %rax, %rax
+
+  mov  0x0(%rdi), %r8
+  mov  0x8(%rdi), %r9
+  mov 0x10(%rdi), %r10
+  mov 0x18(%rdi), %r11
+  mov 0x20(%rdi), %rcx
+  sub  0x0(%rsi), %r8
+  sbb  0x8(%rsi), %r9
+  sbb 0x10(%rsi), %r10
+  sbb 0x18(%rsi), %r11
+  sbb 0x20(%rsi), %rcx
+  mov %r8,   0x0(%rdx)
+  mov %r9,   0x8(%rdx)
+  mov %r10, 0x10(%rdx)
+  mov %r11, 0x18(%rdx)
+  mov %rcx, 0x20(%rdx)
+
+  mov 0x28(%rdi), %r8
+  mov 0x30(%rdi), %r9
+  mov 0x38(%rdi), %r10
+  mov 0x40(%rdi), %r11
+  mov 0x48(%rdi), %rcx
+  sbb 0x28(%rsi), %r8
+  sbb 0x30(%rsi), %r9
+  sbb 0x38(%rsi), %r10
+  sbb 0x40(%rsi), %r11
+  sbb 0x48(%rsi), %rcx
+  mov %r8,  0x28(%rdx)
+  mov %r9,  0x30(%rdx)
+  mov %r10, 0x38(%rdx)
+  mov %r11, 0x40(%rdx)
+  mov %rcx, 0x48(%rdx)
+
+  mov 0x50(%rdi), %r8
+  mov 0x58(%rdi), %r9
+  mov 0x60(%rdi), %r10
+  mov 0x68(%rdi), %r11
+  mov 0x70(%rdi), %rcx
+  sbb 0x50(%rsi), %r8
+  sbb 0x58(%rsi), %r9
+  sbb 0x60(%rsi), %r10
+  sbb 0x68(%rsi), %r11
+  sbb 0x70(%rsi), %rcx
+  mov %r8,  0x50(%rdx)
+  mov %r9,  0x58(%rdx)
+  mov %r10, 0x60(%rdx)
+  mov %r11, 0x68(%rdx)
+  mov %rcx, 0x70(%rdx)
+
+  mov 0x78(%rdi), %r8
+  sbb 0x78(%rsi), %r8
+  sbb \$0x0, %rax
+  mov %r8, 0x78(%rdx)
+  ret
+.cfi_endproc
+___
+
+#  Double 2x503-bit multiprecision subtraction
+#  Operation: c [rdx] = c [rdx] - a [rdi] - b [rsi]
+$code.=<<___;
+.globl  ${PREFIX}_mpdblsubx2_asm
+.type   ${PREFIX}_mpdblsubx2_asm,\@function,3
+${PREFIX}_mpdblsubx2_asm:
+.cfi_startproc
+  push   %r12
+.cfi_adjust_cfa_offset 8
+.cfi_offset r12, -16
+  push   %r13
+.cfi_adjust_cfa_offset 8
+.cfi_offset r13, -24
+  push   %r14
+.cfi_adjust_cfa_offset 8
+.cfi_offset r14, -32
+
+  xor %rax, %rax
+
+  mov  0x0(%rdx), %r8
+  mov  0x8(%rdx), %r9
+  mov 0x10(%rdx), %r10
+  mov 0x18(%rdx), %r11
+  mov 0x20(%rdx), %r12
+  mov 0x28(%rdx), %r13
+  mov 0x30(%rdx), %r14
+  mov 0x38(%rdx), %rcx
+  sub  0x0(%rdi), %r8
+  sbb  0x8(%rdi), %r9
+  sbb 0x10(%rdi), %r10
+  sbb 0x18(%rdi), %r11
+  sbb 0x20(%rdi), %r12
+  sbb 0x28(%rdi), %r13
+  sbb 0x30(%rdi), %r14
+  sbb 0x38(%rdi), %rcx
+  adc \$0x0, %rax
+
+  sub  0x0(%rsi), %r8
+  sbb  0x8(%rsi), %r9
+  sbb 0x10(%rsi), %r10
+  sbb 0x18(%rsi), %r11
+  sbb 0x20(%rsi), %r12
+  sbb 0x28(%rsi), %r13
+  sbb 0x30(%rsi), %r14
+  sbb 0x38(%rsi), %rcx
+  adc \$0x0, %rax
+
+  mov %r8,   0x0(%rdx)
+  mov %r9,   0x8(%rdx)
+  mov %r10, 0x10(%rdx)
+  mov %r11, 0x18(%rdx)
+  mov %r12, 0x20(%rdx)
+  mov %r13, 0x28(%rdx)
+  mov %r14, 0x30(%rdx)
+  mov %rcx, 0x38(%rdx)
+
+  mov 0x40(%rdx), %r8
+  mov 0x48(%rdx), %r9
+  mov 0x50(%rdx), %r10
+  mov 0x58(%rdx), %r11
+  mov 0x60(%rdx), %r12
+  mov 0x68(%rdx), %r13
+  mov 0x70(%rdx), %r14
+  mov 0x78(%rdx), %rcx
+
+  sub %rax, %r8
+  sbb 0x40(%rdi), %r8
+  sbb 0x48(%rdi), %r9
+  sbb 0x50(%rdi), %r10
+  sbb 0x58(%rdi), %r11
+  sbb 0x60(%rdi), %r12
+  sbb 0x68(%rdi), %r13
+  sbb 0x70(%rdi), %r14
+  sbb 0x78(%rdi), %rcx
+  sub 0x40(%rsi), %r8
+  sbb 0x48(%rsi), %r9
+  sbb 0x50(%rsi), %r10
+  sbb 0x58(%rsi), %r11
+  sbb 0x60(%rsi), %r12
+  sbb 0x68(%rsi), %r13
+  sbb 0x70(%rsi), %r14
+  sbb 0x78(%rsi), %rcx
+
+  mov %r8,  0x40(%rdx)
+  mov %r9,  0x48(%rdx)
+  mov %r10, 0x50(%rdx)
+  mov %r11, 0x58(%rdx)
+  mov %r12, 0x60(%rdx)
+  mov %r13, 0x68(%rdx)
+  mov %r14, 0x70(%rdx)
+  mov %rcx, 0x78(%rdx)
+
+  pop %r14
+.cfi_adjust_cfa_offset -8
+  pop %r13
+.cfi_adjust_cfa_offset -8
+  pop %r12
+.cfi_adjust_cfa_offset -8
+  ret
+.cfi_endproc
+
+___
+
+# Performs schoolbook multiplication of 2 256-bit numbers. Uses
+# MULX instruction. Result is stored in 256 bits pointed by $DST.
+sub mul256_school {
+  my ($idxM0,$M0,$idxM1,$M1,$idxDST,$DST,$T0,$T1,$T2,$T3,$T4,$T5,$T6,$T7,$T8,$T9)=@_;
+  my ($ML0,$ML8,$ML16,$ML24)=map("$idxM0+$_(%$M0)",(0,8,16,24));
+  my ($MR0,$MR8,$MR16,$MR24)=map("$idxM1+$_(%$M1)",(0,8,16,24));
+  my ($D0,$D1,$D2,$D3,$D4,$D5,$D6,$D7)=map("$idxDST+$_(%$DST)",(0,8,16,24,32,40,48,56));
+
+  $body=<<___;
+  mov    $ML0, %rdx
+  mulx   $MR0, %$T1, %$T0   # T0:T1 = A0*B0
+  mov    %$T1, $D0          # DST0_final
+  mulx   $MR8, %$T2, %$T1   # T1:T2 = A0*B1
+  xor    %rax, %rax
+  adox   %$T2, %$T0
+  mulx   $MR16,%$T3, %$T2   # T2:T3 = A0*B2
+  adox   %$T3, %$T1
+  mulx   $MR24,%$T4, %$T3   # T3:T4 = A0*B3
+  adox   %$T4, %$T2
+
+  mov    $ML8, %rdx
+  mulx   $MR0, %$T4, %$T5   # T5:T4 = A1*B0
+  adox   %rax, %$T3
+  xor    %rax, %rax
+  mulx   $MR8, %$T7, %$T6   # T6:T7 = A1*B1
+  adox   %$T0, %$T4
+  mov    %$T4, $D1          # DST1_final
+  adcx   %$T7, %$T5
+  mulx   $MR16,%$T8, %$T7   # T7:T8 = A1*B2
+  adcx   %$T8, %$T6
+  adox   %$T1, %$T5
+  mulx   $MR24,%$T9, %$T8   # T8:T9 = A1*B3
+  adcx   %$T9, %$T7
+  adcx   %rax, %$T8
+  adox   %$T2, %$T6
+
+  mov    $ML16,%rdx
+  mulx   $MR0, %$T0, %$T1   # T1:T0 = A2*B0
+  adox   %$T3, %$T7
+  adox   %rax, %$T8
+  xor    %rax, %rax
+  mulx   $MR8, %$T3, %$T2   # T2:T3 = A2*B1
+  adox   %$T5, %$T0
+  mov    %$T0, $D2          # DST2_final
+  adcx   %$T3, %$T1
+  mulx   $MR16,%$T4, %$T3   # T3:T4 = A2*B2
+  adcx   %$T4, %$T2
+  adox   %$T6, %$T1
+  mulx   $MR24,%$T9, %$T4   # T3:T4 = A2*B3
+  adcx   %$T9, %$T3
+
+  adcx   %rax, %$T4
+  adox   %$T7, %$T2
+  adox   %$T8, %$T3
+  adox   %rax, %$T4
+
+  mov    $ML24, %rdx
+  mulx   $MR0,  %$T0, %$T5   # T5:T0 = A3*B0
+  xor    %rax,  %rax
+  mulx   $MR8,  %$T7, %$T6   # T6:T7 = A3*B1
+  adcx   %$T7,  %$T5
+  adox   %$T0,  %$T1
+  mulx   $MR16, %$T8, %$T7   # T7:T8 = A3*B2
+  adcx   %$T8,  %$T6
+  adox   %$T5,  %$T2
+  mulx   $MR24, %$T9, %$T8   # T8:T9 = A3*B3
+  adcx   %$T9,  %$T7
+  adcx   %rax,  %$T8
+  adox   %$T6,  %$T3
+  adox   %$T7,  %$T4
+  adox   %rax,  %$T8
+  mov    %$T1,  $D3          # DST3_final
+  mov    %$T2,  $D4          # DST4_final
+  mov    %$T3,  $D5          # DST5_final
+  mov    %$T4,  $D6          # DST6_final
+  mov    %$T8,  $D7          # DST7_final
+
+___
+  return $body;
+}
+
+# 503-bit multiplication using Karatsuba (one level),
+# schoolbook (one level).
+sub mul_mulx {
+  # [rcx+64] <- (AH+AL) x (BH+BL)
+  my $mul256_low=&mul256_school(0,"rsp",32,"rsp",64,"rcx",map("r$_",(8..15)),"rbx","rbp");
+  # [rcx] <- AL x BL
+  my $mul256_albl=&mul256_school(0,"rdi",0,"rsi",0,"rcx",map("r$_",(8..15)),"rbx","rbp");
+  # [rsp] <- AH x BH
+  my $mul256_ahbh=&mul256_school(32,"rdi",32,"rsi",0,"rsp",map("r$_",(8..15)),"rbx","rbp");
+
+  $body=<<___;
+  .Lmul_mulx:
+  .cfi_startproc
+    # sike_mpmul has already pushed r12--15 by this point.
+  .cfi_adjust_cfa_offset 32
+  .cfi_offset r12, -16
+  .cfi_offset r13, -24
+  .cfi_offset r14, -32
+  .cfi_offset r15, -40
+
+    mov %rdx, %rcx
+
+    # r8-r11 <- AH + AL, rax <- mask
+    xor %rax, %rax
+    mov (%rdi), %r8
+    mov 0x8(%rdi), %r9
+    mov 0x10(%rdi), %r10
+    mov 0x18(%rdi), %r11
+    push %rbx
+
+  .cfi_adjust_cfa_offset 8
+  .cfi_offset rbx, -48
+    push %rbp
+  .cfi_offset rbp, -56
+  .cfi_adjust_cfa_offset 8
+    sub \$96, %rsp
+  .cfi_adjust_cfa_offset 96
+    add 0x20(%rdi), %r8
+    adc 0x28(%rdi), %r9
+    adc 0x30(%rdi), %r10
+    adc 0x38(%rdi), %r11
+    sbb \$0x0, %rax
+    mov %r8, (%rsp)
+    mov %r9, 0x8(%rsp)
+    mov %r10, 0x10(%rsp)
+    mov %r11, 0x18(%rsp)
+
+    # r12-r15 <- BH + BL, rbx <- mask
+    xor %rbx, %rbx
+    mov (%rsi), %r12
+    mov 0x8(%rsi), %r13
+    mov 0x10(%rsi), %r14
+    mov 0x18(%rsi), %r15
+    add 0x20(%rsi), %r12
+    adc 0x28(%rsi), %r13
+    adc 0x30(%rsi), %r14
+    adc 0x38(%rsi), %r15
+    sbb \$0x0, %rbx
+    mov %r12, 0x20(%rsp)
+    mov %r13, 0x28(%rsp)
+    mov %r14, 0x30(%rsp)
+    mov %r15, 0x38(%rsp)
+
+    # r12-r15 <- masked (BH + BL)
+    and %rax, %r12
+    and %rax, %r13
+    and %rax, %r14
+    and %rax, %r15
+
+    # r8-r11 <- masked (AH + AL)
+    and %rbx, %r8
+    and %rbx, %r9
+    and %rbx, %r10
+    and %rbx, %r11
+
+    # r8-r11 <- masked (AH + AL) + masked (AH + AL)
+    add %r12, %r8
+    adc %r13, %r9
+    adc %r14, %r10
+    adc %r15, %r11
+    mov %r8, 0x40(%rsp)
+    mov %r9, 0x48(%rsp)
+    mov %r10, 0x50(%rsp)
+    mov %r11, 0x58(%rsp)
+
+    # [rcx+64] <- (AH+AL) x (BH+BL)
+    $mul256_low
+    # [rcx] <- AL x BL (Result c0-c3)
+    $mul256_albl
+    # [rsp] <- AH x BH
+    $mul256_ahbh
+
+    # r8-r11 <- (AH+AL) x (BH+BL), final step
+    mov 0x40(%rsp), %r8
+    mov 0x48(%rsp), %r9
+    mov 0x50(%rsp), %r10
+    mov 0x58(%rsp), %r11
+    mov 0x60(%rcx), %rax
+    add %rax, %r8
+    mov 0x68(%rcx), %rax
+    adc %rax, %r9
+    mov 0x70(%rcx), %rax
+    adc %rax, %r10
+    mov 0x78(%rcx), %rax
+    adc %rax, %r11
+
+    # [rcx+64], x3-x5 <- (AH+AL) x (BH+BL) - ALxBL
+    mov 0x40(%rcx), %r12
+    mov 0x48(%rcx), %r13
+    mov 0x50(%rcx), %r14
+    mov 0x58(%rcx), %r15
+    sub (%rcx), %r12
+    sbb 0x8(%rcx), %r13
+    sbb 0x10(%rcx), %r14
+    sbb 0x18(%rcx), %r15
+    sbb 0x20(%rcx), %r8
+    sbb 0x28(%rcx), %r9
+    sbb 0x30(%rcx), %r10
+    sbb 0x38(%rcx), %r11
+
+    # r8-r15 <- (AH+AL) x (BH+BL) - ALxBL - AHxBH
+    sub (%rsp), %r12
+    sbb 0x8(%rsp), %r13
+    sbb 0x10(%rsp), %r14
+    sbb 0x18(%rsp), %r15
+    sbb 0x20(%rsp), %r8
+    sbb 0x28(%rsp), %r9
+    sbb 0x30(%rsp), %r10
+    sbb 0x38(%rsp), %r11
+
+    add 0x20(%rcx), %r12
+    mov %r12, 0x20(%rcx)    # Result C4-C7
+    adc 0x28(%rcx), %r13
+    mov %r13, 0x28(%rcx)
+    adc 0x30(%rcx), %r14
+    mov %r14, 0x30(%rcx)
+    adc 0x38(%rcx), %r15
+    mov %r15, 0x38(%rcx)
+    mov (%rsp), %rax
+    adc %rax, %r8           # Result C8-C15
+    mov %r8, 0x40(%rcx)
+    mov 0x8(%rsp), %rax
+    adc %rax, %r9
+    mov %r9, 0x48(%rcx)
+    mov 0x10(%rsp), %rax
+    adc %rax, %r10
+    mov %r10, 0x50(%rcx)
+    mov 0x18(%rsp), %rax
+    adc %rax, %r11
+    mov %r11, 0x58(%rcx)
+    mov 0x20(%rsp), %r12
+    adc \$0x0, %r12
+    mov %r12, 0x60(%rcx)
+    mov 0x28(%rsp), %r13
+    adc \$0x0, %r13
+    mov %r13, 0x68(%rcx)
+    mov 0x30(%rsp), %r14
+    adc \$0x0, %r14
+    mov %r14, 0x70(%rcx)
+    mov 0x38(%rsp), %r15
+    adc \$0x0, %r15
+    mov %r15, 0x78(%rcx)
+
+    add \$96, %rsp
+  .cfi_adjust_cfa_offset -96
+    pop %rbp
+  .cfi_adjust_cfa_offset -8
+  .cfi_same_value rbp
+    pop %rbx
+  .cfi_adjust_cfa_offset -8
+  .cfi_same_value rbx
+    pop %r15
+  .cfi_adjust_cfa_offset -8
+  .cfi_same_value r15
+    pop %r14
+  .cfi_adjust_cfa_offset -8
+  .cfi_same_value r14
+    pop %r13
+  .cfi_adjust_cfa_offset -8
+  .cfi_same_value r13
+    pop %r12
+  .cfi_adjust_cfa_offset -8
+  .cfi_same_value r12
+      ret
+  .cfi_endproc
+
+___
+  return $body;
+}
+
+# Jump to alternative implemenatation provided as an
+# argument in case CPU supports ADOX/ADCX and MULX instructions.
+sub alt_impl {
+  $jmp_func = shift;
+
+  $body=<<___;
+  lea OPENSSL_ia32cap_P(%rip), %rcx
+  mov 8(%rcx), %rcx
+  and \$0x80100, %ecx
+  cmp \$0x80100, %ecx
+  je  $jmp_func
+
+___
+  return $body
+}
+
+#  Integer multiplication based on Karatsuba method
+#  Operation: c [rdx] = a [rdi] * b [rsi]
+#  NOTE: a=c or b=c are not allowed
+sub mul {
+  my $jump_optim.=&alt_impl(".Lmul_mulx") if ($bmi2_adx);
+  my $body.=&mul_mulx() if ($bmi2_adx);
+
+  $body.=<<___;
+  .globl  ${PREFIX}_mpmul
+  .type   ${PREFIX}_mpmul,\@function,3
+  ${PREFIX}_mpmul:
+  .cfi_startproc
+    push %r12
+  .cfi_adjust_cfa_offset 8
+  .cfi_offset r12, -16
+    push %r13
+  .cfi_adjust_cfa_offset 8
+  .cfi_offset r13, -24
+    push %r14
+  .cfi_adjust_cfa_offset 8
+  .cfi_offset r14, -32
+    push %r15
+  .cfi_adjust_cfa_offset 8
+  .cfi_offset r15, -40
+
+    $jump_optim
+
+    mov %rdx, %rcx
+
+    # rcx[0-3] <- AH+AL
+    xor %rax, %rax
+    mov 0x20(%rdi), %r8
+    mov 0x28(%rdi), %r9
+    mov 0x30(%rdi), %r10
+    mov 0x38(%rdi), %r11
+    add  0x0(%rdi), %r8
+    adc  0x8(%rdi), %r9
+    adc 0x10(%rdi), %r10
+    adc 0x18(%rdi), %r11
+    mov %r8,   0x0(%rcx)
+    mov %r9,   0x8(%rcx)
+    mov %r10, 0x10(%rcx)
+    mov %r11, 0x18(%rcx)
+    sbb  \$0,  %rax
+    sub \$80,  %rsp           # Allocating space in stack
+  .cfi_adjust_cfa_offset 80
+
+    # r12-r15 <- BH+BL
+    xor %rdx, %rdx
+    mov 0x20(%rsi), %r12
+    mov 0x28(%rsi), %r13
+    mov 0x30(%rsi), %r14
+    mov 0x38(%rsi), %r15
+    add  0x0(%rsi), %r12
+    adc  0x8(%rsi), %r13
+    adc 0x10(%rsi), %r14
+    adc 0x18(%rsi), %r15
+    sbb \$0x0, %rdx
+    mov %rax, 0x40(%rsp)
+    mov %rdx, 0x48(%rsp)
+
+    # (rsp[0-3],r8,r9,r10,r11) <- (AH+AL)*(BH+BL)
+    mov (%rcx), %rax
+    mul %r12
+    mov %rax, (%rsp)            # c0
+    mov %rdx, %r8
+
+    xor %r9, %r9
+    mov (%rcx), %rax
+    mul %r13
+    add %rax, %r8
+    adc %rdx, %r9
+
+    xor %r10, %r10
+    mov 0x8(%rcx), %rax
+    mul %r12
+    add %rax, %r8
+    mov %r8, 0x8(%rsp)          # c1
+    adc %rdx, %r9
+    adc \$0x0, %r10
+
+    xor %r8, %r8
+    mov (%rcx), %rax
+    mul %r14
+    add %rax, %r9
+    adc %rdx, %r10
+    adc \$0x0, %r8
+
+    mov 0x10(%rcx), %rax
+    mul %r12
+    add %rax, %r9
+    adc %rdx, %r10
+    adc \$0x0, %r8
+
+    mov 0x8(%rcx), %rax
+    mul %r13
+    add %rax, %r9
+    mov %r9, 0x10(%rsp)         # c2
+    adc %rdx, %r10
+    adc \$0x0, %r8
+
+    xor %r9, %r9
+    mov (%rcx), %rax
+    mul %r15
+    add %rax, %r10
+    adc %rdx, %r8
+    adc \$0x0, %r9
+
+    mov 0x18(%rcx), %rax
+    mul %r12
+    add %rax, %r10
+    adc %rdx, %r8
+    adc \$0x0, %r9
+
+    mov 0x8(%rcx), %rax
+    mul %r14
+    add %rax, %r10
+    adc %rdx, %r8
+    adc \$0x0, %r9
+
+    mov 0x10(%rcx), %rax
+    mul %r13
+    add %rax, %r10
+    mov %r10, 0x18(%rsp)        # c3
+    adc %rdx, %r8
+    adc \$0x0, %r9
+
+    xor %r10, %r10
+    mov 0x8(%rcx), %rax
+    mul %r15
+    add %rax, %r8
+    adc %rdx, %r9
+    adc \$0x0, %r10
+
+    mov 0x18(%rcx), %rax
+    mul %r13
+    add %rax, %r8
+    adc %rdx, %r9
+    adc \$0x0, %r10
+
+    mov 0x10(%rcx), %rax
+    mul %r14
+    add %rax, %r8
+    mov %r8, 0x20(%rsp)          # c4
+    adc %rdx, %r9
+    adc \$0x0, %r10
+
+    xor %r11, %r11
+    mov 0x10(%rcx), %rax
+    mul %r15
+    add %rax, %r9
+    adc %rdx, %r10
+    adc \$0x0, %r11
+
+    mov 0x18(%rcx), %rax
+    mul %r14
+    add %rax, %r9               # c5
+    adc %rdx, %r10
+    adc \$0x0, %r11
+
+    mov 0x18(%rcx), %rax
+    mul %r15
+    add %rax, %r10              # c6
+    adc %rdx, %r11              # c7
+
+    mov 0x40(%rsp), %rax
+    and %rax, %r12
+    and %rax, %r13
+    and %rax, %r14
+    and %rax, %r15
+    add %r8, %r12
+    adc %r9, %r13
+    adc %r10, %r14
+    adc %r11, %r15
+
+    mov 0x48(%rsp), %rax
+    mov (%rcx), %r8
+    mov 0x8(%rcx), %r9
+    mov 0x10(%rcx), %r10
+    mov 0x18(%rcx), %r11
+    and %rax, %r8
+    and %rax, %r9
+    and %rax, %r10
+    and %rax, %r11
+    add %r12, %r8
+    adc %r13, %r9
+    adc %r14, %r10
+    adc %r15, %r11
+    mov %r8, 0x20(%rsp)
+    mov %r9, 0x28(%rsp)
+    mov %r10, 0x30(%rsp)
+    mov %r11, 0x38(%rsp)
+
+    mov (%rdi), %r11
+    mov (%rsi), %rax
+    mul %r11
+    xor %r9, %r9
+    mov %rax, (%rcx)              # c0
+    mov %rdx, %r8
+
+    mov 0x10(%rdi), %r14
+    mov 0x8(%rsi), %rax
+    mul %r11
+    xor %r10, %r10
+    add %rax, %r8
+    adc %rdx, %r9
+
+    mov 0x8(%rdi), %r12
+    mov (%rsi), %rax
+    mul %r12
+    add %rax, %r8
+    mov %r8, 0x8(%rcx)            # c1
+    adc %rdx, %r9
+    adc \$0x0, %r10
+
+    xor %r8, %r8
+    mov 0x10(%rsi), %rax
+    mul %r11
+    add %rax, %r9
+    adc %rdx, %r10
+    adc \$0x0, %r8
+
+    mov (%rsi), %r13
+    mov %r14, %rax
+    mul %r13
+    add %rax, %r9
+    adc %rdx, %r10
+    adc \$0x0, %r8
+
+    mov 0x8(%rsi), %rax
+    mul %r12
+    add %rax, %r9
+    mov %r9, 0x10(%rcx)           # c2
+    adc %rdx, %r10
+    adc \$0x0, %r8
+
+    xor %r9, %r9
+    mov 0x18(%rsi), %rax
+    mul %r11
+    mov 0x18(%rdi), %r15
+    add %rax, %r10
+    adc %rdx, %r8
+    adc \$0x0, %r9
+
+    mov %r15, %rax
+    mul %r13
+    add %rax, %r10
+    adc %rdx, %r8
+    adc \$0x0, %r9
+
+    mov 0x10(%rsi), %rax
+    mul %r12
+    add %rax, %r10
+    adc %rdx, %r8
+    adc \$0x0, %r9
+
+    mov 0x8(%rsi), %rax
+    mul %r14
+    add %rax, %r10
+    mov %r10, 0x18(%rcx)           # c3
+    adc %rdx, %r8
+    adc \$0x0, %r9
+
+    xor %r10, %r10
+    mov 0x18(%rsi), %rax
+    mul %r12
+    add %rax, %r8
+    adc %rdx, %r9
+    adc \$0x0, %r10
+
+    mov 0x8(%rsi), %rax
+    mul %r15
+    add %rax, %r8
+    adc %rdx, %r9
+    adc \$0x0, %r10
+
+    mov 0x10(%rsi), %rax
+    mul %r14
+    add %rax, %r8
+    mov %r8, 0x20(%rcx)           # c4
+    adc %rdx, %r9
+    adc \$0x0, %r10
+
+    xor %r8, %r8
+    mov 0x18(%rsi), %rax
+    mul %r14
+    add %rax, %r9
+    adc %rdx, %r10
+    adc \$0x0, %r8
+
+    mov 0x10(%rsi), %rax
+    mul %r15
+    add %rax, %r9
+    mov %r9, 0x28(%rcx)           # c5
+    adc %rdx, %r10
+    adc \$0x0, %r8
+
+    mov 0x18(%rsi), %rax
+    mul %r15
+    add %rax, %r10
+    mov %r10, 0x30(%rcx)          # c6
+    adc %rdx, %r8
+    mov %r8, 0x38(%rcx)           # c7
+
+    # rcx[8-15] <- AH*BH
+    mov 0x20(%rdi), %r11
+    mov 0x20(%rsi), %rax
+    mul %r11
+    xor %r9, %r9
+    mov %rax, 0x40(%rcx)          # c0
+    mov %rdx, %r8
+
+    mov 0x30(%rdi), %r14
+    mov 0x28(%rsi), %rax
+    mul %r11
+    xor %r10, %r10
+    add %rax, %r8
+    adc %rdx, %r9
+
+    mov 0x28(%rdi), %r12
+    mov 0x20(%rsi), %rax
+    mul %r12
+    add %rax, %r8
+    mov %r8, 0x48(%rcx)           # c1
+    adc %rdx, %r9
+    adc \$0x0, %r10
+
+    xor %r8, %r8
+    mov 0x30(%rsi), %rax
+    mul %r11
+    add %rax, %r9
+    adc %rdx, %r10
+    adc \$0x0, %r8
+
+    mov 0x20(%rsi), %r13
+    mov %r14, %rax
+    mul %r13
+    add %rax, %r9
+    adc %rdx, %r10
+    adc \$0x0, %r8
+
+    mov 0x28(%rsi), %rax
+    mul %r12
+    add %rax, %r9
+    mov %r9, 0x50(%rcx)             # c2
+    adc %rdx, %r10
+    adc \$0x0, %r8
+
+    xor %r9, %r9
+    mov 0x38(%rsi), %rax
+    mul %r11
+    mov 0x38(%rdi), %r15
+    add %rax, %r10
+    adc %rdx, %r8
+    adc \$0x0, %r9
+
+    mov %r15, %rax
+    mul %r13
+    add %rax, %r10
+    adc %rdx, %r8
+    adc \$0x0, %r9
+
+    mov 0x30(%rsi), %rax
+    mul %r12
+    add %rax, %r10
+    adc %rdx, %r8
+    adc \$0x0, %r9
+
+    mov 0x28(%rsi), %rax
+    mul %r14
+    add %rax, %r10
+    mov %r10, 0x58(%rcx)            # c3
+    adc %rdx, %r8
+    adc \$0x0, %r9
+
+    xor %r10, %r10
+    mov 0x38(%rsi), %rax
+    mul %r12
+    add %rax, %r8
+    adc %rdx, %r9
+    adc \$0x0, %r10
+
+    mov 0x28(%rsi), %rax
+    mul %r15
+    add %rax, %r8
+    adc %rdx, %r9
+    adc \$0x0, %r10
+
+    mov 0x30(%rsi), %rax
+    mul %r14
+    add %rax, %r8
+    mov %r8, 0x60(%rcx)             # c4
+    adc %rdx, %r9
+    adc \$0x0, %r10
+
+    xor %r8, %r8
+    mov 0x38(%rsi), %rax
+    mul %r14
+    add %rax, %r9
+    adc %rdx, %r10
+    adc \$0x0, %r8
+
+    mov 0x30(%rsi), %rax
+    mul %r15
+    add %rax, %r9
+    mov %r9, 0x68(%rcx)             # c5
+    adc %rdx, %r10
+    adc \$0x0, %r8
+
+    mov 0x38(%rsi), %rax
+    mul %r15
+    add %rax, %r10
+    mov %r10, 0x70(%rcx)            # c6
+    adc %rdx, %r8
+    mov %r8, 0x78(%rcx)             # c7
+
+    # [r8-r15] <- (AH+AL)*(BH+BL) - AL*BL
+    mov  0x0(%rsp), %r8
+    sub  0x0(%rcx), %r8
+    mov  0x8(%rsp), %r9
+    sbb  0x8(%rcx), %r9
+    mov 0x10(%rsp), %r10
+    sbb 0x10(%rcx), %r10
+    mov 0x18(%rsp), %r11
+    sbb 0x18(%rcx), %r11
+    mov 0x20(%rsp), %r12
+    sbb 0x20(%rcx), %r12
+    mov 0x28(%rsp), %r13
+    sbb 0x28(%rcx), %r13
+    mov 0x30(%rsp), %r14
+    sbb 0x30(%rcx), %r14
+    mov 0x38(%rsp), %r15
+    sbb 0x38(%rcx), %r15
+
+    # [r8-r15] <- (AH+AL)*(BH+BL) - AL*BL - AH*BH
+    mov 0x40(%rcx), %rax
+    sub %rax, %r8
+    mov 0x48(%rcx), %rax
+    sbb %rax, %r9
+    mov 0x50(%rcx), %rax
+    sbb %rax, %r10
+    mov 0x58(%rcx), %rax
+    sbb %rax, %r11
+    mov 0x60(%rcx), %rax
+    sbb %rax, %r12
+    mov 0x68(%rcx), %rdx
+    sbb %rdx, %r13
+    mov 0x70(%rcx), %rdi
+    sbb %rdi, %r14
+    mov 0x78(%rcx), %rsi
+    sbb %rsi, %r15
+
+    # Final result
+    add 0x20(%rcx),  %r8
+    mov %r8,  0x20(%rcx)
+    adc 0x28(%rcx),  %r9
+    mov %r9,  0x28(%rcx)
+    adc 0x30(%rcx), %r10
+    mov %r10, 0x30(%rcx)
+    adc 0x38(%rcx), %r11
+    mov %r11, 0x38(%rcx)
+    adc 0x40(%rcx), %r12
+    mov %r12, 0x40(%rcx)
+    adc 0x48(%rcx), %r13
+    mov %r13, 0x48(%rcx)
+    adc 0x50(%rcx), %r14
+    mov %r14, 0x50(%rcx)
+    adc 0x58(%rcx), %r15
+    mov %r15, 0x58(%rcx)
+    adc \$0x0, %rax
+    mov %rax, 0x60(%rcx)
+    adc \$0x0, %rdx
+    mov %rdx, 0x68(%rcx)
+    adc \$0x0, %rdi
+    mov %rdi, 0x70(%rcx)
+    adc \$0x0, %rsi
+    mov %rsi, 0x78(%rcx)
+
+    add \$80, %rsp           # Restoring space in stack
+  .cfi_adjust_cfa_offset -80
+    pop %r15
+  .cfi_adjust_cfa_offset -8
+    pop %r14
+  .cfi_adjust_cfa_offset -8
+    pop %r13
+  .cfi_adjust_cfa_offset -8
+    pop %r12
+  .cfi_adjust_cfa_offset -8
+    ret
+  .cfi_endproc
+
+___
+  return $body;
+}
+
+$code.=&mul();
+
+#  Optimized Montgomery reduction for CPUs with ADOX/ADCX and MULX
+#  Based on method described in Faz-Hernandez et al. https://eprint.iacr.org/2017/1015
+#  Operation: c [rsi] = a [rdi]
+#  NOTE: a=c is not allowed
+sub rdc_mulx {
+  # a[0-1] x .Lp503p1_nz --> result: r8:r14
+  my $mul01=&mul128x320_school(0,"rdi",".Lp503p1_nz(%rip)",map("r$_",(8..14)),"rbx","rcx","r15");
+  # a[2-3] x .Lp503p1_nz --> result: r8:r14
+  my $mul23=&mul128x320_school(16,"rdi",".Lp503p1_nz(%rip)",map("r$_",(8..14)),"rbx","rcx","r15");
+  # a[4-5] x .Lp503p1_nz --> result: r8:r14
+  my $mul45=&mul128x320_school(32,"rdi",".Lp503p1_nz(%rip)",map("r$_",(8..14)),"rbx","rcx","r15");
+  # a[6-7] x .Lp503p1_nz --> result: r8:r14
+  my $mul67=&mul128x320_school(48,"rdi",".Lp503p1_nz(%rip)",map("r$_", (8..14)),"rbx","rcx","r15");
+
+  my $body=<<___;
+    .Lrdc_mulx_asm:
+    .cfi_startproc
+      # sike_fprdc has already pushed r12--15 and rbx by this point.
+    .cfi_adjust_cfa_offset 32
+    .cfi_offset r12, -16
+    .cfi_offset r13, -24
+    .cfi_offset r14, -32
+    .cfi_offset r15, -40
+    .cfi_offset rbx, -48
+    .cfi_adjust_cfa_offset 8
+
+    $mul01
+
+    xor %r15, %r15
+    add 0x18(%rdi), %r8
+    adc 0x20(%rdi), %r9
+    adc 0x28(%rdi), %r10
+    adc 0x30(%rdi), %r11
+    adc 0x38(%rdi), %r12
+    adc 0x40(%rdi), %r13
+    adc 0x48(%rdi), %r14
+    adc 0x50(%rdi), %r15
+    mov %r8, 0x18(%rdi)
+    mov %r9, 0x20(%rdi)
+    mov %r10, 0x28(%rdi)
+    mov %r11, 0x30(%rdi)
+    mov %r12, 0x38(%rdi)
+    mov %r13, 0x40(%rdi)
+    mov %r14, 0x48(%rdi)
+    mov %r15, 0x50(%rdi)
+    mov 0x58(%rdi), %r8
+    mov 0x60(%rdi), %r9
+    mov 0x68(%rdi), %r10
+    mov 0x70(%rdi), %r11
+    mov 0x78(%rdi), %r12
+    adc \$0x0, %r8
+    adc \$0x0, %r9
+    adc \$0x0, %r10
+    adc \$0x0, %r11
+    adc \$0x0, %r12
+    mov %r8, 0x58(%rdi)
+    mov %r9, 0x60(%rdi)
+    mov %r10, 0x68(%rdi)
+    mov %r11, 0x70(%rdi)
+    mov %r12, 0x78(%rdi)
+
+    $mul23
+
+    xor %r15, %r15
+    add 0x28(%rdi), %r8
+    adc 0x30(%rdi), %r9
+    adc 0x38(%rdi), %r10
+    adc 0x40(%rdi), %r11
+    adc 0x48(%rdi), %r12
+    adc 0x50(%rdi), %r13
+    adc 0x58(%rdi), %r14
+    adc 0x60(%rdi), %r15
+    mov %r8, 0x28(%rdi)
+    mov %r9, 0x30(%rdi)
+    mov %r10, 0x38(%rdi)
+    mov %r11, 0x40(%rdi)
+    mov %r12, 0x48(%rdi)
+    mov %r13, 0x50(%rdi)
+    mov %r14, 0x58(%rdi)
+    mov %r15, 0x60(%rdi)
+    mov 0x68(%rdi), %r8
+    mov 0x70(%rdi), %r9
+    mov 0x78(%rdi), %r10
+    adc \$0x0, %r8
+    adc \$0x0, %r9
+    adc \$0x0, %r10
+    mov %r8, 0x68(%rdi)
+    mov %r9, 0x70(%rdi)
+    mov %r10, 0x78(%rdi)
+
+    $mul45
+
+    xor %r15, %r15
+    xor %rbx, %rbx
+    add 0x38(%rdi), %r8
+    adc 0x40(%rdi), %r9
+    adc 0x48(%rdi), %r10
+    adc 0x50(%rdi), %r11
+    adc 0x58(%rdi), %r12
+    adc 0x60(%rdi), %r13
+    adc 0x68(%rdi), %r14
+    adc 0x70(%rdi), %r15
+    adc 0x78(%rdi), %rbx
+    mov %r8, 0x38(%rdi)
+    mov %r9, (%rsi)         # Final result c0
+    mov %r10, 0x48(%rdi)
+    mov %r11, 0x50(%rdi)
+    mov %r12, 0x58(%rdi)
+    mov %r13, 0x60(%rdi)
+    mov %r14, 0x68(%rdi)
+    mov %r15, 0x70(%rdi)
+    mov %rbx, 0x78(%rdi)
+
+    $mul67
+
+    add 0x48(%rdi), %r8
+    adc 0x50(%rdi), %r9
+    adc 0x58(%rdi), %r10
+    adc 0x60(%rdi), %r11
+    adc 0x68(%rdi), %r12
+    adc 0x70(%rdi), %r13
+    adc 0x78(%rdi), %r14
+    mov %r8, 0x8(%rsi)
+    mov %r9, 0x10(%rsi)
+    mov %r10, 0x18(%rsi)
+    mov %r11, 0x20(%rsi)
+    mov %r12, 0x28(%rsi)
+    mov %r13, 0x30(%rsi)
+    mov %r14, 0x38(%rsi)
+
+    pop %rbx
+  .cfi_adjust_cfa_offset -8
+  .cfi_same_value rbx
+    pop %r15
+  .cfi_adjust_cfa_offset -8
+  .cfi_same_value r15
+    pop %r14
+  .cfi_adjust_cfa_offset -8
+  .cfi_same_value r14
+    pop %r13
+  .cfi_adjust_cfa_offset -8
+  .cfi_same_value r13
+    pop %r12
+  .cfi_adjust_cfa_offset -8
+  .cfi_same_value r12
+    ret
+  .cfi_endproc
+___
+  return $body;
+}
+
+#  Montgomery reduction
+#  Based on comba method
+#  Operation: c [rsi] = a [rdi]
+#  NOTE: a=c is not allowed
+sub rdc {
+  my $jump_optim=&alt_impl(".Lrdc_mulx_asm") if ($bmi2_adx);
+  my $body=&rdc_mulx() if ($bmi2_adx);
+
+  $body.=<<___;
+    .globl  ${PREFIX}_fprdc
+    .type   ${PREFIX}_fprdc,\@function,3
+    ${PREFIX}_fprdc:
+    .cfi_startproc
+      push %r12
+    .cfi_adjust_cfa_offset  8
+    .cfi_offset r12, -16
+      push %r13
+    .cfi_adjust_cfa_offset  8
+    .cfi_offset r13, -24
+      push %r14
+    .cfi_adjust_cfa_offset  8
+    .cfi_offset r14, -32
+      push %r15
+    .cfi_adjust_cfa_offset  8
+    .cfi_offset r15, -40
+      push %rbx
+    .cfi_adjust_cfa_offset  8
+    .cfi_offset rbx, -48
+
+    $jump_optim
+
+    # Reduction, generic x86 implementation
+    lea .Lp503p1(%rip), %rbx
+
+    mov (%rdi), %r11
+    mov (%rbx), %rax
+    mul %r11
+    xor %r8, %r8
+    add 0x18(%rdi), %rax
+    mov %rax, 0x18(%rsi)  # z3
+    adc %rdx, %r8
+
+    xor %r9, %r9
+    mov 0x8(%rbx), %rax
+    mul %r11
+    xor %r10, %r10
+    add %rax, %r8
+    adc %rdx, %r9
+
+    mov 0x8(%rdi), %r12
+    mov (%rbx), %rax
+    mul %r12
+    add %rax, %r8
+    adc %rdx, %r9
+    adc  \$0, %r10
+    add 0x20(%rdi), %r8
+    mov %r8, 0x20(%rsi)   # z4
+    adc  \$0, %r9
+    adc  \$0, %r10
+
+    xor %r8, %r8
+    mov 0x10(%rbx), %rax
+    mul %r11
+    add %rax, %r9
+    adc %rdx, %r10
+    adc  \$0, %r8
+
+    mov 8(%rbx), %rax
+    mul %r12
+    add %rax, %r9
+    adc %rdx, %r10
+    adc  \$0, %r8
+
+    mov 0x10(%rdi), %r13
+    mov (%rbx), %rax
+    mul %r13
+    add %rax, %r9
+    adc %rdx, %r10
+    adc  \$0, %r8
+    add 0x28(%rdi), %r9
+    mov %r9, 0x28(%rsi)   # z5
+    adc  \$0, %r10
+    adc  \$0, %r8
+
+    xor %r9, %r9
+    mov 0x18(%rbx), %rax
+    mul %r11
+    add %rax, %r10
+    adc %rdx, %r8
+    adc  \$0, %r9
+
+    mov 0x10(%rbx), %rax
+    mul %r12
+    add %rax, %r10
+    adc %rdx, %r8
+    adc  \$0, %r9
+
+    mov 0x8(%rbx), %rax
+    mul %r13
+    add %rax, %r10
+    adc %rdx, %r8
+    adc  \$0, %r9
+
+    mov 0x18(%rsi), %r14
+    mov (%rbx), %rax
+    mul %r14
+    add %rax, %r10
+    adc %rdx, %r8
+    adc  \$0, %r9
+    add 0x30(%rdi), %r10
+    mov %r10, 0x30(%rsi)    # z6
+    adc  \$0, %r8
+    adc  \$0, %r9
+
+    xor %r10, %r10
+    mov 0x20(%rbx), %rax
+    mul %r11
+    add %rax, %r8
+    adc %rdx, %r9
+    adc  \$0, %r10
+
+    mov 0x18(%rbx), %rax
+    mul %r12
+    add %rax, %r8
+    adc %rdx, %r9
+    adc  \$0, %r10
+
+    mov 0x10(%rbx), %rax
+    mul %r13
+    add %rax, %r8
+    adc %rdx, %r9
+    adc  \$0, %r10
+
+    mov 0x8(%rbx), %rax
+    mul %r14
+    add %rax, %r8
+    adc %rdx, %r9
+    adc  \$0, %r10
+
+    mov 0x20(%rsi), %r15
+    mov (%rbx), %rax
+    mul %r15
+    add %rax, %r8
+    adc %rdx, %r9
+    adc  \$0, %r10
+    add 0x38(%rdi), %r8     # Z7
+    mov %r8, 0x38(%rsi)
+    adc  \$0, %r9
+    adc  \$0, %r10
+
+    xor %r8, %r8
+    mov 0x20(%rbx), %rax
+    mul %r12
+    add %rax, %r9
+    adc %rdx, %r10
+    adc  \$0, %r8
+
+    mov 0x18(%rbx), %rax
+    mul %r13
+    add %rax, %r9
+    adc %rdx, %r10
+    adc  \$0, %r8
+
+    mov 0x10(%rbx), %rax
+    mul %r14
+    add %rax, %r9
+    adc %rdx, %r10
+    adc  \$0, %r8
+
+    mov 0x8(%rbx), %rax
+    mul %r15
+    add %rax, %r9
+    adc %rdx, %r10
+    adc  \$0, %r8
+
+    mov 0x28(%rsi), %rcx
+    mov (%rbx), %rax
+    mul %rcx
+    add %rax, %r9
+    adc %rdx, %r10
+    adc  \$0, %r8
+    add 0x40(%rdi), %r9
+    mov %r9, (%rsi)       # Z9
+    adc  \$0, %r10
+    adc  \$0, %r8
+
+    xor %r9, %r9
+    mov 0x20(%rbx), %rax
+    mul %r13
+    add %rax, %r10
+    adc %rdx, %r8
+    adc  \$0, %r9
+
+    mov 0x18(%rbx), %rax
+    mul %r14
+    add %rax, %r10
+    adc %rdx, %r8
+    adc  \$0, %r9
+
+    mov 0x10(%rbx), %rax
+    mul %r15
+    add %rax, %r10
+    adc %rdx, %r8
+    adc  \$0, %r9
+
+    mov 8(%rbx), %rax
+    mul %rcx
+    add %rax, %r10
+    adc %rdx, %r8
+    adc  \$0, %r9
+
+    mov 0x30(%rsi), %r13
+    mov (%rbx), %rax
+    mul %r13
+    add %rax, %r10
+    adc %rdx, %r8
+    adc  \$0, %r9
+    add 0x48(%rdi), %r10
+    mov %r10, 0x8(%rsi)     # Z1
+    adc  \$0, %r8
+    adc  \$0, %r9
+
+    xor %r10, %r10
+    mov 0x20(%rbx), %rax
+    mul %r14
+    add %rax, %r8
+    adc %rdx, %r9
+    adc  \$0, %r10
+
+    mov 0x18(%rbx), %rax
+    mul %r15
+    add %rax, %r8
+    adc %rdx, %r9
+    adc  \$0, %r10
+
+    mov 0x10(%rbx), %rax
+    mul %rcx
+    add %rax, %r8
+    adc %rdx, %r9
+    adc  \$0, %r10
+
+    mov 8(%rbx), %rax
+    mul %r13
+    add %rax, %r8
+    adc %rdx, %r9
+    adc  \$0, %r10
+
+    mov 0x38(%rsi), %r14
+    mov (%rbx), %rax
+    mul %r14
+    add %rax, %r8
+    adc %rdx, %r9
+    adc  \$0, %r10
+    add 0x50(%rdi), %r8
+    mov %r8, 0x10(%rsi)     # Z2
+    adc  \$0, %r9
+    adc  \$0, %r10
+
+    xor %r8, %r8
+    mov 0x20(%rbx), %rax
+    mul %r15
+    add %rax, %r9
+    adc %rdx, %r10
+    adc  \$0, %r8
+
+    mov 0x18(%rbx), %rax
+    mul %rcx
+    add %rax, %r9
+    adc %rdx, %r10
+    adc  \$0, %r8
+
+    mov 0x10(%rbx), %rax
+    mul %r13
+    add %rax, %r9
+    adc %rdx, %r10
+    adc  \$0, %r8
+
+    mov 8(%rbx), %rax
+    mul %r14
+    add %rax, %r9
+    adc %rdx, %r10
+    adc  \$0, %r8
+    add 0x58(%rdi), %r9
+    mov %r9, 0x18(%rsi)     # Z3
+    adc  \$0, %r10
+    adc  \$0, %r8
+
+    xor %r9, %r9
+    mov 0x20(%rbx), %rax
+    mul %rcx
+    add %rax, %r10
+    adc %rdx, %r8
+    adc  \$0, %r9
+
+    mov 0x18(%rbx), %rax
+    mul %r13
+    add %rax, %r10
+    adc %rdx, %r8
+    adc  \$0, %r9
+
+    mov 0x10(%rbx), %rax
+    mul %r14
+    add %rax, %r10
+    adc %rdx, %r8
+    adc  \$0, %r9
+    add 0x60(%rdi), %r10
+    mov %r10, 0x20(%rsi)    # Z4
+    adc  \$0, %r8
+    adc  \$0, %r9
+
+    xor %r10, %r10
+    mov 0x20(%rbx), %rax
+    mul %r13
+    add %rax, %r8
+    adc %rdx, %r9
+    adc  \$0, %r10
+
+    mov 0x18(%rbx), %rax
+    mul %r14
+    add %rax, %r8
+    adc %rdx, %r9
+    adc  \$0, %r10
+    add 0x68(%rdi), %r8     # Z5
+    mov %r8, 0x28(%rsi)     # Z5
+    adc  \$0, %r9
+    adc  \$0, %r10
+
+    mov 0x20(%rbx), %rax
+    mul %r14
+    add %rax, %r9
+    adc %rdx, %r10
+    add 0x70(%rdi), %r9     # Z6
+    mov %r9, 0x30(%rsi)     # Z6
+    adc  \$0, %r10
+    add 0x78(%rdi), %r10    # Z7
+    mov %r10, 0x38(%rsi)    # Z7
+
+    pop %rbx
+  .cfi_adjust_cfa_offset -8
+    pop %r15
+  .cfi_adjust_cfa_offset -8
+    pop %r14
+  .cfi_adjust_cfa_offset -8
+    pop %r13
+  .cfi_adjust_cfa_offset -8
+    pop %r12
+  .cfi_adjust_cfa_offset -8
+    ret
+  .cfi_endproc
+___
+  return $body;
+}
+
+$code.=&rdc();
+
+foreach (split("\n",$code)) {
+  s/\`([^\`]*)\`/eval($1)/ge;
+  print $_,"\n";
+}
+
+close STDOUT;
diff --git a/third_party/sike/asm/fp_generic.c b/third_party/sike/asm/fp_generic.c
new file mode 100644
index 0000000..cb786ff
--- /dev/null
+++ b/third_party/sike/asm/fp_generic.c
@@ -0,0 +1,181 @@
+/********************************************************************************************
+* SIDH: an efficient supersingular isogeny cryptography library
+*
+* Abstract: portable modular arithmetic for P503
+*********************************************************************************************/
+
+#include <openssl/base.h>
+
+#if defined(OPENSSL_NO_ASM) || \
+    (!defined(OPENSSL_X86_64) && !defined(OPENSSL_AARCH64))
+
+#include "../utils.h"
+#include "../fpx.h"
+
+// Global constants
+extern const struct params_t p503;
+
+static void digit_x_digit(const crypto_word_t a, const crypto_word_t b, crypto_word_t* c)
+{ // Digit multiplication, digit * digit -> 2-digit result
+    crypto_word_t al, ah, bl, bh, temp;
+    crypto_word_t albl, albh, ahbl, ahbh, res1, res2, res3, carry;
+    crypto_word_t mask_low = (crypto_word_t)(-1) >> (sizeof(crypto_word_t)*4);
+    crypto_word_t mask_high = (crypto_word_t)(-1) << (sizeof(crypto_word_t)*4);
+
+    al = a & mask_low;                              // Low part
+    ah = a >> (sizeof(crypto_word_t) * 4);          // High part
+    bl = b & mask_low;
+    bh = b >> (sizeof(crypto_word_t) * 4);
+
+    albl = al*bl;
+    albh = al*bh;
+    ahbl = ah*bl;
+    ahbh = ah*bh;
+    c[0] = albl & mask_low;                         // C00
+
+    res1 = albl >> (sizeof(crypto_word_t) * 4);
+    res2 = ahbl & mask_low;
+    res3 = albh & mask_low;
+    temp = res1 + res2 + res3;
+    carry = temp >> (sizeof(crypto_word_t) * 4);
+    c[0] ^= temp << (sizeof(crypto_word_t) * 4);    // C01
+
+    res1 = ahbl >> (sizeof(crypto_word_t) * 4);
+    res2 = albh >> (sizeof(crypto_word_t) * 4);
+    res3 = ahbh & mask_low;
+    temp = res1 + res2 + res3 + carry;
+    c[1] = temp & mask_low;                         // C10
+    carry = temp & mask_high;
+    c[1] ^= (ahbh & mask_high) + carry;             // C11
+}
+
+void sike_fpadd(const felm_t a, const felm_t b, felm_t c)
+{ // Modular addition, c = a+b mod p503.
+  // Inputs: a, b in [0, 2*p503-1]
+  // Output: c in [0, 2*p503-1]
+    unsigned int i, carry = 0;
+    crypto_word_t mask;
+
+    for (i = 0; i < NWORDS_FIELD; i++) {
+        ADDC(carry, a[i], b[i], carry, c[i]);
+    }
+
+    carry = 0;
+    for (i = 0; i < NWORDS_FIELD; i++) {
+        SUBC(carry, c[i], ((crypto_word_t*)p503.prime_x2)[i], carry, c[i]);
+    }
+    mask = 0 - (crypto_word_t)carry;
+
+    carry = 0;
+    for (i = 0; i < NWORDS_FIELD; i++) {
+        ADDC(carry, c[i], ((crypto_word_t*)p503.prime_x2)[i] & mask, carry, c[i]);
+    }
+}
+
+void sike_fpsub(const felm_t a, const felm_t b, felm_t c)
+{ // Modular subtraction, c = a-b mod p503.
+  // Inputs: a, b in [0, 2*p503-1]
+  // Output: c in [0, 2*p503-1]
+    unsigned int i, borrow = 0;
+    crypto_word_t mask;
+
+    for (i = 0; i < NWORDS_FIELD; i++) {
+        SUBC(borrow, a[i], b[i], borrow, c[i]);
+    }
+    mask = 0 - (crypto_word_t)borrow;
+
+    borrow = 0;
+    for (i = 0; i < NWORDS_FIELD; i++) {
+        ADDC(borrow, c[i], ((crypto_word_t*)p503.prime_x2)[i] & mask, borrow, c[i]);
+    }
+}
+
+void sike_mpmul(const felm_t a, const felm_t b, dfelm_t c)
+{ // Multiprecision comba multiply, c = a*b, where lng(a) = lng(b) = NWORDS_FIELD.
+    unsigned int i, j;
+    crypto_word_t t = 0, u = 0, v = 0, UV[2];
+    unsigned int carry = 0;
+
+    for (i = 0; i < NWORDS_FIELD; i++) {
+        for (j = 0; j <= i; j++) {
+            MUL(a[j], b[i-j], UV+1, UV[0]);
+            ADDC(0, UV[0], v, carry, v);
+            ADDC(carry, UV[1], u, carry, u);
+            t += carry;
+        }
+        c[i] = v;
+        v = u;
+        u = t;
+        t = 0;
+    }
+
+    for (i = NWORDS_FIELD; i < 2*NWORDS_FIELD-1; i++) {
+        for (j = i-NWORDS_FIELD+1; j < NWORDS_FIELD; j++) {
+            MUL(a[j], b[i-j], UV+1, UV[0]);
+            ADDC(0, UV[0], v, carry, v);
+            ADDC(carry, UV[1], u, carry, u);
+            t += carry;
+        }
+        c[i] = v;
+        v = u;
+        u = t;
+        t = 0;
+    }
+    c[2*NWORDS_FIELD-1] = v;
+}
+
+void sike_fprdc(const felm_t ma, felm_t mc)
+{ // Efficient Montgomery reduction using comba and exploiting the special form of the prime p503.
+  // mc = ma*R^-1 mod p503x2, where R = 2^512.
+  // If ma < 2^512*p503, the output mc is in the range [0, 2*p503-1].
+  // ma is assumed to be in Montgomery representation.
+    unsigned int i, j, carry, count = p503_ZERO_WORDS;
+    crypto_word_t UV[2], t = 0, u = 0, v = 0;
+
+    for (i = 0; i < NWORDS_FIELD; i++) {
+        mc[i] = 0;
+    }
+
+    for (i = 0; i < NWORDS_FIELD; i++) {
+        for (j = 0; j < i; j++) {
+            if (j < (i-p503_ZERO_WORDS+1)) {
+                MUL(mc[j], ((crypto_word_t*)p503.prime_p1)[i-j], UV+1, UV[0]);
+                ADDC(0, UV[0], v, carry, v);
+                ADDC(carry, UV[1], u, carry, u);
+                t += carry;
+            }
+        }
+        ADDC(0, v, ma[i], carry, v);
+        ADDC(carry, u, 0, carry, u);
+        t += carry;
+        mc[i] = v;
+        v = u;
+        u = t;
+        t = 0;
+    }
+
+    for (i = NWORDS_FIELD; i < 2*NWORDS_FIELD-1; i++) {
+        if (count > 0) {
+            count -= 1;
+        }
+        for (j = i-NWORDS_FIELD+1; j < NWORDS_FIELD; j++) {
+            if (j < (NWORDS_FIELD-count)) {
+                MUL(mc[j], ((crypto_word_t*)p503.prime_p1)[i-j], UV+1, UV[0]);
+                ADDC(0, UV[0], v, carry, v);
+                ADDC(carry, UV[1], u, carry, u);
+                t += carry;
+            }
+        }
+        ADDC(0, v, ma[i], carry, v);
+        ADDC(carry, u, 0, carry, u);
+        t += carry;
+        mc[i-NWORDS_FIELD] = v;
+        v = u;
+        u = t;
+        t = 0;
+    }
+    ADDC(0, v, ma[2*NWORDS_FIELD-1], carry, v);
+    mc[NWORDS_FIELD-1] = v;
+}
+
+#endif  // NO_ASM || (!X86_64 && !AARCH64)
diff --git a/third_party/sike/fpx.c b/third_party/sike/fpx.c
new file mode 100644
index 0000000..abb1ca2
--- /dev/null
+++ b/third_party/sike/fpx.c
@@ -0,0 +1,305 @@
+/********************************************************************************************
+* SIDH: an efficient supersingular isogeny cryptography library
+*
+* Abstract: core functions over GF(p) and GF(p^2)
+*********************************************************************************************/
+#include <openssl/base.h>
+
+#include "utils.h"
+#include "fpx.h"
+
+extern const struct params_t p503;
+
+// Multiprecision squaring, c = a^2 mod p.
+static void fpsqr_mont(const felm_t ma, felm_t mc)
+{
+    dfelm_t temp = {0};
+    sike_mpmul(ma, ma, temp);
+    sike_fprdc(temp, mc);
+}
+
+// Chain to compute a^(p-3)/4 using Montgomery arithmetic.
+static void fpinv_chain_mont(felm_t a)
+{
+    unsigned int i, j;
+    felm_t t[15], tt;
+
+    // Precomputed table
+    fpsqr_mont(a, tt);
+    sike_fpmul_mont(a, tt, t[0]);
+    for (i = 0; i <= 13; i++) sike_fpmul_mont(t[i], tt, t[i+1]);
+
+    sike_fpcopy(a, tt);
+    for (i = 0; i < 8; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(a, tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[8], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[6], tt, tt);
+    for (i = 0; i < 6; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[9], tt, tt);
+    for (i = 0; i < 7; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[0], tt, tt);
+    for (i = 0; i < 7; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(a, tt, tt);
+    for (i = 0; i < 7; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[6], tt, tt);
+    for (i = 0; i < 7; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[2], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[8], tt, tt);
+    for (i = 0; i < 7; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(a, tt, tt);
+    for (i = 0; i < 8; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[10], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[0], tt, tt);
+    for (i = 0; i < 6; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[10], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[10], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[5], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[2], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[6], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[3], tt, tt);
+    for (i = 0; i < 6; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[5], tt, tt);
+    for (i = 0; i < 12; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[12], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[8], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[6], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[12], tt, tt);
+    for (i = 0; i < 6; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[11], tt, tt);
+    for (i = 0; i < 8; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[6], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[5], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[14], tt, tt);
+    for (i = 0; i < 7; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[14], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[5], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[6], tt, tt);
+    for (i = 0; i < 8; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[8], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(a, tt, tt);
+    for (i = 0; i < 8; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[4], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[6], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[5], tt, tt);
+    for (i = 0; i < 8; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[7], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(a, tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[0], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[11], tt, tt);
+    for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[13], tt, tt);
+    for (i = 0; i < 8; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[1], tt, tt);
+    for (i = 0; i < 6; i++) fpsqr_mont(tt, tt);
+    sike_fpmul_mont(t[10], tt, tt);
+    for (j = 0; j < 49; j++) {
+        for (i = 0; i < 5; i++) fpsqr_mont(tt, tt);
+        sike_fpmul_mont(t[14], tt, tt);
+    }
+    sike_fpcopy(tt, a);
+}
+
+// Field inversion using Montgomery arithmetic, a = a^(-1)*R mod p.
+static void fpinv_mont(felm_t a)
+{
+    felm_t tt = {0};
+    sike_fpcopy(a, tt);
+    fpinv_chain_mont(tt);
+    fpsqr_mont(tt, tt);
+    fpsqr_mont(tt, tt);
+    sike_fpmul_mont(a, tt, a);
+}
+
+// Multiprecision addition, c = a+b, where lng(a) = lng(b) = nwords. Returns the carry bit.
+#if defined(OPENSSL_NO_ASM) || (!defined(OPENSSL_X86_64) && !defined(OPENSSL_AARCH64))
+inline static unsigned int mp_add(const felm_t a, const felm_t b, felm_t c, const unsigned int nwords) {
+    uint8_t carry = 0;
+    for (size_t i = 0; i < nwords; i++) {
+        ADDC(carry, a[i], b[i], carry, c[i]);
+    }
+    return carry;
+}
+
+// Multiprecision subtraction, c = a-b, where lng(a) = lng(b) = nwords. Returns the borrow bit.
+inline static unsigned int mp_sub(const felm_t a, const felm_t b, felm_t c, const unsigned int nwords) {
+    uint32_t borrow = 0;
+    for (size_t i = 0; i < nwords; i++) {
+        SUBC(borrow, a[i], b[i], borrow, c[i]);
+    }
+    return borrow;
+}
+#endif
+
+// Multiprecision addition, c = a+b.
+inline static void mp_addfast(const felm_t a, const felm_t b, felm_t c)
+{
+#if defined(OPENSSL_NO_ASM) || (!defined(OPENSSL_X86_64) && !defined(OPENSSL_AARCH64))
+    mp_add(a, b, c, NWORDS_FIELD);
+#else
+    sike_mpadd_asm(a, b, c);
+#endif
+}
+
+// Multiprecision subtraction, c = a-b, where lng(a) = lng(b) = 2*NWORDS_FIELD.
+// If c < 0 then returns mask = 0xFF..F, else mask = 0x00..0
+inline static crypto_word_t mp_subfast(const dfelm_t a, const dfelm_t b, dfelm_t c) {
+#if defined(OPENSSL_NO_ASM) || (!defined(OPENSSL_X86_64) && !defined(OPENSSL_AARCH64))
+    return (0 - (crypto_word_t)mp_sub(a, b, c, 2*NWORDS_FIELD));
+#else
+    return sike_mpsubx2_asm(a, b, c);
+#endif
+}
+
+// Multiprecision subtraction, c = c-a-b, where lng(a) = lng(b) = 2*NWORDS_FIELD.
+// Inputs should be s.t. c > a and c > b
+inline static void mp_dblsubfast(const dfelm_t a, const dfelm_t b, dfelm_t c) {
+#if defined(OPENSSL_NO_ASM) || (!defined(OPENSSL_X86_64) && !defined(OPENSSL_AARCH64))
+    mp_sub(c, a, c, 2*NWORDS_FIELD);
+    mp_sub(c, b, c, 2*NWORDS_FIELD);
+#else
+    sike_mpdblsubx2_asm(a, b, c);
+#endif
+}
+
+// Copy a field element, c = a.
+void sike_fpcopy(const felm_t a, felm_t c) {
+    for (size_t i = 0; i < NWORDS_FIELD; i++) {
+        c[i] = a[i];
+    }
+}
+
+// Field multiplication using Montgomery arithmetic, c = a*b*R^-1 mod p503, where R=2^768
+void sike_fpmul_mont(const felm_t ma, const felm_t mb, felm_t mc)
+{
+    dfelm_t temp = {0};
+    sike_mpmul(ma, mb, temp);
+    sike_fprdc(temp, mc);
+}
+
+// Conversion from Montgomery representation to standard representation,
+// c = ma*R^(-1) mod p = a mod p, where ma in [0, p-1].
+void sike_from_mont(const felm_t ma, felm_t c)
+{
+    felm_t one = {0};
+    one[0] = 1;
+
+    sike_fpmul_mont(ma, one, c);
+    sike_fpcorrection(c);
+}
+
+// GF(p^2) squaring using Montgomery arithmetic, c = a^2 in GF(p^2).
+// Inputs: a = a0+a1*i, where a0, a1 are in [0, 2*p-1]
+// Output: c = c0+c1*i, where c0, c1 are in [0, 2*p-1]
+void sike_fp2sqr_mont(const f2elm_t a, f2elm_t c) {
+    felm_t t1, t2, t3;
+
+    mp_addfast(a->c0, a->c1, t1);                      // t1 = a0+a1
+    sike_fpsub(a->c0, a->c1, t2);                      // t2 = a0-a1
+    mp_addfast(a->c0, a->c0, t3);                      // t3 = 2a0
+    sike_fpmul_mont(t1, t2, c->c0);                    // c0 = (a0+a1)(a0-a1)
+    sike_fpmul_mont(t3, a->c1, c->c1);                 // c1 = 2a0*a1
+}
+
+// Modular negation, a = -a mod p503.
+// Input/output: a in [0, 2*p503-1]
+void sike_fpneg(felm_t a) {
+  uint32_t borrow = 0;
+  for (size_t i = 0; i < NWORDS_FIELD; i++) {
+    SUBC(borrow, ((crypto_word_t*)p503.prime_x2)[i], a[i], borrow, a[i]);
+  }
+}
+
+// Modular division by two, c = a/2 mod p503.
+// Input : a in [0, 2*p503-1]
+// Output: c in [0, 2*p503-1]
+void sike_fpdiv2(const felm_t a, felm_t c) {
+  uint32_t carry = 0;
+  crypto_word_t mask;
+
+  mask = 0 - (crypto_word_t)(a[0] & 1);    // If a is odd compute a+p503
+  for (size_t i = 0; i < NWORDS_FIELD; i++) {
+    ADDC(carry, a[i], ((crypto_word_t*)p503.prime)[i] & mask, carry, c[i]);
+  }
+
+  // Multiprecision right shift by one.
+  for (size_t i = 0; i < NWORDS_FIELD-1; i++) {
+    c[i] = (c[i] >> 1) ^ (c[i+1] << (RADIX - 1));
+  }
+  c[NWORDS_FIELD-1] >>= 1;
+}
+
+// Modular correction to reduce field element a in [0, 2*p503-1] to [0, p503-1].
+void sike_fpcorrection(felm_t a) {
+  uint32_t borrow = 0;
+  crypto_word_t mask;
+
+  for (size_t i = 0; i < NWORDS_FIELD; i++) {
+    SUBC(borrow, a[i], ((crypto_word_t*)p503.prime)[i], borrow, a[i]);
+  }
+  mask = 0 - (crypto_word_t)borrow;
+
+  borrow = 0;
+  for (size_t i = 0; i < NWORDS_FIELD; i++) {
+    ADDC(borrow, a[i], ((crypto_word_t*)p503.prime)[i] & mask, borrow, a[i]);
+  }
+}
+
+// GF(p^2) multiplication using Montgomery arithmetic, c = a*b in GF(p^2).
+// Inputs: a = a0+a1*i and b = b0+b1*i, where a0, a1, b0, b1 are in [0, 2*p-1]
+// Output: c = c0+c1*i, where c0, c1 are in [0, 2*p-1]
+void sike_fp2mul_mont(const f2elm_t a, const f2elm_t b, f2elm_t c) {
+    felm_t t1, t2;
+    dfelm_t tt1, tt2, tt3;
+    crypto_word_t mask;
+
+    mp_addfast(a->c0, a->c1, t1);                      // t1 = a0+a1
+    mp_addfast(b->c0, b->c1, t2);                      // t2 = b0+b1
+    sike_mpmul(a->c0, b->c0, tt1);                     // tt1 = a0*b0
+    sike_mpmul(a->c1, b->c1, tt2);                     // tt2 = a1*b1
+    sike_mpmul(t1, t2, tt3);                           // tt3 = (a0+a1)*(b0+b1)
+    mp_dblsubfast(tt1, tt2, tt3);                      // tt3 = (a0+a1)*(b0+b1) - a0*b0 - a1*b1
+    mask = mp_subfast(tt1, tt2, tt1);                  // tt1 = a0*b0 - a1*b1. If tt1 < 0 then mask = 0xFF..F, else if tt1 >= 0 then mask = 0x00..0
+
+    for (size_t i = 0; i < NWORDS_FIELD; i++) {
+        t1[i] = ((crypto_word_t*)p503.prime)[i] & mask;
+    }
+
+    sike_fprdc(tt3, c->c1);                             // c[1] = (a0+a1)*(b0+b1) - a0*b0 - a1*b1
+    mp_addfast(&((crypto_word_t*)&tt1)[NWORDS_FIELD], t1, &((crypto_word_t*)&tt1)[NWORDS_FIELD]);
+    sike_fprdc(tt1, c->c0);                             // c[0] = a0*b0 - a1*b1
+}
+
+// GF(p^2) inversion using Montgomery arithmetic, a = (a0-i*a1)/(a0^2+a1^2).
+void sike_fp2inv_mont(f2elm_t a) {
+    f2elm_t t1;
+
+    fpsqr_mont(a->c0, t1->c0);                         // t10 = a0^2
+    fpsqr_mont(a->c1, t1->c1);                         // t11 = a1^2
+    sike_fpadd(t1->c0, t1->c1, t1->c0);                // t10 = a0^2+a1^2
+    fpinv_mont(t1->c0);                                // t10 = (a0^2+a1^2)^-1
+    sike_fpneg(a->c1);                                 // a = a0-i*a1
+    sike_fpmul_mont(a->c0, t1->c0, a->c0);
+    sike_fpmul_mont(a->c1, t1->c0, a->c1);             // a = (a0-i*a1)*(a0^2+a1^2)^-1
+}
diff --git a/third_party/sike/fpx.h b/third_party/sike/fpx.h
new file mode 100644
index 0000000..ed67768
--- /dev/null
+++ b/third_party/sike/fpx.h
@@ -0,0 +1,112 @@
+#ifndef FPX_H_
+#define FPX_H_
+
+#include "utils.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+// Modular addition, c = a+b mod p503.
+void sike_fpadd(const felm_t a, const felm_t b, felm_t c);
+// Modular subtraction, c = a-b mod p503.
+void sike_fpsub(const felm_t a, const felm_t b, felm_t c);
+// Modular division by two, c = a/2 mod p503.
+void sike_fpdiv2(const felm_t a, felm_t c);
+// Modular correction to reduce field element a in [0, 2*p503-1] to [0, p503-1].
+void sike_fpcorrection(felm_t a);
+// Multiprecision multiply, c = a*b, where lng(a) = lng(b) = nwords.
+void sike_mpmul(const felm_t a, const felm_t b, dfelm_t c);
+// 503-bit Montgomery reduction, c = a mod p
+void sike_fprdc(const dfelm_t a, felm_t c);
+// Double 2x503-bit multiprecision subtraction, c = c-a-b
+void sike_mpdblsubx2_asm(const felm_t a, const felm_t b, felm_t c);
+// Multiprecision subtraction, c = a-b
+crypto_word_t sike_mpsubx2_asm(const dfelm_t a, const dfelm_t b, dfelm_t c);
+// 503-bit multiprecision addition, c = a+b
+void sike_mpadd_asm(const felm_t a, const felm_t b, felm_t c);
+// Modular negation, a = -a mod p503.
+void sike_fpneg(felm_t a);
+// Copy of a field element, c = a
+void sike_fpcopy(const felm_t a, felm_t c);
+// Copy a field element, c = a.
+void sike_fpzero(felm_t a);
+// If option = 0xFF...FF x=y; y=x, otherwise swap doesn't happen. Constant time.
+void sike_cswap_asm(point_proj_t x, point_proj_t y, const crypto_word_t option);
+// Conversion from Montgomery representation to standard representation,
+// c = ma*R^(-1) mod p = a mod p, where ma in [0, p-1].
+void sike_from_mont(const felm_t ma, felm_t c);
+// Field multiplication using Montgomery arithmetic, c = a*b*R^-1 mod p503, where R=2^768
+void sike_fpmul_mont(const felm_t ma, const felm_t mb, felm_t mc);
+// GF(p503^2) multiplication using Montgomery arithmetic, c = a*b in GF(p503^2)
+void sike_fp2mul_mont(const f2elm_t a, const f2elm_t b, f2elm_t c);
+// GF(p503^2) inversion using Montgomery arithmetic, a = (a0-i*a1)/(a0^2+a1^2)
+void sike_fp2inv_mont(f2elm_t a);
+// GF(p^2) squaring using Montgomery arithmetic, c = a^2 in GF(p^2).
+void sike_fp2sqr_mont(const f2elm_t a, f2elm_t c);
+// Modular correction, a = a in GF(p^2).
+void sike_fp2correction(f2elm_t a);
+
+#if defined(__cplusplus)
+}  // extern C
+#endif
+
+// GF(p^2) addition, c = a+b in GF(p^2).
+#define sike_fp2add(a, b, c)             \
+do {                                     \
+    sike_fpadd(a->c0, b->c0, c->c0);     \
+    sike_fpadd(a->c1, b->c1, c->c1);     \
+} while(0)
+
+// GF(p^2) subtraction, c = a-b in GF(p^2).
+#define sike_fp2sub(a,b,c)               \
+do {                                     \
+    sike_fpsub(a->c0, b->c0, c->c0);     \
+    sike_fpsub(a->c1, b->c1, c->c1);     \
+} while(0)
+
+// Copy a GF(p^2) element, c = a.
+#define sike_fp2copy(a, c)               \
+do {                                     \
+    sike_fpcopy(a->c0, c->c0);           \
+    sike_fpcopy(a->c1, c->c1);           \
+} while(0)
+
+// GF(p^2) negation, a = -a in GF(p^2).
+#define sike_fp2neg(a)                   \
+do {                                     \
+    sike_fpneg(a->c0);                   \
+    sike_fpneg(a->c1);                   \
+} while(0)
+
+// GF(p^2) division by two, c = a/2  in GF(p^2).
+#define sike_fp2div2(a, c)               \
+do {                                     \
+    sike_fpdiv2(a->c0, c->c0);           \
+    sike_fpdiv2(a->c1, c->c1);           \
+} while(0)
+
+// Modular correction, a = a in GF(p^2).
+#define sike_fp2correction(a)            \
+do {                                     \
+    sike_fpcorrection(a->c0);            \
+    sike_fpcorrection(a->c1);            \
+} while(0)
+
+// Conversion of a GF(p^2) element to Montgomery representation,
+// mc_i = a_i*R^2*R^(-1) = a_i*R in GF(p^2).
+#define sike_to_fp2mont(a, mc)           \
+do {                                     \
+    sike_fpmul_mont(a->c0, (crypto_word_t*)&p503.mont_R2, mc->c0);   \
+    sike_fpmul_mont(a->c1, (crypto_word_t*)&p503.mont_R2, mc->c1);   \
+} while(0)
+
+// Conversion of a GF(p^2) element from Montgomery representation to standard representation,
+// c_i = ma_i*R^(-1) = a_i in GF(p^2).
+#define sike_from_fp2mont(ma, c)         \
+do {                                     \
+    sike_from_mont(ma->c0, c->c0);       \
+    sike_from_mont(ma->c1, c->c1);       \
+} while(0)
+
+#endif // FPX_H_
diff --git a/third_party/sike/isogeny.c b/third_party/sike/isogeny.c
new file mode 100644
index 0000000..45f9c40
--- /dev/null
+++ b/third_party/sike/isogeny.c
@@ -0,0 +1,260 @@
+/********************************************************************************************
+* SIDH: an efficient supersingular isogeny cryptography library
+*
+* Abstract: elliptic curve and isogeny functions
+*********************************************************************************************/
+#include "utils.h"
+#include "isogeny.h"
+#include "fpx.h"
+
+static void xDBL(const point_proj_t P, point_proj_t Q, const f2elm_t A24plus, const f2elm_t C24)
+{ // Doubling of a Montgomery point in projective coordinates (X:Z).
+  // Input: projective Montgomery x-coordinates P = (X1:Z1), where x1=X1/Z1 and Montgomery curve constants A+2C and 4C.
+  // Output: projective Montgomery x-coordinates Q = 2*P = (X2:Z2).
+    f2elm_t t0, t1;
+
+    sike_fp2sub(P->X, P->Z, t0);                         // t0 = X1-Z1
+    sike_fp2add(P->X, P->Z, t1);                         // t1 = X1+Z1
+    sike_fp2sqr_mont(t0, t0);                            // t0 = (X1-Z1)^2
+    sike_fp2sqr_mont(t1, t1);                            // t1 = (X1+Z1)^2
+    sike_fp2mul_mont(C24, t0, Q->Z);                     // Z2 = C24*(X1-Z1)^2
+    sike_fp2mul_mont(t1, Q->Z, Q->X);                    // X2 = C24*(X1-Z1)^2*(X1+Z1)^2
+    sike_fp2sub(t1, t0, t1);                             // t1 = (X1+Z1)^2-(X1-Z1)^2
+    sike_fp2mul_mont(A24plus, t1, t0);                   // t0 = A24plus*[(X1+Z1)^2-(X1-Z1)^2]
+    sike_fp2add(Q->Z, t0, Q->Z);                         // Z2 = A24plus*[(X1+Z1)^2-(X1-Z1)^2] + C24*(X1-Z1)^2
+    sike_fp2mul_mont(Q->Z, t1, Q->Z);                    // Z2 = [A24plus*[(X1+Z1)^2-(X1-Z1)^2] + C24*(X1-Z1)^2]*[(X1+Z1)^2-(X1-Z1)^2]
+}
+
+void xDBLe(const point_proj_t P, point_proj_t Q, const f2elm_t A24plus, const f2elm_t C24, size_t e)
+{ // Computes [2^e](X:Z) on Montgomery curve with projective constant via e repeated doublings.
+  // Input: projective Montgomery x-coordinates P = (XP:ZP), such that xP=XP/ZP and Montgomery curve constants A+2C and 4C.
+  // Output: projective Montgomery x-coordinates Q <- (2^e)*P.
+
+    memmove(Q, P, sizeof(*P));
+    for (size_t i = 0; i < e; i++) {
+        xDBL(Q, Q, A24plus, C24);
+    }
+}
+
+void get_4_isog(const point_proj_t P, f2elm_t A24plus, f2elm_t C24, f2elm_t* coeff)
+{ // Computes the corresponding 4-isogeny of a projective Montgomery point (X4:Z4) of order 4.
+  // Input:  projective point of order four P = (X4:Z4).
+  // Output: the 4-isogenous Montgomery curve with projective coefficients A+2C/4C and the 3 coefficients
+  //         that are used to evaluate the isogeny at a point in eval_4_isog().
+
+    sike_fp2sub(P->X, P->Z, coeff[1]);                   // coeff[1] = X4-Z4
+    sike_fp2add(P->X, P->Z, coeff[2]);                   // coeff[2] = X4+Z4
+    sike_fp2sqr_mont(P->Z, coeff[0]);                    // coeff[0] = Z4^2
+    sike_fp2add(coeff[0], coeff[0], coeff[0]);           // coeff[0] = 2*Z4^2
+    sike_fp2sqr_mont(coeff[0], C24);                     // C24 = 4*Z4^4
+    sike_fp2add(coeff[0], coeff[0], coeff[0]);           // coeff[0] = 4*Z4^2
+    sike_fp2sqr_mont(P->X, A24plus);                     // A24plus = X4^2
+    sike_fp2add(A24plus, A24plus, A24plus);              // A24plus = 2*X4^2
+    sike_fp2sqr_mont(A24plus, A24plus);                  // A24plus = 4*X4^4
+}
+
+void eval_4_isog(point_proj_t P, f2elm_t* coeff)
+{ // Evaluates the isogeny at the point (X:Z) in the domain of the isogeny, given a 4-isogeny phi defined
+  // by the 3 coefficients in coeff (computed in the function get_4_isog()).
+  // Inputs: the coefficients defining the isogeny, and the projective point P = (X:Z).
+  // Output: the projective point P = phi(P) = (X:Z) in the codomain.
+    f2elm_t t0, t1;
+
+    sike_fp2add(P->X, P->Z, t0);                         // t0 = X+Z
+    sike_fp2sub(P->X, P->Z, t1);                         // t1 = X-Z
+    sike_fp2mul_mont(t0, coeff[1], P->X);                // X = (X+Z)*coeff[1]
+    sike_fp2mul_mont(t1, coeff[2], P->Z);                // Z = (X-Z)*coeff[2]
+    sike_fp2mul_mont(t0, t1, t0);                        // t0 = (X+Z)*(X-Z)
+    sike_fp2mul_mont(t0, coeff[0], t0);                  // t0 = coeff[0]*(X+Z)*(X-Z)
+    sike_fp2add(P->X, P->Z, t1);                         // t1 = (X-Z)*coeff[2] + (X+Z)*coeff[1]
+    sike_fp2sub(P->X, P->Z, P->Z);                       // Z = (X-Z)*coeff[2] - (X+Z)*coeff[1]
+    sike_fp2sqr_mont(t1, t1);                            // t1 = [(X-Z)*coeff[2] + (X+Z)*coeff[1]]^2
+    sike_fp2sqr_mont(P->Z, P->Z);                        // Z = [(X-Z)*coeff[2] - (X+Z)*coeff[1]]^2
+    sike_fp2add(t1, t0, P->X);                           // X = coeff[0]*(X+Z)*(X-Z) + [(X-Z)*coeff[2] + (X+Z)*coeff[1]]^2
+    sike_fp2sub(P->Z, t0, t0);                           // t0 = [(X-Z)*coeff[2] - (X+Z)*coeff[1]]^2 - coeff[0]*(X+Z)*(X-Z)
+    sike_fp2mul_mont(P->X, t1, P->X);                    // Xfinal
+    sike_fp2mul_mont(P->Z, t0, P->Z);                    // Zfinal
+}
+
+
+void xTPL(const point_proj_t P, point_proj_t Q, const f2elm_t A24minus, const f2elm_t A24plus)
+{ // Tripling of a Montgomery point in projective coordinates (X:Z).
+  // Input: projective Montgomery x-coordinates P = (X:Z), where x=X/Z and Montgomery curve constants A24plus = A+2C and A24minus = A-2C.
+  // Output: projective Montgomery x-coordinates Q = 3*P = (X3:Z3).
+    f2elm_t t0, t1, t2, t3, t4, t5, t6;
+
+    sike_fp2sub(P->X, P->Z, t0);                         // t0 = X-Z
+    sike_fp2sqr_mont(t0, t2);                            // t2 = (X-Z)^2
+    sike_fp2add(P->X, P->Z, t1);                         // t1 = X+Z
+    sike_fp2sqr_mont(t1, t3);                            // t3 = (X+Z)^2
+    sike_fp2add(t0, t1, t4);                             // t4 = 2*X
+    sike_fp2sub(t1, t0, t0);                             // t0 = 2*Z
+    sike_fp2sqr_mont(t4, t1);                            // t1 = 4*X^2
+    sike_fp2sub(t1, t3, t1);                             // t1 = 4*X^2 - (X+Z)^2
+    sike_fp2sub(t1, t2, t1);                             // t1 = 4*X^2 - (X+Z)^2 - (X-Z)^2
+    sike_fp2mul_mont(t3, A24plus, t5);                   // t5 = A24plus*(X+Z)^2
+    sike_fp2mul_mont(t3, t5, t3);                        // t3 = A24plus*(X+Z)^3
+    sike_fp2mul_mont(A24minus, t2, t6);                  // t6 = A24minus*(X-Z)^2
+    sike_fp2mul_mont(t2, t6, t2);                        // t2 = A24minus*(X-Z)^3
+    sike_fp2sub(t2, t3, t3);                             // t3 = A24minus*(X-Z)^3 - coeff*(X+Z)^3
+    sike_fp2sub(t5, t6, t2);                             // t2 = A24plus*(X+Z)^2 - A24minus*(X-Z)^2
+    sike_fp2mul_mont(t1, t2, t1);                        // t1 = [4*X^2 - (X+Z)^2 - (X-Z)^2]*[A24plus*(X+Z)^2 - A24minus*(X-Z)^2]
+    sike_fp2add(t3, t1, t2);                             // t2 = [4*X^2 - (X+Z)^2 - (X-Z)^2]*[A24plus*(X+Z)^2 - A24minus*(X-Z)^2] + A24minus*(X-Z)^3 - coeff*(X+Z)^3
+    sike_fp2sqr_mont(t2, t2);                            // t2 = t2^2
+    sike_fp2mul_mont(t4, t2, Q->X);                      // X3 = 2*X*t2
+    sike_fp2sub(t3, t1, t1);                             // t1 = A24minus*(X-Z)^3 - A24plus*(X+Z)^3 - [4*X^2 - (X+Z)^2 - (X-Z)^2]*[A24plus*(X+Z)^2 - A24minus*(X-Z)^2]
+    sike_fp2sqr_mont(t1, t1);                            // t1 = t1^2
+    sike_fp2mul_mont(t0, t1, Q->Z);                      // Z3 = 2*Z*t1
+}
+
+void xTPLe(const point_proj_t P, point_proj_t Q, const f2elm_t A24minus, const f2elm_t A24plus, size_t e)
+{ // Computes [3^e](X:Z) on Montgomery curve with projective constant via e repeated triplings.
+  // Input: projective Montgomery x-coordinates P = (XP:ZP), such that xP=XP/ZP and Montgomery curve constants A24plus = A+2C and A24minus = A-2C.
+  // Output: projective Montgomery x-coordinates Q <- (3^e)*P.
+    memmove(Q, P, sizeof(*P));
+    for (size_t i = 0; i < e; i++) {
+        xTPL(Q, Q, A24minus, A24plus);
+    }
+}
+
+void get_3_isog(const point_proj_t P, f2elm_t A24minus, f2elm_t A24plus, f2elm_t* coeff)
+{ // Computes the corresponding 3-isogeny of a projective Montgomery point (X3:Z3) of order 3.
+  // Input:  projective point of order three P = (X3:Z3).
+  // Output: the 3-isogenous Montgomery curve with projective coefficient A/C.
+    f2elm_t t0, t1, t2, t3, t4;
+
+    sike_fp2sub(P->X, P->Z, coeff[0]);                   // coeff0 = X-Z
+    sike_fp2sqr_mont(coeff[0], t0);                      // t0 = (X-Z)^2
+    sike_fp2add(P->X, P->Z, coeff[1]);                   // coeff1 = X+Z
+    sike_fp2sqr_mont(coeff[1], t1);                      // t1 = (X+Z)^2
+    sike_fp2add(t0, t1, t2);                             // t2 = (X+Z)^2 + (X-Z)^2
+    sike_fp2add(coeff[0], coeff[1], t3);                 // t3 = 2*X
+    sike_fp2sqr_mont(t3, t3);                            // t3 = 4*X^2
+    sike_fp2sub(t3, t2, t3);                             // t3 = 4*X^2 - (X+Z)^2 - (X-Z)^2
+    sike_fp2add(t1, t3, t2);                             // t2 = 4*X^2 - (X-Z)^2
+    sike_fp2add(t3, t0, t3);                             // t3 = 4*X^2 - (X+Z)^2
+    sike_fp2add(t0, t3, t4);                             // t4 = 4*X^2 - (X+Z)^2 + (X-Z)^2
+    sike_fp2add(t4, t4, t4);                             // t4 = 2(4*X^2 - (X+Z)^2 + (X-Z)^2)
+    sike_fp2add(t1, t4, t4);                             // t4 = 8*X^2 - (X+Z)^2 + 2*(X-Z)^2
+    sike_fp2mul_mont(t2, t4, A24minus);                  // A24minus = [4*X^2 - (X-Z)^2]*[8*X^2 - (X+Z)^2 + 2*(X-Z)^2]
+    sike_fp2add(t1, t2, t4);                             // t4 = 4*X^2 + (X+Z)^2 - (X-Z)^2
+    sike_fp2add(t4, t4, t4);                             // t4 = 2(4*X^2 + (X+Z)^2 - (X-Z)^2)
+    sike_fp2add(t0, t4, t4);                             // t4 = 8*X^2 + 2*(X+Z)^2 - (X-Z)^2
+    sike_fp2mul_mont(t3, t4, t4);                        // t4 = [4*X^2 - (X+Z)^2]*[8*X^2 + 2*(X+Z)^2 - (X-Z)^2]
+    sike_fp2sub(t4, A24minus, t0);                       // t0 = [4*X^2 - (X+Z)^2]*[8*X^2 + 2*(X+Z)^2 - (X-Z)^2] - [4*X^2 - (X-Z)^2]*[8*X^2 - (X+Z)^2 + 2*(X-Z)^2]
+    sike_fp2add(A24minus, t0, A24plus);                  // A24plus = 8*X^2 - (X+Z)^2 + 2*(X-Z)^2
+}
+
+
+void eval_3_isog(point_proj_t Q, f2elm_t* coeff)
+{ // Computes the 3-isogeny R=phi(X:Z), given projective point (X3:Z3) of order 3 on a Montgomery curve and
+  // a point P with 2 coefficients in coeff (computed in the function get_3_isog()).
+  // Inputs: projective points P = (X3:Z3) and Q = (X:Z).
+  // Output: the projective point Q <- phi(Q) = (X3:Z3).
+    f2elm_t t0, t1, t2;
+
+    sike_fp2add(Q->X, Q->Z, t0);                       // t0 = X+Z
+    sike_fp2sub(Q->X, Q->Z, t1);                       // t1 = X-Z
+    sike_fp2mul_mont(t0, coeff[0], t0);                // t0 = coeff0*(X+Z)
+    sike_fp2mul_mont(t1, coeff[1], t1);                // t1 = coeff1*(X-Z)
+    sike_fp2add(t0, t1, t2);                           // t2 = coeff0*(X+Z) + coeff1*(X-Z)
+    sike_fp2sub(t1, t0, t0);                           // t0 = coeff1*(X-Z) - coeff0*(X+Z)
+    sike_fp2sqr_mont(t2, t2);                          // t2 = [coeff0*(X+Z) + coeff1*(X-Z)]^2
+    sike_fp2sqr_mont(t0, t0);                          // t0 = [coeff1*(X-Z) - coeff0*(X+Z)]^2
+    sike_fp2mul_mont(Q->X, t2, Q->X);                  // X3final = X*[coeff0*(X+Z) + coeff1*(X-Z)]^2
+    sike_fp2mul_mont(Q->Z, t0, Q->Z);                  // Z3final = Z*[coeff1*(X-Z) - coeff0*(X+Z)]^2
+}
+
+
+void inv_3_way(f2elm_t z1, f2elm_t z2, f2elm_t z3)
+{ // 3-way simultaneous inversion
+  // Input:  z1,z2,z3
+  // Output: 1/z1,1/z2,1/z3 (override inputs).
+    f2elm_t t0, t1, t2, t3;
+
+    sike_fp2mul_mont(z1, z2, t0);                      // t0 = z1*z2
+    sike_fp2mul_mont(z3, t0, t1);                      // t1 = z1*z2*z3
+    sike_fp2inv_mont(t1);                              // t1 = 1/(z1*z2*z3)
+    sike_fp2mul_mont(z3, t1, t2);                      // t2 = 1/(z1*z2)
+    sike_fp2mul_mont(t2, z2, t3);                      // t3 = 1/z1
+    sike_fp2mul_mont(t2, z1, z2);                      // z2 = 1/z2
+    sike_fp2mul_mont(t0, t1, z3);                      // z3 = 1/z3
+    sike_fp2copy(t3, z1);                              // z1 = 1/z1
+}
+
+
+void get_A(const f2elm_t xP, const f2elm_t xQ, const f2elm_t xR, f2elm_t A)
+{ // Given the x-coordinates of P, Q, and R, returns the value A corresponding to the Montgomery curve E_A: y^2=x^3+A*x^2+x such that R=Q-P on E_A.
+  // Input:  the x-coordinates xP, xQ, and xR of the points P, Q and R.
+  // Output: the coefficient A corresponding to the curve E_A: y^2=x^3+A*x^2+x.
+    f2elm_t t0, t1, one = F2ELM_INIT;
+
+    extern const struct params_t p503;
+    sike_fpcopy((crypto_word_t*)&p503.mont_one, one->c0);
+    sike_fp2add(xP, xQ, t1);                           // t1 = xP+xQ
+    sike_fp2mul_mont(xP, xQ, t0);                      // t0 = xP*xQ
+    sike_fp2mul_mont(xR, t1, A);                       // A = xR*t1
+    sike_fp2add(t0, A, A);                             // A = A+t0
+    sike_fp2mul_mont(t0, xR, t0);                      // t0 = t0*xR
+    sike_fp2sub(A, one, A);                            // A = A-1
+    sike_fp2add(t0, t0, t0);                           // t0 = t0+t0
+    sike_fp2add(t1, xR, t1);                           // t1 = t1+xR
+    sike_fp2add(t0, t0, t0);                           // t0 = t0+t0
+    sike_fp2sqr_mont(A, A);                            // A = A^2
+    sike_fp2inv_mont(t0);                              // t0 = 1/t0
+    sike_fp2mul_mont(A, t0, A);                        // A = A*t0
+    sike_fp2sub(A, t1, A);                             // Afinal = A-t1
+}
+
+
+void j_inv(const f2elm_t A, const f2elm_t C, f2elm_t jinv)
+{ // Computes the j-invariant of a Montgomery curve with projective constant.
+  // Input: A,C in GF(p^2).
+  // Output: j=256*(A^2-3*C^2)^3/(C^4*(A^2-4*C^2)), which is the j-invariant of the Montgomery curve B*y^2=x^3+(A/C)*x^2+x or (equivalently) j-invariant of B'*y^2=C*x^3+A*x^2+C*x.
+    f2elm_t t0, t1;
+
+    sike_fp2sqr_mont(A, jinv);                           // jinv = A^2
+    sike_fp2sqr_mont(C, t1);                             // t1 = C^2
+    sike_fp2add(t1, t1, t0);                             // t0 = t1+t1
+    sike_fp2sub(jinv, t0, t0);                           // t0 = jinv-t0
+    sike_fp2sub(t0, t1, t0);                             // t0 = t0-t1
+    sike_fp2sub(t0, t1, jinv);                           // jinv = t0-t1
+    sike_fp2sqr_mont(t1, t1);                            // t1 = t1^2
+    sike_fp2mul_mont(jinv, t1, jinv);                    // jinv = jinv*t1
+    sike_fp2add(t0, t0, t0);                             // t0 = t0+t0
+    sike_fp2add(t0, t0, t0);                             // t0 = t0+t0
+    sike_fp2sqr_mont(t0, t1);                            // t1 = t0^2
+    sike_fp2mul_mont(t0, t1, t0);                        // t0 = t0*t1
+    sike_fp2add(t0, t0, t0);                             // t0 = t0+t0
+    sike_fp2add(t0, t0, t0);                             // t0 = t0+t0
+    sike_fp2inv_mont(jinv);                              // jinv = 1/jinv
+    sike_fp2mul_mont(jinv, t0, jinv);                    // jinv = t0*jinv
+}
+
+
+void xDBLADD(point_proj_t P, point_proj_t Q, const f2elm_t xPQ, const f2elm_t A24)
+{ // Simultaneous doubling and differential addition.
+  // Input: projective Montgomery points P=(XP:ZP) and Q=(XQ:ZQ) such that xP=XP/ZP and xQ=XQ/ZQ, affine difference xPQ=x(P-Q) and Montgomery curve constant A24=(A+2)/4.
+  // Output: projective Montgomery points P <- 2*P = (X2P:Z2P) such that x(2P)=X2P/Z2P, and Q <- P+Q = (XQP:ZQP) such that = x(Q+P)=XQP/ZQP.
+    f2elm_t t0, t1, t2;
+
+    sike_fp2add(P->X, P->Z, t0);                         // t0 = XP+ZP
+    sike_fp2sub(P->X, P->Z, t1);                         // t1 = XP-ZP
+    sike_fp2sqr_mont(t0, P->X);                          // XP = (XP+ZP)^2
+    sike_fp2sub(Q->X, Q->Z, t2);                         // t2 = XQ-ZQ
+    sike_fp2correction(t2);
+    sike_fp2add(Q->X, Q->Z, Q->X);                       // XQ = XQ+ZQ
+    sike_fp2mul_mont(t0, t2, t0);                        // t0 = (XP+ZP)*(XQ-ZQ)
+    sike_fp2sqr_mont(t1, P->Z);                          // ZP = (XP-ZP)^2
+    sike_fp2mul_mont(t1, Q->X, t1);                      // t1 = (XP-ZP)*(XQ+ZQ)
+    sike_fp2sub(P->X, P->Z, t2);                         // t2 = (XP+ZP)^2-(XP-ZP)^2
+    sike_fp2mul_mont(P->X, P->Z, P->X);                  // XP = (XP+ZP)^2*(XP-ZP)^2
+    sike_fp2mul_mont(t2, A24, Q->X);                     // XQ = A24*[(XP+ZP)^2-(XP-ZP)^2]
+    sike_fp2sub(t0, t1, Q->Z);                           // ZQ = (XP+ZP)*(XQ-ZQ)-(XP-ZP)*(XQ+ZQ)
+    sike_fp2add(Q->X, P->Z, P->Z);                       // ZP = A24*[(XP+ZP)^2-(XP-ZP)^2]+(XP-ZP)^2
+    sike_fp2add(t0, t1, Q->X);                           // XQ = (XP+ZP)*(XQ-ZQ)+(XP-ZP)*(XQ+ZQ)
+    sike_fp2mul_mont(P->Z, t2, P->Z);                    // ZP = [A24*[(XP+ZP)^2-(XP-ZP)^2]+(XP-ZP)^2]*[(XP+ZP)^2-(XP-ZP)^2]
+    sike_fp2sqr_mont(Q->Z, Q->Z);                        // ZQ = [(XP+ZP)*(XQ-ZQ)-(XP-ZP)*(XQ+ZQ)]^2
+    sike_fp2sqr_mont(Q->X, Q->X);                        // XQ = [(XP+ZP)*(XQ-ZQ)+(XP-ZP)*(XQ+ZQ)]^2
+    sike_fp2mul_mont(Q->Z, xPQ, Q->Z);                   // ZQ = xPQ*[(XP+ZP)*(XQ-ZQ)-(XP-ZP)*(XQ+ZQ)]^2
+}
diff --git a/third_party/sike/isogeny.h b/third_party/sike/isogeny.h
new file mode 100644
index 0000000..460c8c6
--- /dev/null
+++ b/third_party/sike/isogeny.h
@@ -0,0 +1,49 @@
+#ifndef ISOGENY_H_
+#define ISOGENY_H_
+
+// Computes [2^e](X:Z) on Montgomery curve with projective
+// constant via e repeated doublings.
+void xDBLe(
+    const point_proj_t P, point_proj_t Q, const f2elm_t A24plus,
+    const f2elm_t C24, size_t e);
+// Simultaneous doubling and differential addition.
+void xDBLADD(
+    point_proj_t P, point_proj_t Q, const f2elm_t xPQ,
+    const f2elm_t A24);
+// Tripling of a Montgomery point in projective coordinates (X:Z).
+void xTPL(
+    const point_proj_t P, point_proj_t Q, const f2elm_t A24minus,
+    const f2elm_t A24plus);
+// Computes [3^e](X:Z) on Montgomery curve with projective constant
+// via e repeated triplings.
+void xTPLe(
+    const point_proj_t P, point_proj_t Q, const f2elm_t A24minus,
+    const f2elm_t A24plus, size_t e);
+// Given the x-coordinates of P, Q, and R, returns the value A
+// corresponding to the Montgomery curve E_A: y^2=x^3+A*x^2+x such that R=Q-P on E_A.
+void get_A(
+    const f2elm_t xP, const f2elm_t xQ, const f2elm_t xR, f2elm_t A);
+// Computes the j-invariant of a Montgomery curve with projective constant.
+void j_inv(
+    const f2elm_t A, const f2elm_t C, f2elm_t jinv);
+// Computes the corresponding 4-isogeny of a projective Montgomery
+// point (X4:Z4) of order 4.
+void get_4_isog(
+    const point_proj_t P, f2elm_t A24plus, f2elm_t C24, f2elm_t* coeff);
+// Computes the corresponding 3-isogeny of a projective Montgomery
+// point (X3:Z3) of order 3.
+void get_3_isog(
+    const point_proj_t P, f2elm_t A24minus, f2elm_t A24plus,
+    f2elm_t* coeff);
+// Computes the 3-isogeny R=phi(X:Z), given projective point (X3:Z3)
+// of order 3 on a Montgomery curve and a point P with coefficients given in coeff.
+void eval_3_isog(
+    point_proj_t Q, f2elm_t* coeff);
+// Evaluates the isogeny at the point (X:Z) in the domain of the isogeny.
+void eval_4_isog(
+    point_proj_t P, f2elm_t* coeff);
+// 3-way simultaneous inversion
+void inv_3_way(
+    f2elm_t z1, f2elm_t z2, f2elm_t z3);
+
+#endif // ISOGENY_H_
diff --git a/third_party/sike/sike.c b/third_party/sike/sike.c
new file mode 100644
index 0000000..01add94
--- /dev/null
+++ b/third_party/sike/sike.c
@@ -0,0 +1,571 @@
+/********************************************************************************************
+* SIDH: an efficient supersingular isogeny cryptography library
+*
+* Abstract: supersingular isogeny key encapsulation (SIKE) protocol
+*********************************************************************************************/
+
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+#include <openssl/bn.h>
+#include <openssl/base.h>
+#include <openssl/rand.h>
+#include <openssl/mem.h>
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+
+#include "utils.h"
+#include "isogeny.h"
+#include "fpx.h"
+
+extern const struct params_t p503;
+
+// Domain separation parameters for HMAC
+static const uint8_t G[2] = {0,0};
+static const uint8_t H[2] = {1,0};
+static const uint8_t F[2] = {2,0};
+
+// SIDHp503_JINV_BYTESZ is a number of bytes used for encoding j-invariant.
+#define SIDHp503_JINV_BYTESZ    126U
+// SIDHp503_PRV_A_BITSZ is a number of bits of SIDH private key (2-isogeny)
+#define SIDHp503_PRV_A_BITSZ    250U
+// SIDHp503_PRV_A_BITSZ is a number of bits of SIDH private key (3-isogeny)
+#define SIDHp503_PRV_B_BITSZ    253U
+// MAX_INT_POINTS_ALICE is a number of points used in 2-isogeny tree computation
+#define MAX_INT_POINTS_ALICE    7U
+// MAX_INT_POINTS_ALICE is a number of points used in 3-isogeny tree computation
+#define MAX_INT_POINTS_BOB      8U
+
+// Produces HMAC-SHA256 of data |S| mac'ed with the key |key|. Result is stored in |out|
+// which must have size of at least |outsz| bytes and must be not bigger than
+// SHA256_DIGEST_LENGTH. The output of a HMAC may be truncated.
+// The |key| buffer is reused by the hmac_sum and hence, it's size must be equal
+// to SHA256_CBLOCK. The HMAC key provided in |key| buffer must be smaller or equal
+// to SHA256_DIGHEST_LENTH. |key| can overlap |out|.
+static void hmac_sum(
+    uint8_t *out, size_t outsz, const uint8_t S[2], uint8_t key[SHA256_CBLOCK]) {
+    for(size_t i=0; i<SHA256_DIGEST_LENGTH; i++) {
+        key[i] = key[i] ^ 0x36;
+    }
+    // set rest of the buffer to ipad = 0x36
+    memset(&key[SHA256_DIGEST_LENGTH], 0x36, SHA256_CBLOCK - SHA256_DIGEST_LENGTH);
+
+    SHA256_CTX ctx;
+    SHA256_Init(&ctx);
+    SHA256_Update(&ctx, key, SHA256_CBLOCK);
+    SHA256_Update(&ctx, S, 2);
+    uint8_t digest[SHA256_DIGEST_LENGTH];
+    SHA256_Final(digest, &ctx);
+
+    // XOR key with an opad = 0x5C
+    for(size_t i=0; i<SHA256_CBLOCK; i++) {
+        key[i] = key[i] ^ 0x36 ^ 0x5C;
+    }
+
+    SHA256_Init(&ctx);
+    SHA256_Update(&ctx, key, SHA256_CBLOCK);
+    SHA256_Update(&ctx, digest, SHA256_DIGEST_LENGTH);
+    SHA256_Final(digest, &ctx);
+    assert(outsz <= sizeof(digest));
+    memcpy(out, digest, outsz);
+}
+
+// Swap points.
+// If option = 0 then P <- P and Q <- Q, else if option = 0xFF...FF then P <- Q and Q <- P
+#if !defined(OPENSSL_X86_64) || defined(OPENSSL_NO_ASM)
+static void sike_cswap(point_proj_t P, point_proj_t Q, const crypto_word_t option)
+{
+    crypto_word_t temp;
+    for (size_t i = 0; i < NWORDS_FIELD; i++) {
+        temp = option & (P->X->c0[i] ^ Q->X->c0[i]);
+        P->X->c0[i] = temp ^ P->X->c0[i];
+        Q->X->c0[i] = temp ^ Q->X->c0[i];
+        temp = option & (P->Z->c0[i] ^ Q->Z->c0[i]);
+        P->Z->c0[i] = temp ^ P->Z->c0[i];
+        Q->Z->c0[i] = temp ^ Q->Z->c0[i];
+        temp = option & (P->X->c1[i] ^ Q->X->c1[i]);
+        P->X->c1[i] = temp ^ P->X->c1[i];
+        Q->X->c1[i] = temp ^ Q->X->c1[i];
+        temp = option & (P->Z->c1[i] ^ Q->Z->c1[i]);
+        P->Z->c1[i] = temp ^ P->Z->c1[i];
+        Q->Z->c1[i] = temp ^ Q->Z->c1[i];
+    }
+}
+#endif
+
+// Swap points.
+// If option = 0 then P <- P and Q <- Q, else if option = 0xFF...FF then P <- Q and Q <- P
+static inline void sike_fp2cswap(point_proj_t P, point_proj_t Q, const crypto_word_t option)
+{
+#if defined(OPENSSL_X86_64) && !defined(OPENSSL_NO_ASM)
+    sike_cswap_asm(P, Q, option);
+#else
+    sike_cswap(P, Q, option);
+#endif
+}
+
+static void LADDER3PT(
+    const f2elm_t xP, const f2elm_t xQ, const f2elm_t xPQ, const crypto_word_t* m,
+    int is_A, point_proj_t R, const f2elm_t A) {
+    point_proj_t R0 = POINT_PROJ_INIT, R2 = POINT_PROJ_INIT;
+    f2elm_t A24 = F2ELM_INIT;
+    crypto_word_t mask;
+    int bit, swap, prevbit = 0;
+
+    const size_t nbits = is_A?SIDHp503_PRV_A_BITSZ:SIDHp503_PRV_B_BITSZ;
+
+    // Initializing constant
+    sike_fpcopy((crypto_word_t*)&p503.mont_one, A24[0].c0);
+    sike_fp2add(A24, A24, A24);
+    sike_fp2add(A, A24, A24);
+    sike_fp2div2(A24, A24);
+    sike_fp2div2(A24, A24); // A24 = (A+2)/4
+
+    // Initializing points
+    sike_fp2copy(xQ, R0->X);
+    sike_fpcopy((crypto_word_t*)&p503.mont_one, R0->Z[0].c0);
+    sike_fp2copy(xPQ, R2->X);
+    sike_fpcopy((crypto_word_t*)&p503.mont_one, R2->Z[0].c0);
+    sike_fp2copy(xP, R->X);
+    sike_fpcopy((crypto_word_t*)&p503.mont_one, R->Z[0].c0);
+    memset(R->Z->c1, 0, sizeof(R->Z->c1));
+
+    // Main loop
+    for (size_t i = 0; i < nbits; i++) {
+        bit = (m[i >> LOG2RADIX] >> (i & (RADIX-1))) & 1;
+        swap = bit ^ prevbit;
+        prevbit = bit;
+        mask = 0 - (crypto_word_t)swap;
+
+        sike_fp2cswap(R, R2, mask);
+        xDBLADD(R0, R2, R->X, A24);
+        sike_fp2mul_mont(R2->X, R->Z, R2->X);
+    }
+}
+
+// Initialization of basis points
+static inline void sike_init_basis(const crypto_word_t *gen, f2elm_t XP, f2elm_t XQ, f2elm_t XR) {
+    sike_fpcopy(gen,                  XP->c0);
+    sike_fpcopy(gen +   NWORDS_FIELD, XP->c1);
+    sike_fpcopy(gen + 2*NWORDS_FIELD, XQ->c0);
+    memset(XQ->c1, 0, sizeof(XQ->c1));
+    sike_fpcopy(gen + 3*NWORDS_FIELD, XR->c0);
+    sike_fpcopy(gen + 4*NWORDS_FIELD, XR->c1);
+}
+
+// Conversion of GF(p^2) element from Montgomery to standard representation.
+static inline void sike_fp2_encode(const f2elm_t x, uint8_t *enc) {
+    f2elm_t t;
+    sike_from_fp2mont(x, t);
+
+    // convert to bytes in little endian form
+    for (size_t i=0; i<FIELD_BYTESZ; i++) {
+        enc[i+           0] = (t[0].c0[i/LSZ] >> (8*(i%LSZ))) & 0xFF;
+        enc[i+FIELD_BYTESZ] = (t[0].c1[i/LSZ] >> (8*(i%LSZ))) & 0xFF;
+    }
+}
+
+// Parse byte sequence back into GF(p^2) element, and conversion to Montgomery representation.
+// Elements over GF(p503) are encoded in 63 octets in little endian format
+// (i.e., the least significant octet is located in the lowest memory address).
+static inline void fp2_decode(const uint8_t *enc, f2elm_t t) {
+    memset(t[0].c0, 0, sizeof(t[0].c0));
+    memset(t[0].c1, 0, sizeof(t[0].c1));
+    // convert bytes in little endian form to f2elm_t
+    for (size_t i = 0; i < FIELD_BYTESZ; i++) {
+        t[0].c0[i/LSZ] |= ((crypto_word_t)enc[i+           0]) << (8*(i%LSZ));
+        t[0].c1[i/LSZ] |= ((crypto_word_t)enc[i+FIELD_BYTESZ]) << (8*(i%LSZ));
+    }
+    sike_to_fp2mont(t, t);
+}
+
+// Alice's ephemeral public key generation
+// Input:  a private key prA in the range [0, 2^250 - 1], stored in 32 bytes.
+// Output: the public key pkA consisting of 3 GF(p503^2) elements encoded in 378 bytes.
+static void gen_iso_A(const uint8_t* skA, uint8_t* pkA)
+{
+    point_proj_t R, pts[MAX_INT_POINTS_ALICE];
+    point_proj_t phiP = POINT_PROJ_INIT;
+    point_proj_t phiQ = POINT_PROJ_INIT;
+    point_proj_t phiR = POINT_PROJ_INIT;
+    f2elm_t XPA, XQA, XRA, coeff[3];
+    f2elm_t A24plus = F2ELM_INIT;
+    f2elm_t C24 = F2ELM_INIT;
+    f2elm_t A = F2ELM_INIT;
+    unsigned int m, index = 0, pts_index[MAX_INT_POINTS_ALICE], npts = 0, ii = 0;
+
+    // Initialize basis points
+    sike_init_basis((crypto_word_t*)p503.A_gen, XPA, XQA, XRA);
+    sike_init_basis((crypto_word_t*)p503.B_gen, phiP->X, phiQ->X, phiR->X);
+    sike_fpcopy((crypto_word_t*)&p503.mont_one, (phiP->Z)->c0);
+    sike_fpcopy((crypto_word_t*)&p503.mont_one, (phiQ->Z)->c0);
+    sike_fpcopy((crypto_word_t*)&p503.mont_one, (phiR->Z)->c0);
+
+    // Initialize constants
+    sike_fpcopy((crypto_word_t*)&p503.mont_one, A24plus->c0);
+    sike_fp2add(A24plus, A24plus, C24);
+
+    // Retrieve kernel point
+    LADDER3PT(XPA, XQA, XRA, (crypto_word_t*)skA, 1, R, A);
+
+    // Traverse tree
+    index = 0;
+    for (size_t row = 1; row < A_max; row++) {
+        while (index < A_max-row) {
+            sike_fp2copy(R->X, pts[npts]->X);
+            sike_fp2copy(R->Z, pts[npts]->Z);
+            pts_index[npts++] = index;
+            m = p503.A_strat[ii++];
+            xDBLe(R, R, A24plus, C24, (2*m));
+            index += m;
+        }
+        get_4_isog(R, A24plus, C24, coeff);
+
+        for (size_t i = 0; i < npts; i++) {
+            eval_4_isog(pts[i], coeff);
+        }
+        eval_4_isog(phiP, coeff);
+        eval_4_isog(phiQ, coeff);
+        eval_4_isog(phiR, coeff);
+
+        sike_fp2copy(pts[npts-1]->X, R->X);
+        sike_fp2copy(pts[npts-1]->Z, R->Z);
+        index = pts_index[npts-1];
+        npts -= 1;
+    }
+
+    get_4_isog(R, A24plus, C24, coeff);
+    eval_4_isog(phiP, coeff);
+    eval_4_isog(phiQ, coeff);
+    eval_4_isog(phiR, coeff);
+
+    inv_3_way(phiP->Z, phiQ->Z, phiR->Z);
+    sike_fp2mul_mont(phiP->X, phiP->Z, phiP->X);
+    sike_fp2mul_mont(phiQ->X, phiQ->Z, phiQ->X);
+    sike_fp2mul_mont(phiR->X, phiR->Z, phiR->X);
+
+    // Format public key
+    sike_fp2_encode(phiP->X, pkA);
+    sike_fp2_encode(phiQ->X, pkA + SIDHp503_JINV_BYTESZ);
+    sike_fp2_encode(phiR->X, pkA + 2*SIDHp503_JINV_BYTESZ);
+}
+
+// Bob's ephemeral key-pair generation
+// It produces a private key skB and computes the public key pkB.
+// The private key is an integer in the range [0, 2^Floor(Log(2,3^159)) - 1], stored in 32 bytes.
+// The public key consists of 3 GF(p503^2) elements encoded in 378 bytes.
+static void gen_iso_B(const uint8_t* skB, uint8_t* pkB)
+{
+    point_proj_t R, pts[MAX_INT_POINTS_BOB];
+    point_proj_t phiP = POINT_PROJ_INIT;
+    point_proj_t phiQ = POINT_PROJ_INIT;
+    point_proj_t phiR = POINT_PROJ_INIT;
+    f2elm_t XPB, XQB, XRB, coeff[3];
+    f2elm_t A24plus = F2ELM_INIT;
+    f2elm_t A24minus = F2ELM_INIT;
+    f2elm_t A = F2ELM_INIT;
+    unsigned int m, index = 0, pts_index[MAX_INT_POINTS_BOB], npts = 0, ii = 0;
+
+    // Initialize basis points
+    sike_init_basis((crypto_word_t*)p503.B_gen, XPB, XQB, XRB);
+    sike_init_basis((crypto_word_t*)p503.A_gen, phiP->X, phiQ->X, phiR->X);
+    sike_fpcopy((crypto_word_t*)&p503.mont_one, (phiP->Z)->c0);
+    sike_fpcopy((crypto_word_t*)&p503.mont_one, (phiQ->Z)->c0);
+    sike_fpcopy((crypto_word_t*)&p503.mont_one, (phiR->Z)->c0);
+
+    // Initialize constants
+    sike_fpcopy((crypto_word_t*)&p503.mont_one, A24plus->c0);
+    sike_fp2add(A24plus, A24plus, A24plus);
+    sike_fp2copy(A24plus, A24minus);
+    sike_fp2neg(A24minus);
+
+    // Retrieve kernel point
+    LADDER3PT(XPB, XQB, XRB, (crypto_word_t*)skB, 0, R, A);
+
+    // Traverse tree
+    index = 0;
+    for (size_t row = 1; row < B_max; row++) {
+        while (index < B_max-row) {
+            sike_fp2copy(R->X, pts[npts]->X);
+            sike_fp2copy(R->Z, pts[npts]->Z);
+            pts_index[npts++] = index;
+            m = p503.B_strat[ii++];
+            xTPLe(R, R, A24minus, A24plus, m);
+            index += m;
+        }
+        get_3_isog(R, A24minus, A24plus, coeff);
+
+        for (size_t i = 0; i < npts; i++) {
+            eval_3_isog(pts[i], coeff);
+        }
+        eval_3_isog(phiP, coeff);
+        eval_3_isog(phiQ, coeff);
+        eval_3_isog(phiR, coeff);
+
+        sike_fp2copy(pts[npts-1]->X, R->X);
+        sike_fp2copy(pts[npts-1]->Z, R->Z);
+        index = pts_index[npts-1];
+        npts -= 1;
+    }
+
+    get_3_isog(R, A24minus, A24plus, coeff);
+    eval_3_isog(phiP, coeff);
+    eval_3_isog(phiQ, coeff);
+    eval_3_isog(phiR, coeff);
+
+    inv_3_way(phiP->Z, phiQ->Z, phiR->Z);
+    sike_fp2mul_mont(phiP->X, phiP->Z, phiP->X);
+    sike_fp2mul_mont(phiQ->X, phiQ->Z, phiQ->X);
+    sike_fp2mul_mont(phiR->X, phiR->Z, phiR->X);
+
+    // Format public key
+    sike_fp2_encode(phiP->X, pkB);
+    sike_fp2_encode(phiQ->X, pkB + SIDHp503_JINV_BYTESZ);
+    sike_fp2_encode(phiR->X, pkB + 2*SIDHp503_JINV_BYTESZ);
+}
+
+// Alice's ephemeral shared secret computation
+// It produces a shared secret key ssA using her secret key skA and Bob's public key pkB
+// Inputs: Alice's skA is an integer in the range [0, 2^250 - 1], stored in 32 bytes.
+//         Bob's pkB consists of 3 GF(p503^2) elements encoded in 378 bytes.
+// Output: a shared secret ssA that consists of one element in GF(p503^2) encoded in 126 bytes.
+static void ex_iso_A(const uint8_t* skA, const uint8_t* pkB, uint8_t* ssA)
+{
+    point_proj_t R, pts[MAX_INT_POINTS_ALICE];
+    f2elm_t coeff[3], PKB[3], jinv;
+    f2elm_t A24plus = F2ELM_INIT;
+    f2elm_t C24 = F2ELM_INIT;
+    f2elm_t A = F2ELM_INIT;
+    unsigned int m, index = 0, pts_index[MAX_INT_POINTS_ALICE], npts = 0, ii = 0;
+
+    // Initialize images of Bob's basis
+    fp2_decode(pkB, PKB[0]);
+    fp2_decode(pkB + SIDHp503_JINV_BYTESZ, PKB[1]);
+    fp2_decode(pkB + 2*SIDHp503_JINV_BYTESZ, PKB[2]);
+
+    // Initialize constants
+    get_A(PKB[0], PKB[1], PKB[2], A); // TODO: Can return projective A?
+    sike_fpadd((crypto_word_t*)&p503.mont_one, (crypto_word_t*)&p503.mont_one, C24->c0);
+    sike_fp2add(A, C24, A24plus);
+    sike_fpadd(C24->c0, C24->c0, C24->c0);
+
+    // Retrieve kernel point
+    LADDER3PT(PKB[0], PKB[1], PKB[2], (crypto_word_t*)skA, 1, R, A);
+
+    // Traverse tree
+    index = 0;
+    for (size_t row = 1; row < A_max; row++) {
+        while (index < A_max-row) {
+            sike_fp2copy(R->X, pts[npts]->X);
+            sike_fp2copy(R->Z, pts[npts]->Z);
+            pts_index[npts++] = index;
+            m = p503.A_strat[ii++];
+            xDBLe(R, R, A24plus, C24, (2*m));
+            index += m;
+        }
+        get_4_isog(R, A24plus, C24, coeff);
+
+        for (size_t i = 0; i < npts; i++) {
+            eval_4_isog(pts[i], coeff);
+        }
+
+        sike_fp2copy(pts[npts-1]->X, R->X);
+        sike_fp2copy(pts[npts-1]->Z, R->Z);
+        index = pts_index[npts-1];
+        npts -= 1;
+    }
+
+    get_4_isog(R, A24plus, C24, coeff);
+    sike_fp2div2(C24, C24);
+    sike_fp2sub(A24plus, C24, A24plus);
+    sike_fp2div2(C24, C24);
+    j_inv(A24plus, C24, jinv);
+    sike_fp2_encode(jinv, ssA);
+}
+
+// Bob's ephemeral shared secret computation
+// It produces a shared secret key ssB using his secret key skB and Alice's public key pkA
+// Inputs: Bob's skB is an integer in the range [0, 2^Floor(Log(2,3^159)) - 1], stored in 32 bytes.
+//         Alice's pkA consists of 3 GF(p503^2) elements encoded in 378 bytes.
+// Output: a shared secret ssB that consists of one element in GF(p503^2) encoded in 126 bytes.
+static void ex_iso_B(const uint8_t* skB, const uint8_t* pkA, uint8_t* ssB)
+{
+    point_proj_t R, pts[MAX_INT_POINTS_BOB];
+    f2elm_t coeff[3], PKB[3], jinv;
+    f2elm_t A24plus = F2ELM_INIT;
+    f2elm_t A24minus = F2ELM_INIT;
+    f2elm_t A = F2ELM_INIT;
+    unsigned int m, index = 0, pts_index[MAX_INT_POINTS_BOB], npts = 0, ii = 0;
+
+    // Initialize images of Alice's basis
+    fp2_decode(pkA, PKB[0]);
+    fp2_decode(pkA + SIDHp503_JINV_BYTESZ, PKB[1]);
+    fp2_decode(pkA + 2*SIDHp503_JINV_BYTESZ, PKB[2]);
+
+    // Initialize constants
+    get_A(PKB[0], PKB[1], PKB[2], A);
+    sike_fpadd((crypto_word_t*)&p503.mont_one, (crypto_word_t*)&p503.mont_one, A24minus->c0);
+    sike_fp2add(A, A24minus, A24plus);
+    sike_fp2sub(A, A24minus, A24minus);
+
+    // Retrieve kernel point
+    LADDER3PT(PKB[0], PKB[1], PKB[2], (crypto_word_t*)skB, 0, R, A);
+
+    // Traverse tree
+    index = 0;
+    for (size_t row = 1; row < B_max; row++) {
+        while (index < B_max-row) {
+            sike_fp2copy(R->X, pts[npts]->X);
+            sike_fp2copy(R->Z, pts[npts]->Z);
+            pts_index[npts++] = index;
+            m = p503.B_strat[ii++];
+            xTPLe(R, R, A24minus, A24plus, m);
+            index += m;
+        }
+        get_3_isog(R, A24minus, A24plus, coeff);
+
+        for (size_t i = 0; i < npts; i++) {
+            eval_3_isog(pts[i], coeff);
+        }
+
+        sike_fp2copy(pts[npts-1]->X, R->X);
+        sike_fp2copy(pts[npts-1]->Z, R->Z);
+        index = pts_index[npts-1];
+        npts -= 1;
+    }
+
+    get_3_isog(R, A24minus, A24plus, coeff);
+    sike_fp2add(A24plus, A24minus, A);
+    sike_fp2add(A, A, A);
+    sike_fp2sub(A24plus, A24minus, A24plus);
+    j_inv(A, A24plus, jinv);
+    sike_fp2_encode(jinv, ssB);
+}
+
+int SIKE_keypair(uint8_t out_priv[SIKEp503_PRV_BYTESZ],
+                 uint8_t out_pub[SIKEp503_PUB_BYTESZ]) {
+  int ret = 0;
+
+  // Calculate private key for Alice. Needs to be in range [0, 2^0xFA - 1] and <
+  // 253 bits
+  BIGNUM *bn_sidh_prv = BN_new();
+  if (!bn_sidh_prv ||
+      !BN_rand(bn_sidh_prv, SIDHp503_PRV_B_BITSZ, BN_RAND_TOP_ONE,
+               BN_RAND_BOTTOM_ANY) ||
+      !BN_bn2le_padded(out_priv, BITS_TO_BYTES(SIDHp503_PRV_B_BITSZ),
+                       bn_sidh_prv)) {
+    goto end;
+  }
+
+  gen_iso_B(out_priv, out_pub);
+  ret = 1;
+
+end:
+  BN_free(bn_sidh_prv);
+  return ret;
+}
+
+void SIKE_encaps(uint8_t out_shared_key[SIKEp503_SS_BYTESZ],
+                 uint8_t out_ciphertext[SIKEp503_CT_BYTESZ],
+                 const uint8_t pub_key[SIKEp503_PUB_BYTESZ]) {
+  // Secret buffer is reused by the function to store some ephemeral
+  // secret data. It's size must be maximum of SHA256_CBLOCK,
+  // SIKEp503_MSG_BYTESZ and SIDHp503_PRV_A_BITSZ in bytes.
+  uint8_t secret[SHA256_CBLOCK];
+  uint8_t j[SIDHp503_JINV_BYTESZ];
+  uint8_t temp[SIKEp503_MSG_BYTESZ + SIKEp503_CT_BYTESZ];
+  SHA256_CTX ctx;
+
+  // Generate secret key for A
+  // secret key A = HMAC({0,1}^n || pub_key), G) mod SIDHp503_PRV_A_BITSZ
+  RAND_bytes(temp, SIKEp503_MSG_BYTESZ);
+
+  SHA256_Init(&ctx);
+  SHA256_Update(&ctx, temp, SIKEp503_MSG_BYTESZ);
+  SHA256_Update(&ctx, pub_key, SIKEp503_PUB_BYTESZ);
+  SHA256_Final(secret, &ctx);
+  hmac_sum(secret, BITS_TO_BYTES(SIDHp503_PRV_A_BITSZ), G, secret);
+  secret[BITS_TO_BYTES(SIDHp503_PRV_A_BITSZ) - 1] &=
+      (1 << (SIDHp503_PRV_A_BITSZ % 8)) - 1;
+
+  // Generate public key for A - first part of the ciphertext
+  gen_iso_A(secret, out_ciphertext);
+
+  // Generate c1:
+  //  h = HMAC(j-invariant(secret key A, public key B), F)
+  // c1 = h ^ m
+  ex_iso_A(secret, pub_key, j);
+  SHA256_Init(&ctx);
+  SHA256_Update(&ctx, j, sizeof(j));
+  SHA256_Final(secret, &ctx);
+  hmac_sum(secret, SIKEp503_MSG_BYTESZ, F, secret);
+
+  // c1 = h ^ m
+  uint8_t *c1 = &out_ciphertext[SIKEp503_PUB_BYTESZ];
+  for (size_t i = 0; i < SIKEp503_MSG_BYTESZ; i++) {
+    c1[i] = temp[i] ^ secret[i];
+  }
+
+  SHA256_Init(&ctx);
+  SHA256_Update(&ctx, temp, SIKEp503_MSG_BYTESZ);
+  SHA256_Update(&ctx, out_ciphertext, SIKEp503_CT_BYTESZ);
+  SHA256_Final(secret, &ctx);
+  // Generate shared secret out_shared_key = HMAC(m||out_ciphertext, F)
+  hmac_sum(out_shared_key, SIKEp503_SS_BYTESZ, H, secret);
+}
+
+void SIKE_decaps(uint8_t out_shared_key[SIKEp503_SS_BYTESZ],
+                 const uint8_t ciphertext[SIKEp503_CT_BYTESZ],
+                 const uint8_t pub_key[SIKEp503_PUB_BYTESZ],
+                 const uint8_t priv_key[SIKEp503_PRV_BYTESZ]) {
+  // Secret buffer is reused by the function to store some ephemeral
+  // secret data. It's size must be maximum of SHA256_CBLOCK,
+  // SIKEp503_MSG_BYTESZ and SIDHp503_PRV_A_BITSZ in bytes.
+  uint8_t secret[SHA256_CBLOCK];
+  uint8_t j[SIDHp503_JINV_BYTESZ];
+  uint8_t c0[SIKEp503_PUB_BYTESZ];
+  uint8_t temp[SIKEp503_MSG_BYTESZ];
+  uint8_t shared_nok[SIKEp503_MSG_BYTESZ];
+  SHA256_CTX ctx;
+
+  RAND_bytes(shared_nok, SIKEp503_MSG_BYTESZ);
+
+  // Recover m
+  // Let ciphertext = c0 || c1 - both have fixed sizes
+  // m = F(j-invariant(c0, priv_key)) ^ c1
+  ex_iso_B(priv_key, ciphertext, j);
+
+  SHA256_Init(&ctx);
+  SHA256_Update(&ctx, j, sizeof(j));
+  SHA256_Final(secret, &ctx);
+  hmac_sum(secret, SIKEp503_MSG_BYTESZ, F, secret);
+
+  const uint8_t *c1 = &ciphertext[sizeof(c0)];
+  for (size_t i = 0; i < SIKEp503_MSG_BYTESZ; i++) {
+    temp[i] = c1[i] ^ secret[i];
+  }
+
+  SHA256_Init(&ctx);
+  SHA256_Update(&ctx, temp, SIKEp503_MSG_BYTESZ);
+  SHA256_Update(&ctx, pub_key, SIKEp503_PUB_BYTESZ);
+  SHA256_Final(secret, &ctx);
+  hmac_sum(secret, BITS_TO_BYTES(SIDHp503_PRV_A_BITSZ), G, secret);
+
+  // Recover secret key A = G(m||pub_key) mod
+  secret[BITS_TO_BYTES(SIDHp503_PRV_A_BITSZ) - 1] &=
+      (1 << (SIDHp503_PRV_A_BITSZ % 8)) - 1;
+
+  // Recover c0 = public key A
+  gen_iso_A(secret, c0);
+  crypto_word_t ok = constant_time_is_zero_w(
+      CRYPTO_memcmp(c0, ciphertext, SIKEp503_PUB_BYTESZ));
+  for (size_t i = 0; i < SIKEp503_MSG_BYTESZ; i++) {
+    temp[i] = constant_time_select_8(ok, temp[i], shared_nok[i]);
+  }
+
+  SHA256_Init(&ctx);
+  SHA256_Update(&ctx, temp, SIKEp503_MSG_BYTESZ);
+  SHA256_Update(&ctx, ciphertext, SIKEp503_CT_BYTESZ);
+  SHA256_Final(secret, &ctx);
+  hmac_sum(out_shared_key, SIKEp503_SS_BYTESZ, H, secret);
+}
diff --git a/third_party/sike/sike.h b/third_party/sike/sike.h
new file mode 100644
index 0000000..09093cd
--- /dev/null
+++ b/third_party/sike/sike.h
@@ -0,0 +1,64 @@
+/********************************************************************************************
+* SIDH: an efficient supersingular isogeny cryptography library
+*
+* Abstract: API header file for SIKE
+*********************************************************************************************/
+
+#ifndef SIKE_H_
+#define SIKE_H_
+
+#include <stdint.h>
+#include <openssl/base.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* SIKEp503
+ *
+ * SIKE is a isogeny based post-quantum key encapsulation mechanism. Description of the
+ * algorithm is provided in [SIKE]. This implementation uses 503-bit field size. The code
+ * is based on "Additional_Implementations" from PQC NIST submission package which can
+ * be found here:
+ * https://csrc.nist.gov/CSRC/media/Projects/Post-Quantum-Cryptography/documents/round-1/submissions/SIKE.zip
+ *
+ * [SIKE] https://sike.org/files/SIDH-spec.pdf
+ */
+
+// SIKEp503_PUB_BYTESZ is the number of bytes in a public key.
+#define SIKEp503_PUB_BYTESZ 378
+// SIKEp503_PRV_BYTESZ is the number of bytes in a private key.
+#define SIKEp503_PRV_BYTESZ 32
+// SIKEp503_SS_BYTESZ is the number of bytes in a shared key.
+#define SIKEp503_SS_BYTESZ  16
+// SIKEp503_MSG_BYTESZ is the number of bytes in a random bit string concatenated
+// with the public key (see 1.4 of SIKE).
+#define SIKEp503_MSG_BYTESZ 24
+// SIKEp503_SS_BYTESZ is the number of bytes in a ciphertext.
+#define SIKEp503_CT_BYTESZ  (SIKEp503_PUB_BYTESZ + SIKEp503_MSG_BYTESZ)
+
+// SIKE_keypair outputs a public and secret key. Internally it uses BN_rand() as
+// an entropy source. In case of success function returns 1, otherwise 0.
+OPENSSL_EXPORT int SIKE_keypair(
+    uint8_t out_priv[SIKEp503_PRV_BYTESZ],
+    uint8_t out_pub[SIKEp503_PUB_BYTESZ]);
+
+// SIKE_encaps generates and encrypts a random session key, writing those values to
+// |out_shared_key| and |out_ciphertext|, respectively.
+OPENSSL_EXPORT void SIKE_encaps(
+    uint8_t out_shared_key[SIKEp503_SS_BYTESZ],
+    uint8_t out_ciphertext[SIKEp503_CT_BYTESZ],
+    const uint8_t pub_key[SIKEp503_PUB_BYTESZ]);
+
+// SIKE_decaps outputs a random session key, writing it to |out_shared_key|.
+OPENSSL_EXPORT void SIKE_decaps(
+    uint8_t out_shared_key[SIKEp503_SS_BYTESZ],
+    const uint8_t ciphertext[SIKEp503_CT_BYTESZ],
+    const uint8_t pub_key[SIKEp503_PUB_BYTESZ],
+    const uint8_t priv_key[SIKEp503_PRV_BYTESZ]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/third_party/sike/sike_test.cc b/third_party/sike/sike_test.cc
new file mode 100644
index 0000000..a1426ef
--- /dev/null
+++ b/third_party/sike/sike_test.cc
@@ -0,0 +1,208 @@
+/* Copyright (c) 2018, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include <stdint.h>
+
+#include <gtest/gtest.h>
+
+#include "../../crypto/test/abi_test.h"
+#include "sike.h"
+#include "fpx.h"
+
+TEST(SIKE, RoundTrip) {
+    uint8_t sk[SIKEp503_PRV_BYTESZ] = {0};
+    uint8_t pk[SIKEp503_PUB_BYTESZ] = {0};
+    uint8_t ct[SIKEp503_CT_BYTESZ] = {0};
+    uint8_t ss_enc[SIKEp503_SS_BYTESZ] = {0};
+    uint8_t ss_dec[SIKEp503_SS_BYTESZ] = {0};
+
+    EXPECT_EQ(SIKE_keypair(sk, pk), 1);
+    SIKE_encaps(ss_enc, ct, pk);
+    SIKE_decaps(ss_dec, ct, pk, sk);
+
+    EXPECT_EQ(memcmp(ss_enc, ss_dec, SIKEp503_SS_BYTESZ), 0);
+}
+
+TEST(SIKE, Decapsulation) {
+    const uint8_t sk[SIKEp503_PRV_BYTESZ] = {
+        0xDB, 0xAF, 0x2C, 0x89, 0xCA, 0x5A, 0xD4, 0x9D, 0x4F, 0x13,
+        0x40, 0xDF, 0x2D, 0xB1, 0x5F, 0x4C, 0x91, 0xA7, 0x1F, 0x0B,
+        0x29, 0x15, 0x01, 0x59, 0xBC, 0x5F, 0x0B, 0x4A, 0x03, 0x27,
+        0x6F, 0x18};
+
+    const uint8_t pk[SIKEp503_PUB_BYTESZ] = {
+        0x07, 0xAA, 0x51, 0x45, 0x3E, 0x1F, 0x53, 0x2A, 0x0A, 0x05,
+        0x46, 0xF6, 0x54, 0x7F, 0x5D, 0x56, 0xD6, 0x76, 0xD3, 0xEA,
+        0x4B, 0x6B, 0x01, 0x9B, 0x11, 0x72, 0x6F, 0x75, 0xEA, 0x34,
+        0x3C, 0x28, 0x2C, 0x36, 0xFD, 0x77, 0xDA, 0xBE, 0xB6, 0x20,
+        0x18, 0xC1, 0x93, 0x98, 0x18, 0x86, 0x30, 0x2F, 0x2E, 0xD2,
+        0x00, 0x61, 0xFF, 0xAE, 0x78, 0xAE, 0xFB, 0x6F, 0x32, 0xAC,
+        0x06, 0xBF, 0x35, 0xF6, 0xF7, 0x5B, 0x98, 0x26, 0x95, 0xC2,
+        0xD8, 0xD6, 0x1C, 0x0E, 0x47, 0xDA, 0x76, 0xCE, 0xB5, 0xF1,
+        0x19, 0xCC, 0x01, 0xE1, 0x17, 0xA9, 0x62, 0xF7, 0x82, 0x6C,
+        0x25, 0x51, 0x25, 0xAE, 0xFE, 0xE3, 0xE2, 0xE1, 0x35, 0xAE,
+        0x2E, 0x8F, 0x38, 0xE0, 0x7C, 0x74, 0x3C, 0x1D, 0x39, 0x91,
+        0x1B, 0xC7, 0x9F, 0x8E, 0x33, 0x4E, 0x84, 0x19, 0xB8, 0xD9,
+        0xC2, 0x71, 0x35, 0x02, 0x47, 0x3E, 0x79, 0xEF, 0x47, 0xE1,
+        0xD8, 0x21, 0x96, 0x1F, 0x11, 0x59, 0x39, 0x34, 0x76, 0xEF,
+        0x3E, 0xB7, 0x4E, 0xFB, 0x7C, 0x55, 0xA1, 0x85, 0xAA, 0xAB,
+        0xAD, 0xF0, 0x09, 0xCB, 0xD1, 0xE3, 0x7C, 0x4F, 0x5D, 0x2D,
+        0xE1, 0x13, 0xF0, 0x71, 0xD9, 0xE5, 0xF6, 0xAF, 0x7F, 0xC1,
+        0x27, 0x95, 0x8D, 0x52, 0xD5, 0x96, 0x42, 0x38, 0x41, 0xF7,
+        0x24, 0x3F, 0x3A, 0xB5, 0x7E, 0x11, 0xE4, 0xF9, 0x33, 0xEE,
+        0x4D, 0xBE, 0x74, 0x48, 0xF9, 0x98, 0x04, 0x01, 0x16, 0xEB,
+        0xA9, 0x0D, 0x61, 0xC6, 0xFD, 0x4C, 0xCF, 0x98, 0x84, 0x4A,
+        0x94, 0xAC, 0x69, 0x2C, 0x02, 0x8B, 0xE3, 0xD1, 0x41, 0x0D,
+        0xF2, 0x2D, 0x46, 0x1F, 0x57, 0x1C, 0x77, 0x86, 0x18, 0xE3,
+        0x63, 0xDE, 0xF3, 0xE3, 0x02, 0x30, 0x54, 0x73, 0xAE, 0xC2,
+        0x32, 0xA2, 0xCE, 0xEB, 0xCF, 0x81, 0x46, 0x54, 0x5C, 0xF4,
+        0x5D, 0x2A, 0x03, 0x5D, 0x9C, 0xAE, 0xE0, 0x60, 0x03, 0x80,
+        0x11, 0x30, 0xA5, 0xAA, 0xD1, 0x75, 0x67, 0xE0, 0x1C, 0x2B,
+        0x6B, 0x5D, 0x83, 0xDE, 0x92, 0x9B, 0x0E, 0xD7, 0x11, 0x0F,
+        0x00, 0xC4, 0x59, 0xE4, 0x81, 0x04, 0x3B, 0xEE, 0x5C, 0x04,
+        0xD1, 0x0E, 0xD0, 0x67, 0xF5, 0xCC, 0xAA, 0x72, 0x73, 0xEA,
+        0xC4, 0x76, 0x99, 0x3B, 0x4C, 0x90, 0x2F, 0xCB, 0xD8, 0x0A,
+        0x5B, 0xEC, 0x0E, 0x0E, 0x1F, 0x59, 0xEA, 0x14, 0x8D, 0x34,
+        0x53, 0x65, 0x4C, 0x1A, 0x59, 0xA8, 0x95, 0x66, 0x60, 0xBB,
+        0xC4, 0xCC, 0x32, 0xA9, 0x8D, 0x2A, 0xAA, 0x14, 0x6F, 0x0F,
+        0x81, 0x4D, 0x32, 0x02, 0xFD, 0x33, 0x58, 0x42, 0xCF, 0xF3,
+        0x67, 0xD0, 0x9F, 0x0B, 0xB1, 0xCC, 0x18, 0xA5, 0xC4, 0x19,
+        0xB6, 0x00, 0xED, 0xFA, 0x32, 0x1A, 0x5F, 0x67, 0xC8, 0xC3,
+        0xEB, 0x0D, 0xB5, 0x9A, 0x36, 0x47, 0x82, 0x00};
+
+    const uint8_t ct_exp[SIKEp503_CT_BYTESZ] = {
+        0xE6, 0xB7, 0xE5, 0x7B, 0xA9, 0x19, 0xD1, 0x2C, 0xB8, 0x5C,
+        0x7B, 0x66, 0x74, 0xB0, 0x71, 0xA1, 0xFF, 0x71, 0x7F, 0x4B,
+        0xB5, 0xA6, 0xAF, 0x48, 0x32, 0x52, 0xD5, 0x82, 0xEE, 0x8A,
+        0xBB, 0x08, 0x1E, 0xF6, 0xAC, 0x91, 0xA2, 0xCB, 0x6B, 0x6A,
+        0x09, 0x2B, 0xD9, 0xC6, 0x27, 0xD6, 0x3A, 0x6B, 0x8D, 0xFC,
+        0xB8, 0x90, 0x8F, 0x72, 0xB3, 0xFA, 0x7D, 0x34, 0x7A, 0xC4,
+        0x7E, 0xE3, 0x30, 0xC5, 0xA0, 0xFE, 0x3D, 0x43, 0x14, 0x4E,
+        0x3A, 0x14, 0x76, 0x3E, 0xFB, 0xDF, 0xE3, 0xA8, 0xE3, 0x5E,
+        0x38, 0xF2, 0xE0, 0x39, 0x67, 0x60, 0xFD, 0xFB, 0xB4, 0x19,
+        0xCD, 0xE1, 0x93, 0xA2, 0x06, 0xCC, 0x65, 0xCD, 0x6E, 0xC8,
+        0xB4, 0x5E, 0x41, 0x4B, 0x6C, 0xA5, 0xF4, 0xE4, 0x9D, 0x52,
+        0x8C, 0x25, 0x60, 0xDD, 0x3D, 0xA9, 0x7F, 0xF2, 0x88, 0xC1,
+        0x0C, 0xEE, 0x97, 0xE0, 0xE7, 0x3B, 0xB7, 0xD3, 0x6F, 0x28,
+        0x79, 0x2F, 0x50, 0xB2, 0x4F, 0x74, 0x3A, 0x0C, 0x88, 0x27,
+        0x98, 0x3A, 0x27, 0xD3, 0x26, 0x83, 0x59, 0x49, 0x81, 0x5B,
+        0x0D, 0xA7, 0x0C, 0x4F, 0xEF, 0xFB, 0x1E, 0xAF, 0xE9, 0xD2,
+        0x1C, 0x10, 0x25, 0xEC, 0x9E, 0xFA, 0x57, 0x36, 0xAA, 0x3F,
+        0xC1, 0xA3, 0x2C, 0xE9, 0xB5, 0xC9, 0xED, 0x72, 0x51, 0x4C,
+        0x02, 0xB4, 0x7B, 0xB3, 0xED, 0x9F, 0x45, 0x03, 0x34, 0xAC,
+        0x9A, 0x9E, 0x62, 0x5F, 0x82, 0x7A, 0x77, 0x34, 0xF9, 0x21,
+        0x94, 0xD2, 0x38, 0x3D, 0x05, 0xF0, 0x8A, 0x60, 0x1C, 0xB7,
+        0x1D, 0xF5, 0xB7, 0x53, 0x77, 0xD3, 0x9D, 0x3D, 0x70, 0x6A,
+        0xCB, 0x18, 0x20, 0x6B, 0x29, 0x17, 0x3A, 0x6D, 0xA1, 0xB2,
+        0x64, 0xDB, 0x6C, 0xE6, 0x1A, 0x95, 0xA7, 0xF4, 0x1A, 0x78,
+        0x1D, 0xA2, 0x40, 0x15, 0x41, 0x59, 0xDD, 0xEE, 0x23, 0x57,
+        0xCE, 0x36, 0x0D, 0x55, 0xBD, 0xB8, 0xFD, 0x0F, 0x35, 0xBD,
+        0x5B, 0x92, 0xD6, 0x1C, 0x84, 0x8C, 0x32, 0x64, 0xA6, 0x5C,
+        0x45, 0x18, 0x07, 0x6B, 0xF9, 0xA9, 0x43, 0x9A, 0x83, 0xCD,
+        0xB5, 0xB3, 0xD9, 0x17, 0x99, 0x2C, 0x2A, 0x8B, 0xE0, 0x8E,
+        0xAF, 0xA6, 0x4C, 0x95, 0xBB, 0x70, 0x60, 0x1A, 0x3A, 0x97,
+        0xAA, 0x2F, 0x3D, 0x22, 0x83, 0xB7, 0x4F, 0x59, 0xED, 0x3F,
+        0x4E, 0xF4, 0x19, 0xC6, 0x25, 0x0B, 0x0A, 0x5E, 0x21, 0xB9,
+        0x91, 0xB8, 0x19, 0x84, 0x48, 0x78, 0xCE, 0x27, 0xBF, 0x41,
+        0x89, 0xF6, 0x30, 0xFD, 0x6B, 0xD9, 0xB8, 0x1D, 0x72, 0x8A,
+        0x56, 0xCC, 0x2F, 0x82, 0xE4, 0x46, 0x4D, 0x75, 0xD8, 0x92,
+        0xE6, 0x9C, 0xCC, 0xD2, 0xCD, 0x35, 0xE4, 0xFC, 0x2A, 0x85,
+        0x6B, 0xA9, 0xB2, 0x27, 0xC9, 0xA1, 0xFF, 0xB3, 0x96, 0x3E,
+        0x59, 0xF6, 0x4C, 0x66, 0x56, 0x2E, 0xF5, 0x1B, 0x97, 0x32,
+        0xB0, 0x71, 0x5A, 0x9C, 0x50, 0x4B, 0x6F, 0xC4, 0xCA, 0x94,
+        0x75, 0x37, 0x46, 0x10, 0x12, 0x2F, 0x4F, 0xA3, 0x82, 0xCD,
+        0xBD, 0x7C};
+
+    const uint8_t ss_exp[SIKEp503_SS_BYTESZ] = {
+        0x74, 0x3D, 0x25, 0x36, 0x00, 0x24, 0x63, 0x1A, 0x39, 0x1A,
+        0xB4, 0xAD, 0x01, 0x17, 0x78, 0xE9};
+
+    uint8_t ss_dec[SIKEp503_SS_BYTESZ] = {0};
+    SIKE_decaps(ss_dec, ct_exp, pk, sk);
+    EXPECT_EQ(memcmp(ss_dec, ss_exp, sizeof(ss_exp)), 0);
+}
+
+// SIKE_encaps and SIKE_keypair doesn't return zeros.
+TEST(SIKE, NonZero) {
+    uint8_t sk[SIKEp503_PRV_BYTESZ] = {0};
+    uint8_t pk[SIKEp503_PUB_BYTESZ] = {0};
+    uint8_t ct[SIKEp503_CT_BYTESZ] = {0};
+    uint8_t ss[SIKEp503_SS_BYTESZ] = {0};
+
+    // Check secret and public key returned by SIKE_keypair
+    EXPECT_EQ(SIKE_keypair(sk, pk), 1);
+    uint8_t tmp = 0;
+    for (size_t i=0; i<sizeof(sk); i++) tmp|=sk[i];
+    EXPECT_NE(tmp, 0);
+
+    tmp = 0;
+    for (size_t i=0; i<sizeof(pk); i++) tmp|=pk[i];
+    EXPECT_NE(tmp, 0);
+
+    // Check shared secret and ciphertext returned by SIKE_encaps
+    SIKE_encaps(ss, ct, pk);
+    tmp = 0;
+    for (size_t i=0; i<sizeof(ct); i++) tmp|=ct[i];
+    EXPECT_NE(tmp, 0);
+
+    tmp = 0;
+    for (size_t i=0; i<sizeof(ss); i++) tmp|=ss[i];
+    EXPECT_NE(tmp, 0);
+}
+
+TEST(SIKE, Negative) {
+    uint8_t sk[SIKEp503_PRV_BYTESZ] = {0};
+    uint8_t pk[SIKEp503_PUB_BYTESZ] = {0};
+    uint8_t ct[SIKEp503_CT_BYTESZ] = {0};
+    uint8_t ss_enc[SIKEp503_SS_BYTESZ] = {0};
+    uint8_t ss_dec[SIKEp503_SS_BYTESZ] = {0};
+
+    EXPECT_EQ(SIKE_keypair(sk, pk), 1);
+    SIKE_encaps(ss_enc, ct, pk);
+
+    // Change cipertext
+    uint8_t ct_tmp[SIKEp503_CT_BYTESZ] = {0};
+    memcpy(ct_tmp, ct, sizeof(ct));
+    ct_tmp[0] = ~ct_tmp[0];
+    SIKE_decaps(ss_dec, ct_tmp, pk, sk);
+    EXPECT_NE(memcmp(ss_enc, ss_dec, SIKEp503_SS_BYTESZ), 0);
+
+    // Change secret key
+    uint8_t sk_tmp[SIKEp503_PRV_BYTESZ] = {0};
+    memcpy(sk_tmp, sk, sizeof(sk));
+    sk_tmp[0] = ~sk_tmp[0];
+    SIKE_decaps(ss_dec, ct, pk, sk_tmp);
+    EXPECT_NE(memcmp(ss_enc, ss_dec, SIKEp503_SS_BYTESZ), 0);
+
+    // Change public key
+    uint8_t pk_tmp[SIKEp503_PUB_BYTESZ] = {0};
+    memcpy(pk_tmp, pk, sizeof(pk));
+    pk_tmp[0] = ~pk_tmp[0];
+    SIKE_decaps(ss_dec, ct, pk_tmp, sk);
+    EXPECT_NE(memcmp(ss_enc, ss_dec, SIKEp503_SS_BYTESZ), 0);
+}
+
+#if defined(SUPPORTS_ABI_TEST) && (defined(OPENSSL_X86_64) || defined(OPENSSL_AARCH64))
+TEST(SIKE, ABI) {
+  felm_t a, b, c;
+  dfelm_t d, e, f;
+  CHECK_ABI(sike_fpadd, a, b, c);
+  CHECK_ABI(sike_fpsub, a, b, c);
+  CHECK_ABI(sike_mpmul, a, b, d);
+  CHECK_ABI(sike_fprdc, d, a);
+  CHECK_ABI(sike_mpadd_asm, a, b, c);
+  CHECK_ABI(sike_mpsubx2_asm, d, e, f);
+  CHECK_ABI(sike_mpdblsubx2_asm, d, e, f);
+}
+#endif  // SUPPORTS_ABI_TEST && (X86_64 || AARCH64)
diff --git a/third_party/sike/utils.h b/third_party/sike/utils.h
new file mode 100644
index 0000000..ee9e685
--- /dev/null
+++ b/third_party/sike/utils.h
@@ -0,0 +1,144 @@
+/********************************************************************************************
+* SIDH: an efficient supersingular isogeny cryptography library
+*
+* Abstract: internal header file for P503
+*********************************************************************************************/
+
+#ifndef UTILS_H_
+#define UTILS_H_
+
+#include <stdbool.h>
+#include "openssl/base.h"
+#include "../crypto/internal.h"
+#include "sike.h"
+
+// Conversion macro from number of bits to number of bytes
+#define BITS_TO_BYTES(nbits)      (((nbits)+7)/8)
+
+// Bit size of the field
+#define BITS_FIELD             503
+// Byte size of the field
+#define FIELD_BYTESZ            BITS_TO_BYTES(BITS_FIELD)
+// Number of 64-bit words of a 503-bit field element
+#define NWORDS64_FIELD          ((BITS_FIELD+63)/64)
+// Number of 64-bit words of a 256-bit element
+#define NBITS_ORDER             256
+#define NWORDS64_ORDER          ((NBITS_ORDER+63)/64)
+// Number of elements in Alice's strategy
+#define A_max                   125
+// Number of elements in Bob's strategy
+#define B_max                   159
+// Word size size
+#define RADIX                   sizeof(crypto_word_t)*8
+// Byte size of a limb
+#define LSZ                     sizeof(crypto_word_t)
+
+#if defined(OPENSSL_64_BIT)
+    // Number of words of a 503-bit field element
+    #define NWORDS_FIELD    8
+    // Number of "0" digits in the least significant part of p503 + 1
+    #define p503_ZERO_WORDS 3
+    // log_2(RADIX)
+    #define LOG2RADIX       6
+#else
+    // Number of words of a 503-bit field element
+    #define NWORDS_FIELD    16
+    // Number of "0" digits in the least significant part of p503 + 1
+    #define p503_ZERO_WORDS 7
+    // log_2(RADIX)
+    #define LOG2RADIX       5
+#endif
+
+// Extended datatype support
+#if !defined(BORINGSSL_HAS_UINT128)
+    typedef uint64_t uint128_t[2];
+#endif
+
+// The following functions return 1 (TRUE) if condition is true, 0 (FALSE) otherwise
+// Digit multiplication
+#define MUL(multiplier, multiplicand, hi, lo) digit_x_digit((multiplier), (multiplicand), &(lo));
+
+// If mask |x|==0xff.ff set |x| to 1, otherwise 0
+#define M2B(x) ((x)>>(RADIX-1))
+
+// Digit addition with carry
+#define ADDC(carryIn, addend1, addend2, carryOut, sumOut)                   \
+do {                                                                        \
+  crypto_word_t tempReg = (addend1) + (crypto_word_t)(carryIn);             \
+  (sumOut) = (addend2) + tempReg;                                           \
+  (carryOut) = M2B(constant_time_lt_w(tempReg, (crypto_word_t)(carryIn)) |  \
+                   constant_time_lt_w((sumOut), tempReg));                  \
+} while(0)
+
+// Digit subtraction with borrow
+#define SUBC(borrowIn, minuend, subtrahend, borrowOut, differenceOut)           \
+do {                                                                            \
+    crypto_word_t tempReg = (minuend) - (subtrahend);                           \
+    crypto_word_t borrowReg = M2B(constant_time_lt_w((minuend), (subtrahend))); \
+    borrowReg |= ((borrowIn) & constant_time_is_zero_w(tempReg));               \
+    (differenceOut) = tempReg - (crypto_word_t)(borrowIn);                      \
+    (borrowOut) = borrowReg;                                                    \
+} while(0)
+
+/* Old GCC 4.9 (jessie) doesn't implement {0} initialization properly,
+   which violates C11 as described in 6.7.9, 21 (similarily C99, 6.7.8).
+   Defines below are used to work around the bug, and provide a way
+   to initialize f2elem_t and point_proj_t structs.
+   Bug has been fixed in GCC6 (debian stretch).
+*/
+#define F2ELM_INIT {{ {0}, {0} }}
+#define POINT_PROJ_INIT {{ F2ELM_INIT, F2ELM_INIT }}
+
+// Datatype for representing 503-bit field elements (512-bit max.)
+// Elements over GF(p503) are encoded in 63 octets in little endian format
+// (i.e., the least significant octet is located in the lowest memory address).
+typedef crypto_word_t felm_t[NWORDS_FIELD];
+
+// An element in F_{p^2}, is composed of two coefficients from F_p, * i.e.
+// Fp2 element = c0 + c1*i in F_{p^2}
+// Datatype for representing double-precision 2x503-bit field elements (512-bit max.)
+// Elements (a+b*i) over GF(p503^2), where a and b are defined over GF(p503), are
+// encoded as {a, b}, with a in the lowest memory portion.
+typedef struct {
+    felm_t c0;
+    felm_t c1;
+} fp2;
+
+// Our F_{p^2} element type is a pointer to the struct.
+typedef fp2 f2elm_t[1];
+
+// Datatype for representing double-precision 2x503-bit
+// field elements in contiguous memory.
+typedef crypto_word_t dfelm_t[2*NWORDS_FIELD];
+
+// Constants used during SIKEp503 computation.
+struct params_t {
+    // Stores P503 prime
+    const uint64_t prime[NWORDS64_FIELD];
+    // Stores P503 + 1
+    const uint64_t prime_p1[NWORDS64_FIELD];
+    // Stores P503 * 2
+    const uint64_t prime_x2[NWORDS64_FIELD];
+    // Alice's generator values {XPA0 + XPA1*i, XQA0, XRA0 + XRA1*i}
+    // in GF(p503^2), expressed in Montgomery representation
+    const uint64_t A_gen[5*NWORDS64_FIELD];
+    // Bob's generator values {XPB0 + XPB1*i, XQB0, XRB0 + XRB1*i}
+    // in GF(p503^2), expressed in Montgomery representation
+    const uint64_t B_gen[5*NWORDS64_FIELD];
+    // Montgomery constant mont_R2 = (2^512)^2 mod p503
+    const uint64_t mont_R2[NWORDS64_FIELD];
+    // Value 'one' in Montgomery representation
+    const uint64_t mont_one[NWORDS64_FIELD];
+    // Fixed parameters for isogeny tree computation
+    const unsigned int A_strat[A_max-1];
+    const unsigned int B_strat[B_max-1];
+};
+
+// Point representation in projective XZ Montgomery coordinates.
+typedef struct {
+    f2elm_t X;
+    f2elm_t Z;
+} point_proj;
+typedef point_proj point_proj_t[1];
+
+#endif // UTILS_H_
diff --git a/tool/speed.cc b/tool/speed.cc
index b26470e..47edc75 100644
--- a/tool/speed.cc
+++ b/tool/speed.cc
@@ -51,6 +51,8 @@
 #include "../crypto/internal.h"
 #include "internal.h"
 
+#include "../third_party/sike/sike.h"
+
 
 // TimeResults represents the results of benchmarking a function.
 struct TimeResults {
@@ -294,6 +296,64 @@
   return true;
 }
 
+static bool SpeedSIKEP503(const std::string &selected) {
+  if (!selected.empty() && selected.find("SIKE") == std::string::npos) {
+    return true;
+  }
+  // speed generation
+  uint8_t public_SIKE[SIKEp503_PUB_BYTESZ];
+  uint8_t private_SIKE[SIKEp503_PRV_BYTESZ];
+  uint8_t ct[SIKEp503_CT_BYTESZ];
+  bool res;
+
+  {
+    TimeResults results;
+    res = TimeFunction(&results,
+                [&private_SIKE, &public_SIKE]() -> bool {
+      return (SIKE_keypair(private_SIKE, public_SIKE) == 1);
+    });
+    results.Print("SIKE/P503 generate");
+  }
+
+  if (!res) {
+    fprintf(stderr, "Failed to time SIKE_keypair.\n");
+    return false;
+  }
+
+  {
+    TimeResults results;
+    TimeFunction(&results,
+                [&ct, &public_SIKE]() -> bool {
+      uint8_t ss[SIKEp503_SS_BYTESZ];
+      SIKE_encaps(ss, ct, public_SIKE);
+      return true;
+    });
+    results.Print("SIKE/P503 encap");
+  }
+
+  if (!res) {
+    fprintf(stderr, "Failed to time SIKE_encaps.\n");
+    return false;
+  }
+
+  {
+    TimeResults results;
+    TimeFunction(&results,
+                [&ct, &public_SIKE, &private_SIKE]() -> bool {
+      uint8_t ss[SIKEp503_SS_BYTESZ];
+      SIKE_decaps(ss, ct, public_SIKE, private_SIKE);
+      return true;
+    });
+    results.Print("SIKE/P503 decap");
+  }
+
+  if (!res) {
+    fprintf(stderr, "Failed to time SIKE_decaps.\n");
+    return false;
+  }
+  return true;
+}
+
 static uint8_t *align(uint8_t *in, unsigned alignment) {
   return reinterpret_cast<uint8_t *>(
       (reinterpret_cast<uintptr_t>(in) + alignment) &
@@ -938,6 +998,7 @@
       !SpeedECDH(selected) ||
       !SpeedECDSA(selected) ||
       !Speed25519(selected) ||
+      !SpeedSIKEP503(selected) ||
       !SpeedSPAKE2(selected) ||
       !SpeedScrypt(selected) ||
       !SpeedRSAKeyGen(selected) ||