├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── android ├── .gitignore ├── build.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── CMakeLists_CurrentPlatform.cmake │ │ └── spake2_jni.cpp │ └── java │ │ └── io │ │ └── github │ │ └── muntashirakon │ │ └── crypto │ │ └── spake2 │ │ ├── Spake2Context.java │ │ └── Spake2Role.java │ └── test │ └── java │ └── io │ └── github │ └── muntashirakon │ └── crypto │ └── spake2 │ ├── Spake2ContextTest.java │ └── Utils.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── java ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── github │ │ └── muntashirakon │ │ └── crypto │ │ ├── ed25519 │ │ ├── Curve.java │ │ ├── Ed25519.java │ │ ├── Ed25519CurveParameterSpec.java │ │ ├── Ed25519Field.java │ │ ├── Ed25519FieldElement.java │ │ ├── Ed25519LittleEndianEncoding.java │ │ ├── Ed25519ScalarOps.java │ │ ├── FieldElement.java │ │ ├── GroupElement.java │ │ └── Utils.java │ │ └── spake2 │ │ ├── Spake2Context.java │ │ └── Spake2Role.java │ └── test │ └── java │ └── io │ └── github │ └── muntashirakon │ └── crypto │ └── spake2 │ └── Spake25519Test.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .gradle/ 3 | .DS_Store 4 | build/ 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "android/src/main/cpp/spake2-c"] 2 | path = android/src/main/cpp/spake2-c 3 | url = https://github.com/MuntashirAkon/spake2-c.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SPAKE2-Java 2 | 3 | Implementation of SPAKE2 protocol in Java, fully compatible with BoringSSL implementation. 4 | 5 | **DISCLAIMER:** This is an unaudited library tested with Android 11 wireless debugging. Use it at your own risk. 6 | 7 | The ultimate goal of this project is to provide both pure java and (for android) JNI implementation of SPAKE2. 8 | The project is fully usable as of now, but it requires further optimizations. 9 | 10 | ## Get Started 11 | import `io.github.muntashirakon.crypto.spake2.*` in your project. Do not import any other files from 12 | `io.github.muntashirakon.crypto` as there's no guarantee that they will not be modified in the future. 13 | 14 | ```java 15 | // Create alice and bob 16 | Spake2Context alice = new Spake2Context(Spake2Role.Alice, "alice", "bob"); 17 | Spake2Context bob = new Spake2Context(Spake2Role.Bob, "bob", "alice"); 18 | // The below methods are kept for compatibility with BoringSSL 19 | // alice.setDisablePasswordScalarHack(true); 20 | // bob.setDisablePasswordScalarHack(true); 21 | // Messages 22 | byte[] aliceMsg = alice.generateMessage(alicePassword.getBytes(StandardCharsets.UTF_8)); 23 | byte[] bobMsg = bob.generateMessage(bobPassword.getBytes(StandardCharsets.UTF_8)); 24 | // Fetch keys 25 | byte[] aliceKey = alice.processMessage(bobMsg); 26 | byte[] bobKey = bob.processMessage(aliceMsg); 27 | ``` 28 | 29 | ### Notice for BoringSSL Users 30 | Beware that strings in C/C++ adds a `NULL` character (i.e. `\u0000`) at the end, so if you've accidentally used 31 | `sizeof(my_name)` instead of `sizeof(my_name)-1`, you have to make sure that you're adding this character to your 32 | Java implementation too. 33 | 34 | ## Credits 35 | 36 | ED25519 implementation is a modified and a simplified version of the [EdDSA-Java](https://github.com/str4d/ed25519-java) library (CC0 license). 37 | 38 | ## License 39 | Copyright 2021 Muntashir Al-Islam 40 | 41 | Licensed under the LGPL-3.0: http://www.gnu.org/licenses/lgpl-3.0.html 42 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /.cxx -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | plugins { 8 | id 'com.android.library' 9 | id 'maven-publish' 10 | } 11 | 12 | android { 13 | compileSdkVersion 31 14 | buildToolsVersion "30.0.3" 15 | 16 | defaultConfig { 17 | minSdkVersion 1 18 | targetSdkVersion 31 19 | versionCode 1 20 | versionName "2.0.1" 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | } 27 | } 28 | externalNativeBuild { 29 | cmake { 30 | path 'src/main/cpp/CMakeLists.txt' 31 | } 32 | } 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | } 38 | 39 | afterEvaluate { 40 | publishing { 41 | publications { 42 | release(MavenPublication) { 43 | from components.release 44 | } 45 | } 46 | } 47 | } 48 | 49 | dependencies { 50 | implementation "androidx.annotation:annotation:1.3.0" 51 | 52 | testImplementation 'junit:junit:4.13.2' 53 | // testImplementation 'org.robolectric:robolectric:4.6.1' 54 | } -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /android/src/main/cpp/.gitignore: -------------------------------------------------------------------------------- 1 | CMakeFiles 2 | cmake_install.cmake 3 | CMakeCache.txt 4 | Makefile 5 | *.dylib 6 | *.so 7 | *.dll 8 | -------------------------------------------------------------------------------- /android/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.1) 2 | 3 | project(spake2) 4 | 5 | if (CURRENT_PLATFORM) 6 | include(${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists_CurrentPlatform.cmake) 7 | return () 8 | endif () 9 | 10 | set(CMAKE_CXX_STANDARD 17) 11 | 12 | set(C_FLAGS "-Werror=format -fdata-sections -ffunction-sections -fno-exceptions -fno-rtti -fno-threadsafe-statics") 13 | set(LINKER_FLAGS "-Wl,--hash-style=both") 14 | 15 | if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") 16 | message("Builing Release...") 17 | 18 | set(C_FLAGS "${C_FLAGS} -O2 -fvisibility=hidden -fvisibility-inlines-hidden") 19 | set(LINKER_FLAGS "${LINKER_FLAGS} -Wl,-exclude-libs,ALL -Wl,--gc-sections") 20 | else() 21 | message("Builing Debug...") 22 | 23 | add_definitions(-DDEBUG) 24 | endif () 25 | 26 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_FLAGS}") 27 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${C_FLAGS}") 28 | 29 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINKER_FLAGS}") 30 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${LINKER_FLAGS}") 31 | 32 | add_library(spake2 SHARED 33 | spake2-c/sha512.c 34 | spake2-c/spake2.c 35 | spake2_jni.cpp) 36 | 37 | target_include_directories(spake2 PUBLIC spake2-c/include) 38 | 39 | if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") 40 | add_custom_command(TARGET spake2 POST_BUILD 41 | COMMAND ${CMAKE_STRIP} --remove-section=.comment "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libspake2.so") 42 | endif () 43 | -------------------------------------------------------------------------------- /android/src/main/cpp/CMakeLists_CurrentPlatform.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.1) 2 | 3 | project(spake2) 4 | 5 | if(NOT DEFINED JAVA_HOME) 6 | set(JAVA_HOME "/usr/local/opt/java") 7 | endif() 8 | 9 | set(CMAKE_CXX_STANDARD 17) 10 | 11 | set(C_FLAGS "-Werror=format -fdata-sections -ffunction-sections -fno-exceptions -fno-rtti -fno-threadsafe-statics") 12 | set(LINKER_FLAGS "-Wl") 13 | 14 | if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") 15 | message("Builing Release...") 16 | 17 | set(C_FLAGS "${C_FLAGS} -O2 -fvisibility=hidden -fvisibility-inlines-hidden") 18 | else() 19 | message("Builing Debug...") 20 | 21 | add_definitions(-DDEBUG) 22 | endif () 23 | 24 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_FLAGS}") 25 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${C_FLAGS}") 26 | 27 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINKER_FLAGS}") 28 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${LINKER_FLAGS}") 29 | 30 | add_library(spake2 SHARED 31 | spake2-c/sha512.c 32 | spake2-c/spake2.c 33 | spake2_jni.cpp) 34 | 35 | target_include_directories(spake2 PUBLIC ${JAVA_HOME}/include spake2-c/include) 36 | -------------------------------------------------------------------------------- /android/src/main/cpp/spake2_jni.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #ifndef nullptr 14 | #define nullptr NULL 15 | #endif 16 | 17 | static jlong Spake2Context_AllocNewContext(JNIEnv *env, jclass clazz, jint myRole, jbyteArray myName, jbyteArray theirName) { 18 | spake2_role_t my_role = myRole == 0 ? spake2_role_alice : spake2_role_bob; 19 | auto my_len = env->GetArrayLength(myName); 20 | auto my_name = env->GetByteArrayElements(myName, nullptr); 21 | auto their_len = env->GetArrayLength(theirName); 22 | auto their_name = env->GetByteArrayElements(theirName, nullptr); 23 | 24 | struct spake2_ctx_st *ctx = SPAKE2_CTX_new(my_role, (uint8_t *) my_name, my_len, (uint8_t *) their_name, their_len); 25 | env->ReleaseByteArrayElements(myName, my_name, 0); 26 | env->ReleaseByteArrayElements(theirName, their_name, 0); 27 | if (ctx == nullptr) { 28 | printf("Couldn't create SPAKE2 context"); 29 | return 0; 30 | } 31 | return (jlong) ctx; 32 | } 33 | 34 | static jbyteArray Spake2Context_GenerateMessage(JNIEnv *env, jclass clazz, jlong ctxPtr, jbyteArray password) { 35 | struct spake2_ctx_st *ctx = (struct spake2_ctx_st *) ctxPtr; 36 | auto pswd_size = env->GetArrayLength(password); 37 | auto pswd = env->GetByteArrayElements(password, nullptr); 38 | size_t msg_size = 0; 39 | uint8_t msg[SPAKE2_MAX_MSG_SIZE]; 40 | int status = SPAKE2_generate_msg(ctx, msg, &msg_size, SPAKE2_MAX_MSG_SIZE, (uint8_t *) pswd, pswd_size); 41 | env->ReleaseByteArrayElements(password, pswd, 0); 42 | if (status != 1 || msg_size == 0) { 43 | printf("Couldn't generate message"); 44 | SPAKE2_CTX_free(ctx); 45 | return nullptr; 46 | } 47 | jbyteArray outMsg = env->NewByteArray(msg_size); 48 | env->SetByteArrayRegion(outMsg, 0, msg_size, (jbyte *) msg); 49 | return outMsg; 50 | } 51 | 52 | static jbyteArray Spake2Context_ProcessMessage(JNIEnv *env, jclass clazz, jlong ctxPtr, jbyteArray theirMessage) { 53 | struct spake2_ctx_st *ctx = (struct spake2_ctx_st *) ctxPtr; 54 | auto their_msg_len = env->GetArrayLength(theirMessage); 55 | auto their_msg = env->GetByteArrayElements(theirMessage, nullptr); 56 | size_t key_material_len = 0; 57 | uint8_t key_material[SPAKE2_MAX_KEY_SIZE]; 58 | int status = SPAKE2_process_msg(ctx, key_material, &key_material_len, SPAKE2_MAX_KEY_SIZE, (uint8_t *) their_msg, their_msg_len); 59 | env->ReleaseByteArrayElements(theirMessage, their_msg, 0); 60 | if (status != 1 || key_material_len == 0) { 61 | printf("Couldn't generate key"); 62 | SPAKE2_CTX_free(ctx); 63 | return nullptr; 64 | } 65 | jbyteArray outKey = env->NewByteArray(key_material_len); 66 | env->SetByteArrayRegion(outKey, 0, key_material_len, (jbyte *) key_material); 67 | return outKey; 68 | } 69 | 70 | static void Spake2Context_Destroy(JNIEnv *env, jclass clazz, jlong ctxPtr) { 71 | struct spake2_ctx_st *ctx = (struct spake2_ctx_st *) ctxPtr; 72 | SPAKE2_CTX_free(ctx); 73 | } 74 | 75 | JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { 76 | JNIEnv *env = nullptr; 77 | 78 | if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) 79 | return -1; 80 | 81 | JNINativeMethod methods_Spake2Context[] = { 82 | {"allocNewContext", "(I[B[B)J", (void *) Spake2Context_AllocNewContext}, 83 | {"generateMessage", "(J[B)[B", (void *) Spake2Context_GenerateMessage}, 84 | {"processMessage", "(J[B)[B", (void *) Spake2Context_ProcessMessage}, 85 | {"destroy", "(J)V", (void *) Spake2Context_Destroy}, 86 | }; 87 | 88 | env->RegisterNatives(env->FindClass("io/github/muntashirakon/crypto/spake2/Spake2Context"), methods_Spake2Context, 89 | sizeof(methods_Spake2Context) / sizeof(JNINativeMethod)); 90 | 91 | return JNI_VERSION_1_6; 92 | } 93 | -------------------------------------------------------------------------------- /android/src/main/java/io/github/muntashirakon/crypto/spake2/Spake2Context.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.spake2; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.annotation.Nullable; 11 | 12 | import javax.security.auth.Destroyable; 13 | 14 | public class Spake2Context implements Destroyable { 15 | static { 16 | System.loadLibrary("spake2"); 17 | } 18 | 19 | /** 20 | * Maximum message size in bytes 21 | */ 22 | public static final int MAX_MSG_SIZE = 32; 23 | /** 24 | * Maximum key size in bytes 25 | */ 26 | public static final int MAX_KEY_SIZE = 64; 27 | 28 | private final long mCtx; 29 | 30 | private final byte[] mMyMsg = new byte[MAX_MSG_SIZE]; 31 | private boolean mIsDestroyed; 32 | 33 | public Spake2Context(@NonNull Spake2Role myRole, 34 | final byte[] myName, 35 | final byte[] theirName) { 36 | mCtx = allocNewContext(myRole.ordinal(), myName, theirName); 37 | if (mCtx == 0L) { 38 | throw new UnsupportedOperationException("Could not allocate native context"); 39 | } 40 | } 41 | 42 | @NonNull 43 | public byte[] getMyMsg() { 44 | return mMyMsg; 45 | } 46 | 47 | public byte[] generateMessage(byte[] password) throws IllegalStateException { 48 | if (mIsDestroyed) { 49 | throw new IllegalStateException("The context was destroyed."); 50 | } 51 | byte[] myMsg = generateMessage(mCtx, password); 52 | if (myMsg == null) { 53 | throw new IllegalStateException("Generated empty message"); 54 | } 55 | System.arraycopy(myMsg, 0, this.mMyMsg, 0, MAX_MSG_SIZE); 56 | return myMsg; 57 | } 58 | 59 | public byte[] processMessage(byte[] theirMessage) throws IllegalStateException { 60 | if (mIsDestroyed) { 61 | throw new IllegalStateException("The context was destroyed."); 62 | } 63 | byte[] key = processMessage(mCtx, theirMessage); 64 | if (key == null) { 65 | throw new IllegalStateException("No key was returned"); 66 | } 67 | return key; 68 | } 69 | 70 | @Override 71 | public boolean isDestroyed() { 72 | return mIsDestroyed; 73 | } 74 | 75 | @Override 76 | public void destroy() { 77 | mIsDestroyed = true; 78 | destroy(mCtx); 79 | } 80 | 81 | private static native long allocNewContext(int myRole, byte[] myName, byte[] theirName); 82 | 83 | @Nullable 84 | private static native byte[] generateMessage(long ctx, byte[] password); 85 | 86 | @Nullable 87 | private static native byte[] processMessage(long ctx, byte[] theirMessage); 88 | 89 | private static native void destroy(long ctx); 90 | } 91 | -------------------------------------------------------------------------------- /android/src/main/java/io/github/muntashirakon/crypto/spake2/Spake2Role.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.spake2; 8 | 9 | public enum Spake2Role { 10 | Alice, 11 | Bob, 12 | } 13 | -------------------------------------------------------------------------------- /android/src/test/java/io/github/muntashirakon/crypto/spake2/Spake2ContextTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.spake2; 8 | 9 | import org.junit.Test; 10 | 11 | import java.nio.charset.StandardCharsets; 12 | import java.util.Arrays; 13 | 14 | import static org.junit.Assert.*; 15 | 16 | public class Spake2ContextTest { 17 | @Test 18 | public void spake2() { 19 | for (int i = 0; i < 20; i++) { 20 | System.out.println("========"); 21 | SPAKE2Run spake2 = new SPAKE2Run(); 22 | assertTrue(spake2.run()); 23 | assertTrue(spake2.keyMatches()); 24 | } 25 | } 26 | 27 | @Test 28 | public void oldAlice() { 29 | for (int i = 0; i < 20; i++) { 30 | SPAKE2Run spake2 = new SPAKE2Run(); 31 | spake2.aliceDisablePasswordScalarHack = true; 32 | assertTrue(spake2.run()); 33 | if (!spake2.keyMatches()) { 34 | System.out.printf("Iteration %d: Keys didn't match.\n", i); 35 | } 36 | } 37 | } 38 | 39 | @Test 40 | public void oldBob() { 41 | for (int i = 0; i < 20; i++) { 42 | SPAKE2Run spake2 = new SPAKE2Run(); 43 | spake2.bobDisablePasswordScalarHack = true; 44 | assertTrue(spake2.run()); 45 | if (!spake2.keyMatches()) { 46 | System.out.printf("Iteration %d: Keys didn't match.\n", i); 47 | } 48 | } 49 | } 50 | 51 | @Test 52 | public void wrongPassword() { 53 | SPAKE2Run spake2 = new SPAKE2Run(); 54 | spake2.bobPassword = "wrong password".getBytes(StandardCharsets.UTF_8); 55 | assertTrue(spake2.run()); 56 | assertFalse(spake2.keyMatches()); 57 | } 58 | 59 | @Test 60 | public void wrongNames() { 61 | SPAKE2Run spake2 = new SPAKE2Run(); 62 | spake2.aliceNames.second = "charlie"; 63 | spake2.bobNames.second = "charlie"; 64 | assertTrue(spake2.run()); 65 | assertFalse(spake2.keyMatches()); 66 | } 67 | 68 | @Test 69 | public void corruptMessages() { 70 | for (int i = 0; i < 8 * Spake2Context.MAX_MSG_SIZE; i++) { 71 | SPAKE2Run spake2 = new SPAKE2Run(); 72 | spake2.aliceCorruptMsgBit = i; 73 | assertFalse(spake2.run() && spake2.keyMatches()); 74 | } 75 | } 76 | 77 | // Based on https://android.googlesource.com/platform/external/boringssl/+/f9e0b0e17fabac35627f18f94a8954c3857784ac/src/crypto/curve25519/spake25519_test.cc 78 | private static class SPAKE2Run { 79 | private final Pair aliceNames = new Pair<>("adb pair client\u0000", "adb pair server\u0000"); 80 | private final Pair bobNames = new Pair<>("adb pair server\u0000", "adb pair client\u0000"); 81 | private final byte[] alicePassword = Utils.hexToBytes("353932373831E63DD959651C211600F3B6561D0B9D90AF09D0A4A453EE2059A480CC7C5A94D4D48933F9FFF5FE43317D52FA7BFF8F8BC4F3488B8007330FEC7C7EDC91C20E5D"); 82 | private byte[] bobPassword = alicePassword; 83 | private boolean aliceDisablePasswordScalarHack = false; 84 | private boolean bobDisablePasswordScalarHack = false; 85 | private int aliceCorruptMsgBit = -1; 86 | private boolean keyMatches = false; 87 | 88 | private boolean run() { 89 | Spake2Context alice = new Spake2Context( 90 | Spake2Role.Alice, 91 | aliceNames.first.getBytes(StandardCharsets.UTF_8), 92 | aliceNames.second.getBytes(StandardCharsets.UTF_8)); 93 | Spake2Context bob = new Spake2Context( 94 | Spake2Role.Bob, 95 | bobNames.first.getBytes(StandardCharsets.UTF_8), 96 | bobNames.second.getBytes(StandardCharsets.UTF_8)); 97 | 98 | // if (aliceDisablePasswordScalarHack) { 99 | // alice.setDisablePasswordScalarHack(true); 100 | // } 101 | // if (bobDisablePasswordScalarHack) { 102 | // bob.setDisablePasswordScalarHack(true); 103 | // } 104 | 105 | byte[] aliceMsg; 106 | byte[] bobMsg; 107 | 108 | try { 109 | aliceMsg = alice.generateMessage(alicePassword); 110 | bobMsg = bob.generateMessage(bobPassword); 111 | } catch (Exception e) { 112 | return false; 113 | } 114 | 115 | System.out.printf("ALICE_MSG: %s%n", Utils.bytesToHex(aliceMsg)); 116 | System.out.printf("BOB_MSG: %s%n", Utils.bytesToHex(bobMsg)); 117 | 118 | if (aliceCorruptMsgBit >= 0 && aliceCorruptMsgBit < (8 * aliceMsg.length)) { 119 | aliceMsg[aliceCorruptMsgBit / 8] ^= 1 << (aliceCorruptMsgBit & 7); 120 | } 121 | 122 | byte[] aliceKey; 123 | byte[] bobKey; 124 | try { 125 | aliceKey = alice.processMessage(bobMsg); 126 | bobKey = bob.processMessage(aliceMsg); 127 | } catch (Exception e) { 128 | return false; 129 | } 130 | 131 | System.out.printf("ALICE_KEY: %s%n", Utils.bytesToHex(aliceKey)); 132 | System.out.printf("BOB_KEY: %s%n", Utils.bytesToHex(bobKey)); 133 | 134 | keyMatches = Arrays.equals(aliceKey, bobKey); 135 | 136 | return true; 137 | } 138 | 139 | boolean keyMatches() { 140 | return keyMatches; 141 | } 142 | } 143 | 144 | private static class Pair { 145 | private S first; 146 | private T second; 147 | 148 | public Pair(S first, T second) { 149 | this.first = first; 150 | this.second = second; 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /android/src/test/java/io/github/muntashirakon/crypto/spake2/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.spake2; 8 | 9 | /** 10 | * Basic utilities for Ed25519. 11 | * Not for external use, not maintained as a public API. 12 | */ 13 | public class Utils { 14 | /** 15 | * Constant-time byte comparison. 16 | * @param b a byte 17 | * @param c a byte 18 | * @return 1 if b and c are equal, 0 otherwise. 19 | */ 20 | public static int equal(int b, int c) { 21 | int result = 0; 22 | int xor = b ^ c; 23 | for (int i = 0; i < 8; i++) { 24 | result |= xor >> i; 25 | } 26 | return (result ^ 0x01) & 0x01; 27 | } 28 | 29 | /** 30 | * Constant-time byte[] comparison. 31 | * @param b a byte[] 32 | * @param c a byte[] 33 | * @return 1 if b and c are equal, 0 otherwise. 34 | */ 35 | public static int equal(byte[] b, byte[] c) { 36 | int result = 0; 37 | for (int i = 0; i < 32; i++) { 38 | result |= b[i] ^ c[i]; 39 | } 40 | 41 | return equal(result, 0); 42 | } 43 | 44 | /** 45 | * Constant-time determine if byte is negative. 46 | * @param b the byte to check. 47 | * @return 1 if the byte is negative, 0 otherwise. 48 | */ 49 | public static int negative(int b) { 50 | return (b >> 8) & 1; 51 | } 52 | 53 | /** 54 | * Get the i'th bit of a byte array. 55 | * @param h the byte array. 56 | * @param i the bit index. 57 | * @return 0 or 1, the value of the i'th bit in h 58 | */ 59 | public static int bit(byte[] h, int i) { 60 | return (h[i >> 3] >> (i & 7)) & 1; 61 | } 62 | 63 | /** 64 | * Converts a hex string to bytes. 65 | * @param s the hex string to be converted. 66 | * @return the byte[] 67 | */ 68 | public static byte[] hexToBytes(String s) { 69 | int len = s.length(); 70 | if (len % 2 != 0) { 71 | throw new IllegalArgumentException("Hex string must have an even length"); 72 | } 73 | byte[] data = new byte[len / 2]; 74 | for (int i = 0; i < len; i += 2) { 75 | data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) 76 | + Character.digit(s.charAt(i+1), 16)); 77 | } 78 | return data; 79 | } 80 | 81 | /** 82 | * Converts bytes to a hex string. 83 | * @param raw the byte[] to be converted. 84 | * @return the hex representation as a string. 85 | */ 86 | public static String bytesToHex(byte[] raw) { 87 | if ( raw == null ) { 88 | return null; 89 | } 90 | final StringBuilder hex = new StringBuilder(2 * raw.length); 91 | for (final byte b : raw) { 92 | hex.append(Character.forDigit((b & 0xF0) >> 4, 16)) 93 | .append(Character.forDigit((b & 0x0F), 16)); 94 | } 95 | return hex.toString(); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | buildscript { 8 | repositories { 9 | google() 10 | mavenCentral() 11 | } 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:4.2.2' 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | } 17 | } 18 | 19 | wrapper { 20 | distributionType = Wrapper.DistributionType.ALL 21 | } 22 | 23 | allprojects { 24 | repositories { 25 | google() 26 | mavenCentral() 27 | maven { url "https://jitpack.io" } 28 | } 29 | gradle.projectsEvaluated { 30 | tasks.withType(JavaCompile) { 31 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MuntashirAkon/spake2-java/bc57ecc77fab8556c1bff2bb02febe2301cedb40/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=9bb8bc05f562f2d42bdf1ba8db62f6b6fa1c3bf6c392228802cc7cb0578fe7e0 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /java/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | plugins { 8 | id 'java-library' 9 | id 'maven-publish' 10 | } 11 | 12 | group = 'io.github.muntashirakon' 13 | version = '0.9.0' 14 | 15 | sourceCompatibility = JavaVersion.VERSION_1_8 16 | targetCompatibility = JavaVersion.VERSION_1_8 17 | 18 | repositories { 19 | mavenCentral() 20 | } 21 | 22 | publishing { 23 | publications { 24 | maven(MavenPublication) { 25 | from components.java 26 | 27 | pom { 28 | name = 'SPAKE2-Java' 29 | description = 'Implementation of SPAKE2 protocol in Java, fully compatible with BoringSSL.' 30 | url = 'https://github.com/MuntashirAkon/spake2-java' 31 | licenses { 32 | license { 33 | name = 'GNU General Public License, Version 3.0 or later' 34 | url = 'https://www.gnu.org/licenses/gpl-3.0.txt' 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | java { 43 | withJavadocJar() 44 | withSourcesJar() 45 | } 46 | 47 | javadoc { 48 | exclude 'io/github/muntashirakon/crypto/ed25519/**' 49 | if(JavaVersion.current().isJava9Compatible()) { 50 | options.addBooleanOption('html5', true) 51 | } 52 | } 53 | 54 | dependencies { 55 | testImplementation 'junit:junit:4.13.2' 56 | } 57 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/muntashirakon/crypto/ed25519/Curve.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.ed25519; 8 | 9 | import java.io.Serializable; 10 | 11 | /** 12 | * A twisted Edwards curve. 13 | * Points on the curve satisfy $-x^2 + y^2 = 1 + d x^2y^2$ 14 | */ 15 | public class Curve implements Serializable { 16 | private static final long serialVersionUID = 4578920872509827L; 17 | private final Ed25519Field f; 18 | private final FieldElement d; 19 | private final FieldElement d2; 20 | private final FieldElement I; 21 | 22 | private final GroupElement zeroP2; 23 | private final GroupElement zeroP3; 24 | private final GroupElement zeroP3PrecomputedDouble; 25 | private final GroupElement zeroPrecomp; 26 | 27 | public Curve(Ed25519Field f, byte[] d, FieldElement I) { 28 | this.f = f; 29 | this.d = f.fromByteArray(d); 30 | this.d2 = this.d.add(this.d); 31 | this.I = I; 32 | 33 | FieldElement zero = f.ZERO; 34 | FieldElement one = f.ONE; 35 | zeroP2 = GroupElement.p2(this, zero, one, one); 36 | zeroP3 = GroupElement.p3(this, zero, one, one, zero, false); 37 | zeroP3PrecomputedDouble = GroupElement.p3(this, zero, one, one, zero, true); 38 | zeroPrecomp = GroupElement.precomp(this, one, one, zero); 39 | } 40 | 41 | public Ed25519Field getField() { 42 | return f; 43 | } 44 | 45 | public FieldElement getD() { 46 | return d; 47 | } 48 | 49 | public FieldElement get2D() { 50 | return d2; 51 | } 52 | 53 | public FieldElement getI() { 54 | return I; 55 | } 56 | 57 | public GroupElement getZero(GroupElement.Representation repr) { 58 | switch (repr) { 59 | case P2: 60 | return zeroP2; 61 | case P3: 62 | return zeroP3; 63 | case P3PrecomputedDouble: 64 | return zeroP3PrecomputedDouble; 65 | case PRECOMP: 66 | return zeroPrecomp; 67 | default: 68 | return null; 69 | } 70 | } 71 | 72 | public GroupElement createPoint(byte[] P, boolean precompute) { 73 | return new GroupElement(this, P, precompute); 74 | } 75 | 76 | public GroupElement fromBytesNegateVarTime(final byte[] s) { 77 | FieldElement Y = f.fromByteArray(s); 78 | FieldElement Z = f.ONE; 79 | FieldElement y2 = Y.square(); 80 | FieldElement dy2 = y2.multiply(d); 81 | FieldElement u = y2.subtract(Z); // u = y^2-1 82 | FieldElement v = dy2.add(Z); // v = dy^2+1 83 | 84 | FieldElement v3 = v.square().multiply(v); // v3 = v^3 85 | FieldElement uv7 = v3.square().multiply(v).multiply(u); // x = uv^7 86 | 87 | FieldElement X = uv7.pow22523(); // x = (uv^7)^((q-5)/8) 88 | X = X.multiply(v3).multiply(u); // x = uv^3(uv^7)^((q-5)/8) 89 | 90 | FieldElement vx2 = X.square().multiply(v); // vx^2 91 | FieldElement check = vx2.subtract(u); // vx^2 - u 92 | if (check.isNonZero()) { 93 | check = vx2.add(u); // vx^2 + u 94 | if (check.isNonZero()) { 95 | return null; 96 | } 97 | X = X.multiply(I); // x = iuv^3(uv^7)^((q-5)/8) 98 | } 99 | 100 | int isNegative = X.isNegative() ? 1 : 0; 101 | if (isNegative != (s[31] >>> 7)) { 102 | X = X.negate(); // x = -iuv^3(uv^7)^((q-5)/8) 103 | } 104 | 105 | FieldElement T = X.multiply(Y); // t = [-]yiuv^3(uv^7)^((q-5)/8) 106 | return GroupElement.p3(this, X, Y, Z, T); 107 | } 108 | 109 | @Override 110 | public int hashCode() { 111 | return f.hashCode() ^ d.hashCode() ^ I.hashCode(); 112 | } 113 | 114 | @Override 115 | public boolean equals(Object o) { 116 | if (o == this) 117 | return true; 118 | if (!(o instanceof Curve)) 119 | return false; 120 | Curve c = (Curve) o; 121 | return f.equals(c.getField()) && 122 | d.equals(c.getD()) && 123 | I.equals(c.getI()); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/muntashirakon/crypto/ed25519/Ed25519.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.ed25519; 8 | 9 | public class Ed25519 { 10 | private static final Ed25519Field ed25519field = new Ed25519Field( 11 | 256, // b 12 | Utils.hexToBytes("edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), // q 13 | new Ed25519LittleEndianEncoding()); 14 | 15 | private static final Curve ed25519curve = new Curve(ed25519field, 16 | Utils.hexToBytes("a3785913ca4deb75abd841414d0a700098e879777940c78c73fe6f2bee6c0352"), // d 17 | ed25519field.fromByteArray(Utils.hexToBytes("b0a00e4a271beec478e42fad0618432fa7d7fb3d99004d2b0bdfc14f8024832b"))); // I 18 | 19 | // RFC 8032 20 | private static final Ed25519CurveParameterSpec ED_25519_CURVE_SPEC = new Ed25519CurveParameterSpec( 21 | ed25519curve, 22 | "SHA-512", // H 23 | new Ed25519ScalarOps(), // l 24 | ed25519curve.createPoint( // B 25 | Utils.hexToBytes("5866666666666666666666666666666666666666666666666666666666666666"), 26 | true)); // Precompute tables for B 27 | 28 | public static Ed25519CurveParameterSpec getSpec() { 29 | return ED_25519_CURVE_SPEC; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/muntashirakon/crypto/ed25519/Ed25519CurveParameterSpec.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.ed25519; 8 | 9 | import java.io.Serializable; 10 | import java.security.MessageDigest; 11 | import java.security.NoSuchAlgorithmException; 12 | import java.security.spec.AlgorithmParameterSpec; 13 | 14 | /** 15 | * Parameter specification for Ed25519 curve. 16 | */ 17 | public class Ed25519CurveParameterSpec implements AlgorithmParameterSpec, Serializable { 18 | public static final String ED_25519 = "Ed25519"; 19 | 20 | private static final long serialVersionUID = 8274987108472012L; 21 | 22 | private final Curve curve; 23 | private final String hashAlgo; 24 | private final Ed25519ScalarOps sc; 25 | private final GroupElement B; 26 | 27 | /** 28 | * @param curve the curve 29 | * @param hashAlgo the JCA string for the hash algorithm 30 | * @param sc the parameter L represented as Ed25519ScalarOps 31 | * @param B the parameter B 32 | * @throws IllegalArgumentException if hash algorithm is unsupported or length is wrong 33 | */ 34 | public Ed25519CurveParameterSpec(Curve curve, String hashAlgo, Ed25519ScalarOps sc, GroupElement B) { 35 | try { 36 | MessageDigest hash = MessageDigest.getInstance(hashAlgo); 37 | // EdDSA hash function must produce 2b-bit output 38 | if (curve.getField().getb() / 4 != hash.getDigestLength()) 39 | throw new IllegalArgumentException("Hash output is not 2b-bit"); 40 | } catch (NoSuchAlgorithmException e) { 41 | throw new IllegalArgumentException("Unsupported hash algorithm"); 42 | } 43 | 44 | this.curve = curve; 45 | this.hashAlgo = hashAlgo; 46 | this.sc = sc; 47 | this.B = B; 48 | } 49 | 50 | public String getName() { 51 | return ED_25519; 52 | } 53 | 54 | public Curve getCurve() { 55 | return curve; 56 | } 57 | 58 | public String getHashAlgorithm() { 59 | return hashAlgo; 60 | } 61 | 62 | public Ed25519ScalarOps getScalarOps() { 63 | return sc; 64 | } 65 | 66 | /** 67 | * @return the base (generator) 68 | */ 69 | public GroupElement getB() { 70 | return B; 71 | } 72 | 73 | @Override 74 | public int hashCode() { 75 | return hashAlgo.hashCode() ^ curve.hashCode() ^ B.hashCode(); 76 | } 77 | 78 | @Override 79 | public boolean equals(Object o) { 80 | if (o == this) 81 | return true; 82 | if (!(o instanceof Ed25519CurveParameterSpec)) 83 | return false; 84 | Ed25519CurveParameterSpec s = (Ed25519CurveParameterSpec) o; 85 | return hashAlgo.equals(s.getHashAlgorithm()) && 86 | curve.equals(s.getCurve()) && 87 | B.equals(s.getB()); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/muntashirakon/crypto/ed25519/Ed25519Field.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.ed25519; 8 | 9 | import java.io.Serializable; 10 | 11 | /** 12 | * An Ed25519 finite field. Includes several pre-computed values. 13 | */ 14 | public class Ed25519Field implements Serializable { 15 | private static final long serialVersionUID = 8746587465875676L; 16 | 17 | private static final byte[] B_ZERO = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000"); 18 | private static final byte[] B_ONE = Utils.hexToBytes("0100000000000000000000000000000000000000000000000000000000000000"); 19 | private static final byte[] B_TWO = Utils.hexToBytes("0200000000000000000000000000000000000000000000000000000000000000"); 20 | private static final byte[] B_FOUR = Utils.hexToBytes("0400000000000000000000000000000000000000000000000000000000000000"); 21 | private static final byte[] B_FIVE = Utils.hexToBytes("0500000000000000000000000000000000000000000000000000000000000000"); 22 | private static final byte[] B_EIGHT = Utils.hexToBytes("0800000000000000000000000000000000000000000000000000000000000000"); 23 | 24 | public final FieldElement ZERO; 25 | public final FieldElement ONE; 26 | public final FieldElement TWO; 27 | public final FieldElement FOUR; 28 | public final FieldElement FIVE; 29 | public final FieldElement EIGHT; 30 | 31 | private final int b; 32 | private final FieldElement q; 33 | /** 34 | * q-2 35 | */ 36 | private final FieldElement qm2; 37 | /** 38 | * (q-5) / 8 39 | */ 40 | private final FieldElement qm5d8; 41 | private final Ed25519LittleEndianEncoding enc; 42 | 43 | public Ed25519Field(int b, byte[] q, Ed25519LittleEndianEncoding enc) { 44 | this.b = b; 45 | this.enc = enc; 46 | this.enc.setField(this); 47 | 48 | this.q = fromByteArray(q); 49 | 50 | // Set up constants 51 | ZERO = fromByteArray(B_ZERO); 52 | ONE = fromByteArray(B_ONE); 53 | TWO = fromByteArray(B_TWO); 54 | FOUR = fromByteArray(B_FOUR); 55 | FIVE = fromByteArray(B_FIVE); 56 | EIGHT = fromByteArray(B_EIGHT); 57 | 58 | // Precompute values 59 | qm2 = this.q.subtract(TWO); 60 | qm5d8 = this.q.subtract(FIVE).divide(EIGHT); 61 | } 62 | 63 | public FieldElement fromByteArray(byte[] x) { 64 | return enc.decode(x); 65 | } 66 | 67 | public int getb() { 68 | return b; 69 | } 70 | 71 | public FieldElement getQ() { 72 | return q; 73 | } 74 | 75 | public FieldElement getQm2() { 76 | return qm2; 77 | } 78 | 79 | public FieldElement getQm5d8() { 80 | return qm5d8; 81 | } 82 | 83 | public Ed25519LittleEndianEncoding getEncoding(){ 84 | return enc; 85 | } 86 | 87 | @Override 88 | public int hashCode() { 89 | return q.hashCode(); 90 | } 91 | 92 | @Override 93 | public boolean equals(Object obj) { 94 | if (!(obj instanceof Ed25519Field)) 95 | return false; 96 | Ed25519Field f = (Ed25519Field) obj; 97 | return b == f.b && q.equals(f.q); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/muntashirakon/crypto/ed25519/Ed25519FieldElement.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.ed25519; 8 | 9 | import java.util.Arrays; 10 | 11 | /** 12 | * Class to represent a field element of the finite field $p = 2^{255} - 19$ elements. 13 | *

14 | * An element $t$, entries $t[0] \dots t[9]$, represents the integer 15 | * $t[0]+2^{26} t[1]+2^{51} t[2]+2^{77} t[3]+2^{102} t[4]+\dots+2^{230} t[9]$. 16 | * Bounds on each $t[i]$ vary depending on context. 17 | *

18 | * Reviewed/commented by Bloody Rookie (nemproject@gmx.de) 19 | */ 20 | public class Ed25519FieldElement extends FieldElement { 21 | /** 22 | * Variable is package private for encoding. 23 | */ 24 | protected final int[] t; 25 | 26 | /** 27 | * Creates a field element. 28 | * 29 | * @param f The underlying field, must be the finite field with $p = 2^{255} - 19$ elements 30 | * @param t The $2^{25.5}$ bit representation of the field element. 31 | */ 32 | public Ed25519FieldElement(Ed25519Field f, int[] t) { 33 | super(f); 34 | if (t.length != 10) 35 | throw new IllegalArgumentException("Invalid radix-2^51 representation"); 36 | this.t = t; 37 | } 38 | 39 | private static final byte[] ZERO = new byte[32]; 40 | 41 | /** 42 | * Gets a value indicating whether the field element is non-zero. 43 | * 44 | * @return 1 if it is non-zero, 0 otherwise. 45 | */ 46 | public boolean isNonZero() { 47 | final byte[] s = toByteArray(); 48 | return Utils.equal(s, ZERO) == 0; 49 | } 50 | 51 | /** 52 | * $h = f + g$ 53 | *

54 | * TODO-CR BR: $h$ is allocated via new, probably not a good idea. Do we need the copying into temp variables if we do that? 55 | *

56 | * Preconditions: 57 | *

    58 | *
  • $|f|$ bounded by $1.1*2^{25},1.1*2^{24},1.1*2^{25},1.1*2^{24},$ etc. 59 | *
  • $|g|$ bounded by $1.1*2^{25},1.1*2^{24},1.1*2^{25},1.1*2^{24},$ etc. 60 | *

61 | * Postconditions: 62 | *

    63 | *
  • $|h|$ bounded by $1.1*2^{26},1.1*2^{25},1.1*2^{26},1.1*2^{25},$ etc. 64 | *
65 | * 66 | * @param val The field element to add. 67 | * @return The field element this + val. 68 | */ 69 | public FieldElement add(FieldElement val) { 70 | int[] g = ((Ed25519FieldElement)val).t; 71 | int[] h = new int[10]; 72 | for (int i = 0; i < 10; i++) { 73 | h[i] = t[i] + g[i]; 74 | } 75 | return new Ed25519FieldElement(f, h); 76 | } 77 | 78 | /** 79 | * $h = f - g$ 80 | *

81 | * Can overlap $h$ with $f$ or $g$. 82 | *

83 | * TODO-CR BR: See above. 84 | *

85 | * Preconditions: 86 | *

    87 | *
  • $|f|$ bounded by $1.1*2^{25},1.1*2^{24},1.1*2^{25},1.1*2^{24},$ etc. 88 | *
  • $|g|$ bounded by $1.1*2^{25},1.1*2^{24},1.1*2^{25},1.1*2^{24},$ etc. 89 | *

90 | * Postconditions: 91 | *

    92 | *
  • $|h|$ bounded by $1.1*2^{26},1.1*2^{25},1.1*2^{26},1.1*2^{25},$ etc. 93 | *
94 | * 95 | * @param val The field element to subtract. 96 | * @return The field element this - val. 97 | **/ 98 | public FieldElement subtract(FieldElement val) { 99 | int[] g = ((Ed25519FieldElement)val).t; 100 | int[] h = new int[10]; 101 | for (int i = 0; i < 10; i++) { 102 | h[i] = t[i] - g[i]; 103 | } 104 | return new Ed25519FieldElement(f, h); 105 | } 106 | 107 | /** 108 | * $h = -f$ 109 | *

110 | * TODO-CR BR: see above. 111 | *

112 | * Preconditions: 113 | *

    114 | *
  • $|f|$ bounded by $1.1*2^{25},1.1*2^{24},1.1*2^{25},1.1*2^{24},$ etc. 115 | *

116 | * Postconditions: 117 | *

    118 | *
  • $|h|$ bounded by $1.1*2^{25},1.1*2^{24},1.1*2^{25},1.1*2^{24},$ etc. 119 | *
120 | * 121 | * @return The field element (-1) * this. 122 | */ 123 | public FieldElement negate() { 124 | int[] h = new int[10]; 125 | for (int i = 0; i < 10; i++) { 126 | h[i] = - t[i]; 127 | } 128 | return new Ed25519FieldElement(f, h); 129 | } 130 | 131 | /** 132 | * $h = f * g$ 133 | *

134 | * Can overlap $h$ with $f$ or $g$. 135 | *

136 | * Preconditions: 137 | *

    138 | *
  • $|f|$ bounded by 139 | * $1.65*2^{26},1.65*2^{25},1.65*2^{26},1.65*2^{25},$ etc. 140 | *
  • $|g|$ bounded by 141 | * $1.65*2^{26},1.65*2^{25},1.65*2^{26},1.65*2^{25},$ etc. 142 | *

143 | * Postconditions: 144 | *

    145 | *
  • $|h|$ bounded by 146 | * $1.01*2^{25},1.01*2^{24},1.01*2^{25},1.01*2^{24},$ etc. 147 | *

148 | * Notes on implementation strategy: 149 | *

150 | * Using schoolbook multiplication. Karatsuba would save a little in some 151 | * cost models. 152 | *

153 | * Most multiplications by 2 and 19 are 32-bit precomputations; cheaper than 154 | * 64-bit postcomputations. 155 | *

156 | * There is one remaining multiplication by 19 in the carry chain; one *19 157 | * precomputation can be merged into this, but the resulting data flow is 158 | * considerably less clean. 159 | *

160 | * There are 12 carries below. 10 of them are 2-way parallelizable and 161 | * vectorizable. Can get away with 11 carries, but then data flow is much 162 | * deeper. 163 | *

164 | * With tighter constraints on inputs can squeeze carries into int32. 165 | * 166 | * @param val The field element to multiply. 167 | * @return The (reasonably reduced) field element this * val. 168 | */ 169 | public FieldElement multiply(FieldElement val) { 170 | int[] g = ((Ed25519FieldElement)val).t; 171 | long x1; 172 | long x2; 173 | long x3; 174 | long x4; 175 | long x5; 176 | long x6; 177 | long x7; 178 | long x8; 179 | long x9; 180 | long x10; 181 | long x11; 182 | long x12; 183 | long x13; 184 | long x14; 185 | long x15; 186 | long x16; 187 | long x17; 188 | long x18; 189 | long x19; 190 | long x20; 191 | long x21; 192 | long x22; 193 | long x23; 194 | long x24; 195 | long x25; 196 | long x26; 197 | long x27; 198 | long x28; 199 | long x29; 200 | long x30; 201 | long x31; 202 | long x32; 203 | long x33; 204 | long x34; 205 | long x35; 206 | long x36; 207 | long x37; 208 | long x38; 209 | long x39; 210 | long x40; 211 | long x41; 212 | long x42; 213 | long x43; 214 | long x44; 215 | long x45; 216 | long x46; 217 | long x47; 218 | long x48; 219 | long x49; 220 | long x50; 221 | long x51; 222 | long x52; 223 | long x53; 224 | long x54; 225 | long x55; 226 | long x56; 227 | long x57; 228 | long x58; 229 | long x59; 230 | long x60; 231 | long x61; 232 | long x62; 233 | long x63; 234 | long x64; 235 | long x65; 236 | long x66; 237 | long x67; 238 | long x68; 239 | long x69; 240 | long x70; 241 | long x71; 242 | long x72; 243 | long x73; 244 | long x74; 245 | long x75; 246 | long x76; 247 | long x77; 248 | long x78; 249 | long x79; 250 | long x80; 251 | long x81; 252 | long x82; 253 | long x83; 254 | long x84; 255 | long x85; 256 | long x86; 257 | long x87; 258 | long x88; 259 | long x89; 260 | long x90; 261 | long x91; 262 | long x92; 263 | long x93; 264 | long x94; 265 | long x95; 266 | long x96; 267 | long x97; 268 | long x98; 269 | long x99; 270 | long x100; 271 | long x101; 272 | long x102; 273 | int x103; 274 | long x104; 275 | long x105; 276 | long x106; 277 | long x107; 278 | long x108; 279 | long x109; 280 | long x110; 281 | long x111; 282 | long x112; 283 | long x113; 284 | long x114; 285 | int x115; 286 | long x116; 287 | long x117; 288 | int x118; 289 | long x119; 290 | long x120; 291 | int x121; 292 | long x122; 293 | long x123; 294 | int x124; 295 | long x125; 296 | long x126; 297 | int x127; 298 | long x128; 299 | long x129; 300 | int x130; 301 | long x131; 302 | long x132; 303 | int x133; 304 | long x134; 305 | long x135; 306 | int x136; 307 | long x137; 308 | long x138; 309 | int x139; 310 | long x140; 311 | long x141; 312 | int x142; 313 | int x143; 314 | int x144; 315 | byte x145; 316 | int x146; 317 | int x147; 318 | x1 = ((long)(t[9]) * ((g[9]) * (byte) 0x26)); 319 | x2 = ((long)(t[9]) * ((g[8]) * (byte) 0x13)); 320 | x3 = ((long)(t[9]) * ((g[7]) * (byte) 0x26)); 321 | x4 = ((long)(t[9]) * ((g[6]) * (byte) 0x13)); 322 | x5 = ((long)(t[9]) * ((g[5]) * (byte) 0x26)); 323 | x6 = ((long)(t[9]) * ((g[4]) * (byte) 0x13)); 324 | x7 = ((long)(t[9]) * ((g[3]) * (byte) 0x26)); 325 | x8 = ((long)(t[9]) * ((g[2]) * (byte) 0x13)); 326 | x9 = ((long)(t[9]) * ((g[1]) * (byte) 0x26)); 327 | x10 = ((long)(t[8]) * ((g[9]) * (byte) 0x13)); 328 | x11 = ((long)(t[8]) * ((g[8]) * (byte) 0x13)); 329 | x12 = ((long)(t[8]) * ((g[7]) * (byte) 0x13)); 330 | x13 = ((long)(t[8]) * ((g[6]) * (byte) 0x13)); 331 | x14 = ((long)(t[8]) * ((g[5]) * (byte) 0x13)); 332 | x15 = ((long)(t[8]) * ((g[4]) * (byte) 0x13)); 333 | x16 = ((long)(t[8]) * ((g[3]) * (byte) 0x13)); 334 | x17 = ((long)(t[8]) * ((g[2]) * (byte) 0x13)); 335 | x18 = ((long)(t[7]) * ((g[9]) * (byte) 0x26)); 336 | x19 = ((long)(t[7]) * ((g[8]) * (byte) 0x13)); 337 | x20 = ((long)(t[7]) * ((g[7]) * (byte) 0x26)); 338 | x21 = ((long)(t[7]) * ((g[6]) * (byte) 0x13)); 339 | x22 = ((long)(t[7]) * ((g[5]) * (byte) 0x26)); 340 | x23 = ((long)(t[7]) * ((g[4]) * (byte) 0x13)); 341 | x24 = ((long)(t[7]) * ((g[3]) * (byte) 0x26)); 342 | x25 = ((long)(t[6]) * ((g[9]) * (byte) 0x13)); 343 | x26 = ((long)(t[6]) * ((g[8]) * (byte) 0x13)); 344 | x27 = ((long)(t[6]) * ((g[7]) * (byte) 0x13)); 345 | x28 = ((long)(t[6]) * ((g[6]) * (byte) 0x13)); 346 | x29 = ((long)(t[6]) * ((g[5]) * (byte) 0x13)); 347 | x30 = ((long)(t[6]) * ((g[4]) * (byte) 0x13)); 348 | x31 = ((long)(t[5]) * ((g[9]) * (byte) 0x26)); 349 | x32 = ((long)(t[5]) * ((g[8]) * (byte) 0x13)); 350 | x33 = ((long)(t[5]) * ((g[7]) * (byte) 0x26)); 351 | x34 = ((long)(t[5]) * ((g[6]) * (byte) 0x13)); 352 | x35 = ((long)(t[5]) * ((g[5]) * (byte) 0x26)); 353 | x36 = ((long)(t[4]) * ((g[9]) * (byte) 0x13)); 354 | x37 = ((long)(t[4]) * ((g[8]) * (byte) 0x13)); 355 | x38 = ((long)(t[4]) * ((g[7]) * (byte) 0x13)); 356 | x39 = ((long)(t[4]) * ((g[6]) * (byte) 0x13)); 357 | x40 = ((long)(t[3]) * ((g[9]) * (byte) 0x26)); 358 | x41 = ((long)(t[3]) * ((g[8]) * (byte) 0x13)); 359 | x42 = ((long)(t[3]) * ((g[7]) * (byte) 0x26)); 360 | x43 = ((long)(t[2]) * ((g[9]) * (byte) 0x13)); 361 | x44 = ((long)(t[2]) * ((g[8]) * (byte) 0x13)); 362 | x45 = ((long)(t[1]) * ((g[9]) * (byte) 0x26)); 363 | x46 = ((long)(t[9]) * (g[0])); 364 | x47 = ((long)(t[8]) * (g[1])); 365 | x48 = ((long)(t[8]) * (g[0])); 366 | x49 = ((long)(t[7]) * (g[2])); 367 | x50 = ((long)(t[7]) * ((g[1]) * 0x2)); 368 | x51 = ((long)(t[7]) * (g[0])); 369 | x52 = ((long)(t[6]) * (g[3])); 370 | x53 = ((long)(t[6]) * (g[2])); 371 | x54 = ((long)(t[6]) * (g[1])); 372 | x55 = ((long)(t[6]) * (g[0])); 373 | x56 = ((long)(t[5]) * (g[4])); 374 | x57 = ((long)(t[5]) * ((g[3]) * 0x2)); 375 | x58 = ((long)(t[5]) * (g[2])); 376 | x59 = ((long)(t[5]) * ((g[1]) * 0x2)); 377 | x60 = ((long)(t[5]) * (g[0])); 378 | x61 = ((long)(t[4]) * (g[5])); 379 | x62 = ((long)(t[4]) * (g[4])); 380 | x63 = ((long)(t[4]) * (g[3])); 381 | x64 = ((long)(t[4]) * (g[2])); 382 | x65 = ((long)(t[4]) * (g[1])); 383 | x66 = ((long)(t[4]) * (g[0])); 384 | x67 = ((long)(t[3]) * (g[6])); 385 | x68 = ((long)(t[3]) * ((g[5]) * 0x2)); 386 | x69 = ((long)(t[3]) * (g[4])); 387 | x70 = ((long)(t[3]) * ((g[3]) * 0x2)); 388 | x71 = ((long)(t[3]) * (g[2])); 389 | x72 = ((long)(t[3]) * ((g[1]) * 0x2)); 390 | x73 = ((long)(t[3]) * (g[0])); 391 | x74 = ((long)(t[2]) * (g[7])); 392 | x75 = ((long)(t[2]) * (g[6])); 393 | x76 = ((long)(t[2]) * (g[5])); 394 | x77 = ((long)(t[2]) * (g[4])); 395 | x78 = ((long)(t[2]) * (g[3])); 396 | x79 = ((long)(t[2]) * (g[2])); 397 | x80 = ((long)(t[2]) * (g[1])); 398 | x81 = ((long)(t[2]) * (g[0])); 399 | x82 = ((long)(t[1]) * (g[8])); 400 | x83 = ((long)(t[1]) * ((g[7]) * 0x2)); 401 | x84 = ((long)(t[1]) * (g[6])); 402 | x85 = ((long)(t[1]) * ((g[5]) * 0x2)); 403 | x86 = ((long)(t[1]) * (g[4])); 404 | x87 = ((long)(t[1]) * ((g[3]) * 0x2)); 405 | x88 = ((long)(t[1]) * (g[2])); 406 | x89 = ((long)(t[1]) * ((g[1]) * 0x2)); 407 | x90 = ((long)(t[1]) * (g[0])); 408 | x91 = ((long)(t[0]) * (g[9])); 409 | x92 = ((long)(t[0]) * (g[8])); 410 | x93 = ((long)(t[0]) * (g[7])); 411 | x94 = ((long)(t[0]) * (g[6])); 412 | x95 = ((long)(t[0]) * (g[5])); 413 | x96 = ((long)(t[0]) * (g[4])); 414 | x97 = ((long)(t[0]) * (g[3])); 415 | x98 = ((long)(t[0]) * (g[2])); 416 | x99 = ((long)(t[0]) * (g[1])); 417 | x100 = ((long)(t[0]) * (g[0])); 418 | x101 = (x100 + (x45 + (x44 + (x42 + (x39 + (x35 + (x30 + (x24 + (x17 + x9))))))))); 419 | x102 = (x101 >> 26); 420 | x103 = (int)(x101 & 0x3ffffff); 421 | x104 = (x91 + (x82 + (x74 + (x67 + (x61 + (x56 + (x52 + (x49 + (x47 + x46))))))))); 422 | x105 = (x92 + (x83 + (x75 + (x68 + (x62 + (x57 + (x53 + (x50 + (x48 + x1))))))))); 423 | x106 = (x93 + (x84 + (x76 + (x69 + (x63 + (x58 + (x54 + (x51 + (x10 + x2))))))))); 424 | x107 = (x94 + (x85 + (x77 + (x70 + (x64 + (x59 + (x55 + (x18 + (x11 + x3))))))))); 425 | x108 = (x95 + (x86 + (x78 + (x71 + (x65 + (x60 + (x25 + (x19 + (x12 + x4))))))))); 426 | x109 = (x96 + (x87 + (x79 + (x72 + (x66 + (x31 + (x26 + (x20 + (x13 + x5))))))))); 427 | x110 = (x97 + (x88 + (x80 + (x73 + (x36 + (x32 + (x27 + (x21 + (x14 + x6))))))))); 428 | x111 = (x98 + (x89 + (x81 + (x40 + (x37 + (x33 + (x28 + (x22 + (x15 + x7))))))))); 429 | x112 = (x99 + (x90 + (x43 + (x41 + (x38 + (x34 + (x29 + (x23 + (x16 + x8))))))))); 430 | x113 = (x102 + x112); 431 | x114 = (x113 >> 25); 432 | x115 = (int)(x113 & 0x1ffffff); 433 | x116 = (x114 + x111); 434 | x117 = (x116 >> 26); 435 | x118 = (int)(x116 & 0x3ffffff); 436 | x119 = (x117 + x110); 437 | x120 = (x119 >> 25); 438 | x121 = (int)(x119 & 0x1ffffff); 439 | x122 = (x120 + x109); 440 | x123 = (x122 >> 26); 441 | x124 = (int)(x122 & 0x3ffffff); 442 | x125 = (x123 + x108); 443 | x126 = (x125 >> 25); 444 | x127 = (int)(x125 & 0x1ffffff); 445 | x128 = (x126 + x107); 446 | x129 = (x128 >> 26); 447 | x130 = (int)(x128 & 0x3ffffff); 448 | x131 = (x129 + x106); 449 | x132 = (x131 >> 25); 450 | x133 = (int)(x131 & 0x1ffffff); 451 | x134 = (x132 + x105); 452 | x135 = (x134 >> 26); 453 | x136 = (int)(x134 & 0x3ffffff); 454 | x137 = (x135 + x104); 455 | x138 = (x137 >> 25); 456 | x139 = (int)(x137 & 0x1ffffff); 457 | x140 = (x138 * (byte) 0x13); 458 | x141 = (x103 + x140); 459 | x142 = (int)(x141 >> 26); 460 | x143 = (int)(x141 & 0x3ffffff); 461 | x144 = (x142 + x115); 462 | x145 = (byte)(x144 >> 25); 463 | x146 = (x144 & 0x1ffffff); 464 | x147 = (x145 + x118); 465 | int[] out1 = new int[10]; 466 | out1[0] = x143; 467 | out1[1] = x146; 468 | out1[2] = x147; 469 | out1[3] = x121; 470 | out1[4] = x124; 471 | out1[5] = x127; 472 | out1[6] = x130; 473 | out1[7] = x133; 474 | out1[8] = x136; 475 | out1[9] = x139; 476 | return new Ed25519FieldElement(f, out1); 477 | } 478 | 479 | /** 480 | * $h = f * f$ 481 | *

482 | * Can overlap $h$ with $f$. 483 | *

484 | * Preconditions: 485 | *

    486 | *
  • $|f|$ bounded by $1.65*2^{26},1.65*2^{25},1.65*2^{26},1.65*2^{25},$ etc. 487 | *

488 | * Postconditions: 489 | *

    490 | *
  • $|h|$ bounded by $1.01*2^{25},1.01*2^{24},1.01*2^{25},1.01*2^{24},$ etc. 491 | *

492 | * See {@link #multiply(FieldElement)} for discussion 493 | * of implementation strategy. 494 | * 495 | * @return The (reasonably reduced) square of this field element. 496 | */ 497 | public FieldElement square() { 498 | int f0 = t[0]; 499 | int f1 = t[1]; 500 | int f2 = t[2]; 501 | int f3 = t[3]; 502 | int f4 = t[4]; 503 | int f5 = t[5]; 504 | int f6 = t[6]; 505 | int f7 = t[7]; 506 | int f8 = t[8]; 507 | int f9 = t[9]; 508 | int f0_2 = 2 * f0; 509 | int f1_2 = 2 * f1; 510 | int f2_2 = 2 * f2; 511 | int f3_2 = 2 * f3; 512 | int f4_2 = 2 * f4; 513 | int f5_2 = 2 * f5; 514 | int f6_2 = 2 * f6; 515 | int f7_2 = 2 * f7; 516 | int f5_38 = 38 * f5; /* 1.959375*2^30 */ 517 | int f6_19 = 19 * f6; /* 1.959375*2^30 */ 518 | int f7_38 = 38 * f7; /* 1.959375*2^30 */ 519 | int f8_19 = 19 * f8; /* 1.959375*2^30 */ 520 | int f9_38 = 38 * f9; /* 1.959375*2^30 */ 521 | long f0f0 = f0 * (long) f0; 522 | long f0f1_2 = f0_2 * (long) f1; 523 | long f0f2_2 = f0_2 * (long) f2; 524 | long f0f3_2 = f0_2 * (long) f3; 525 | long f0f4_2 = f0_2 * (long) f4; 526 | long f0f5_2 = f0_2 * (long) f5; 527 | long f0f6_2 = f0_2 * (long) f6; 528 | long f0f7_2 = f0_2 * (long) f7; 529 | long f0f8_2 = f0_2 * (long) f8; 530 | long f0f9_2 = f0_2 * (long) f9; 531 | long f1f1_2 = f1_2 * (long) f1; 532 | long f1f2_2 = f1_2 * (long) f2; 533 | long f1f3_4 = f1_2 * (long) f3_2; 534 | long f1f4_2 = f1_2 * (long) f4; 535 | long f1f5_4 = f1_2 * (long) f5_2; 536 | long f1f6_2 = f1_2 * (long) f6; 537 | long f1f7_4 = f1_2 * (long) f7_2; 538 | long f1f8_2 = f1_2 * (long) f8; 539 | long f1f9_76 = f1_2 * (long) f9_38; 540 | long f2f2 = f2 * (long) f2; 541 | long f2f3_2 = f2_2 * (long) f3; 542 | long f2f4_2 = f2_2 * (long) f4; 543 | long f2f5_2 = f2_2 * (long) f5; 544 | long f2f6_2 = f2_2 * (long) f6; 545 | long f2f7_2 = f2_2 * (long) f7; 546 | long f2f8_38 = f2_2 * (long) f8_19; 547 | long f2f9_38 = f2 * (long) f9_38; 548 | long f3f3_2 = f3_2 * (long) f3; 549 | long f3f4_2 = f3_2 * (long) f4; 550 | long f3f5_4 = f3_2 * (long) f5_2; 551 | long f3f6_2 = f3_2 * (long) f6; 552 | long f3f7_76 = f3_2 * (long) f7_38; 553 | long f3f8_38 = f3_2 * (long) f8_19; 554 | long f3f9_76 = f3_2 * (long) f9_38; 555 | long f4f4 = f4 * (long) f4; 556 | long f4f5_2 = f4_2 * (long) f5; 557 | long f4f6_38 = f4_2 * (long) f6_19; 558 | long f4f7_38 = f4 * (long) f7_38; 559 | long f4f8_38 = f4_2 * (long) f8_19; 560 | long f4f9_38 = f4 * (long) f9_38; 561 | long f5f5_38 = f5 * (long) f5_38; 562 | long f5f6_38 = f5_2 * (long) f6_19; 563 | long f5f7_76 = f5_2 * (long) f7_38; 564 | long f5f8_38 = f5_2 * (long) f8_19; 565 | long f5f9_76 = f5_2 * (long) f9_38; 566 | long f6f6_19 = f6 * (long) f6_19; 567 | long f6f7_38 = f6 * (long) f7_38; 568 | long f6f8_38 = f6_2 * (long) f8_19; 569 | long f6f9_38 = f6 * (long) f9_38; 570 | long f7f7_38 = f7 * (long) f7_38; 571 | long f7f8_38 = f7_2 * (long) f8_19; 572 | long f7f9_76 = f7_2 * (long) f9_38; 573 | long f8f8_19 = f8 * (long) f8_19; 574 | long f8f9_38 = f8 * (long) f9_38; 575 | long f9f9_38 = f9 * (long) f9_38; 576 | 577 | /** 578 | * Same procedure as in multiply, but this time we have a higher symmetry leading to less summands. 579 | * e.g. f1f9_76 really stands for f1 * 2^26 * f9 * 2^230 + f9 * 2^230 + f1 * 2^26 congruent 2 * 2 * 19 * f1 * f9 2^0 modulo p. 580 | */ 581 | long h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38; 582 | long h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38; 583 | long h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19; 584 | long h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38; 585 | long h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38; 586 | long h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38; 587 | long h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19; 588 | long h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38; 589 | long h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38; 590 | long h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2; 591 | long carry0; 592 | long carry1; 593 | long carry2; 594 | long carry3; 595 | long carry4; 596 | long carry5; 597 | long carry6; 598 | long carry7; 599 | long carry8; 600 | long carry9; 601 | 602 | carry0 = (h0 + (long) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; 603 | carry4 = (h4 + (long) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; 604 | 605 | carry1 = (h1 + (long) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25; 606 | carry5 = (h5 + (long) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25; 607 | 608 | carry2 = (h2 + (long) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26; 609 | carry6 = (h6 + (long) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26; 610 | 611 | carry3 = (h3 + (long) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25; 612 | carry7 = (h7 + (long) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25; 613 | 614 | carry4 = (h4 + (long) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; 615 | carry8 = (h8 + (long) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26; 616 | 617 | carry9 = (h9 + (long) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; 618 | 619 | carry0 = (h0 + (long) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; 620 | 621 | int[] h = new int[10]; 622 | h[0] = (int) h0; 623 | h[1] = (int) h1; 624 | h[2] = (int) h2; 625 | h[3] = (int) h3; 626 | h[4] = (int) h4; 627 | h[5] = (int) h5; 628 | h[6] = (int) h6; 629 | h[7] = (int) h7; 630 | h[8] = (int) h8; 631 | h[9] = (int) h9; 632 | return new Ed25519FieldElement(f, h); 633 | } 634 | 635 | /** 636 | * $h = 2 * f * f$ 637 | *

638 | * Can overlap $h$ with $f$. 639 | *

640 | * Preconditions: 641 | *

    642 | *
  • $|f|$ bounded by $1.65*2^{26},1.65*2^{25},1.65*2^{26},1.65*2^{25},$ etc. 643 | *

644 | * Postconditions: 645 | *

    646 | *
  • $|h|$ bounded by $1.01*2^{25},1.01*2^{24},1.01*2^{25},1.01*2^{24},$ etc. 647 | *

648 | * See {@link #multiply(FieldElement)} for discussion 649 | * of implementation strategy. 650 | * 651 | * @return The (reasonably reduced) square of this field element times 2. 652 | */ 653 | public FieldElement squareAndDouble() { 654 | int f0 = t[0]; 655 | int f1 = t[1]; 656 | int f2 = t[2]; 657 | int f3 = t[3]; 658 | int f4 = t[4]; 659 | int f5 = t[5]; 660 | int f6 = t[6]; 661 | int f7 = t[7]; 662 | int f8 = t[8]; 663 | int f9 = t[9]; 664 | int f0_2 = 2 * f0; 665 | int f1_2 = 2 * f1; 666 | int f2_2 = 2 * f2; 667 | int f3_2 = 2 * f3; 668 | int f4_2 = 2 * f4; 669 | int f5_2 = 2 * f5; 670 | int f6_2 = 2 * f6; 671 | int f7_2 = 2 * f7; 672 | int f5_38 = 38 * f5; /* 1.959375*2^30 */ 673 | int f6_19 = 19 * f6; /* 1.959375*2^30 */ 674 | int f7_38 = 38 * f7; /* 1.959375*2^30 */ 675 | int f8_19 = 19 * f8; /* 1.959375*2^30 */ 676 | int f9_38 = 38 * f9; /* 1.959375*2^30 */ 677 | long f0f0 = f0 * (long) f0; 678 | long f0f1_2 = f0_2 * (long) f1; 679 | long f0f2_2 = f0_2 * (long) f2; 680 | long f0f3_2 = f0_2 * (long) f3; 681 | long f0f4_2 = f0_2 * (long) f4; 682 | long f0f5_2 = f0_2 * (long) f5; 683 | long f0f6_2 = f0_2 * (long) f6; 684 | long f0f7_2 = f0_2 * (long) f7; 685 | long f0f8_2 = f0_2 * (long) f8; 686 | long f0f9_2 = f0_2 * (long) f9; 687 | long f1f1_2 = f1_2 * (long) f1; 688 | long f1f2_2 = f1_2 * (long) f2; 689 | long f1f3_4 = f1_2 * (long) f3_2; 690 | long f1f4_2 = f1_2 * (long) f4; 691 | long f1f5_4 = f1_2 * (long) f5_2; 692 | long f1f6_2 = f1_2 * (long) f6; 693 | long f1f7_4 = f1_2 * (long) f7_2; 694 | long f1f8_2 = f1_2 * (long) f8; 695 | long f1f9_76 = f1_2 * (long) f9_38; 696 | long f2f2 = f2 * (long) f2; 697 | long f2f3_2 = f2_2 * (long) f3; 698 | long f2f4_2 = f2_2 * (long) f4; 699 | long f2f5_2 = f2_2 * (long) f5; 700 | long f2f6_2 = f2_2 * (long) f6; 701 | long f2f7_2 = f2_2 * (long) f7; 702 | long f2f8_38 = f2_2 * (long) f8_19; 703 | long f2f9_38 = f2 * (long) f9_38; 704 | long f3f3_2 = f3_2 * (long) f3; 705 | long f3f4_2 = f3_2 * (long) f4; 706 | long f3f5_4 = f3_2 * (long) f5_2; 707 | long f3f6_2 = f3_2 * (long) f6; 708 | long f3f7_76 = f3_2 * (long) f7_38; 709 | long f3f8_38 = f3_2 * (long) f8_19; 710 | long f3f9_76 = f3_2 * (long) f9_38; 711 | long f4f4 = f4 * (long) f4; 712 | long f4f5_2 = f4_2 * (long) f5; 713 | long f4f6_38 = f4_2 * (long) f6_19; 714 | long f4f7_38 = f4 * (long) f7_38; 715 | long f4f8_38 = f4_2 * (long) f8_19; 716 | long f4f9_38 = f4 * (long) f9_38; 717 | long f5f5_38 = f5 * (long) f5_38; 718 | long f5f6_38 = f5_2 * (long) f6_19; 719 | long f5f7_76 = f5_2 * (long) f7_38; 720 | long f5f8_38 = f5_2 * (long) f8_19; 721 | long f5f9_76 = f5_2 * (long) f9_38; 722 | long f6f6_19 = f6 * (long) f6_19; 723 | long f6f7_38 = f6 * (long) f7_38; 724 | long f6f8_38 = f6_2 * (long) f8_19; 725 | long f6f9_38 = f6 * (long) f9_38; 726 | long f7f7_38 = f7 * (long) f7_38; 727 | long f7f8_38 = f7_2 * (long) f8_19; 728 | long f7f9_76 = f7_2 * (long) f9_38; 729 | long f8f8_19 = f8 * (long) f8_19; 730 | long f8f9_38 = f8 * (long) f9_38; 731 | long f9f9_38 = f9 * (long) f9_38; 732 | long h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38; 733 | long h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38; 734 | long h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19; 735 | long h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38; 736 | long h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38; 737 | long h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38; 738 | long h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19; 739 | long h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38; 740 | long h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38; 741 | long h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2; 742 | long carry0; 743 | long carry1; 744 | long carry2; 745 | long carry3; 746 | long carry4; 747 | long carry5; 748 | long carry6; 749 | long carry7; 750 | long carry8; 751 | long carry9; 752 | 753 | h0 += h0; 754 | h1 += h1; 755 | h2 += h2; 756 | h3 += h3; 757 | h4 += h4; 758 | h5 += h5; 759 | h6 += h6; 760 | h7 += h7; 761 | h8 += h8; 762 | h9 += h9; 763 | 764 | carry0 = (h0 + (long) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; 765 | carry4 = (h4 + (long) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; 766 | 767 | carry1 = (h1 + (long) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25; 768 | carry5 = (h5 + (long) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25; 769 | 770 | carry2 = (h2 + (long) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26; 771 | carry6 = (h6 + (long) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26; 772 | 773 | carry3 = (h3 + (long) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25; 774 | carry7 = (h7 + (long) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25; 775 | 776 | carry4 = (h4 + (long) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; 777 | carry8 = (h8 + (long) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26; 778 | 779 | carry9 = (h9 + (long) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; 780 | 781 | carry0 = (h0 + (long) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; 782 | 783 | int[] h = new int[10]; 784 | h[0] = (int) h0; 785 | h[1] = (int) h1; 786 | h[2] = (int) h2; 787 | h[3] = (int) h3; 788 | h[4] = (int) h4; 789 | h[5] = (int) h5; 790 | h[6] = (int) h6; 791 | h[7] = (int) h7; 792 | h[8] = (int) h8; 793 | h[9] = (int) h9; 794 | return new Ed25519FieldElement(f, h); 795 | } 796 | 797 | /** 798 | * Invert this field element. 799 | *

800 | * The inverse is found via Fermat's little theorem:
801 | * $a^p \cong a \mod p$ and therefore $a^{(p-2)} \cong a^{-1} \mod p$ 802 | * 803 | * @return The inverse of this field element. 804 | */ 805 | public FieldElement invert() { 806 | FieldElement t0, t1, t2, t3; 807 | 808 | // 2 == 2 * 1 809 | t0 = square(); 810 | 811 | // 4 == 2 * 2 812 | t1 = t0.square(); 813 | 814 | // 8 == 2 * 4 815 | t1 = t1.square(); 816 | 817 | // 9 == 8 + 1 818 | t1 = multiply(t1); 819 | 820 | // 11 == 9 + 2 821 | t0 = t0.multiply(t1); 822 | 823 | // 22 == 2 * 11 824 | t2 = t0.square(); 825 | 826 | // 31 == 22 + 9 827 | t1 = t1.multiply(t2); 828 | 829 | // 2^6 - 2^1 830 | t2 = t1.square(); 831 | 832 | // 2^10 - 2^5 833 | for (int i = 1; i < 5; ++i) { 834 | t2 = t2.square(); 835 | } 836 | 837 | // 2^10 - 2^0 838 | t1 = t2.multiply(t1); 839 | 840 | // 2^11 - 2^1 841 | t2 = t1.square(); 842 | 843 | // 2^20 - 2^10 844 | for (int i = 1; i < 10; ++i) { 845 | t2 = t2.square(); 846 | } 847 | 848 | // 2^20 - 2^0 849 | t2 = t2.multiply(t1); 850 | 851 | // 2^21 - 2^1 852 | t3 = t2.square(); 853 | 854 | // 2^40 - 2^20 855 | for (int i = 1; i < 20; ++i) { 856 | t3 = t3.square(); 857 | } 858 | 859 | // 2^40 - 2^0 860 | t2 = t3.multiply(t2); 861 | 862 | // 2^41 - 2^1 863 | t2 = t2.square(); 864 | 865 | // 2^50 - 2^10 866 | for (int i = 1; i < 10; ++i) { 867 | t2 = t2.square(); 868 | } 869 | 870 | // 2^50 - 2^0 871 | t1 = t2.multiply(t1); 872 | 873 | // 2^51 - 2^1 874 | t2 = t1.square(); 875 | 876 | // 2^100 - 2^50 877 | for (int i = 1; i < 50; ++i) { 878 | t2 = t2.square(); 879 | } 880 | 881 | // 2^100 - 2^0 882 | t2 = t2.multiply(t1); 883 | 884 | // 2^101 - 2^1 885 | t3 = t2.square(); 886 | 887 | // 2^200 - 2^100 888 | for (int i = 1; i < 100; ++i) { 889 | t3 = t3.square(); 890 | } 891 | 892 | // 2^200 - 2^0 893 | t2 = t3.multiply(t2); 894 | 895 | // 2^201 - 2^1 896 | t2 = t2.square(); 897 | 898 | // 2^250 - 2^50 899 | for (int i = 1; i < 50; ++i) { 900 | t2 = t2.square(); 901 | } 902 | 903 | // 2^250 - 2^0 904 | t1 = t2.multiply(t1); 905 | 906 | // 2^251 - 2^1 907 | t1 = t1.square(); 908 | 909 | // 2^255 - 2^5 910 | for (int i = 1; i < 5; ++i) { 911 | t1 = t1.square(); 912 | } 913 | 914 | // 2^255 - 21 915 | return t1.multiply(t0); 916 | } 917 | 918 | /** 919 | * Gets this field element to the power of $(2^{252} - 3)$. 920 | * This is a helper function for calculating the square root. 921 | *

922 | * TODO-CR BR: I think it makes sense to have a sqrt function. 923 | * 924 | * @return This field element to the power of $(2^{252} - 3)$. 925 | */ 926 | public FieldElement pow22523() { 927 | FieldElement t0, t1, t2; 928 | 929 | // 2 == 2 * 1 930 | t0 = square(); 931 | 932 | // 4 == 2 * 2 933 | t1 = t0.square(); 934 | 935 | // 8 == 2 * 4 936 | t1 = t1.square(); 937 | 938 | // z9 = z1*z8 939 | t1 = multiply(t1); 940 | 941 | // 11 == 9 + 2 942 | t0 = t0.multiply(t1); 943 | 944 | // 22 == 2 * 11 945 | t0 = t0.square(); 946 | 947 | // 31 == 22 + 9 948 | t0 = t1.multiply(t0); 949 | 950 | // 2^6 - 2^1 951 | t1 = t0.square(); 952 | 953 | // 2^10 - 2^5 954 | for (int i = 1; i < 5; ++i) { 955 | t1 = t1.square(); 956 | } 957 | 958 | // 2^10 - 2^0 959 | t0 = t1.multiply(t0); 960 | 961 | // 2^11 - 2^1 962 | t1 = t0.square(); 963 | 964 | // 2^20 - 2^10 965 | for (int i = 1; i < 10; ++i) { 966 | t1 = t1.square(); 967 | } 968 | 969 | // 2^20 - 2^0 970 | t1 = t1.multiply(t0); 971 | 972 | // 2^21 - 2^1 973 | t2 = t1.square(); 974 | 975 | // 2^40 - 2^20 976 | for (int i = 1; i < 20; ++i) { 977 | t2 = t2.square(); 978 | } 979 | 980 | // 2^40 - 2^0 981 | t1 = t2.multiply(t1); 982 | 983 | // 2^41 - 2^1 984 | t1 = t1.square(); 985 | 986 | // 2^50 - 2^10 987 | for (int i = 1; i < 10; ++i) { 988 | t1 = t1.square(); 989 | } 990 | 991 | // 2^50 - 2^0 992 | t0 = t1.multiply(t0); 993 | 994 | // 2^51 - 2^1 995 | t1 = t0.square(); 996 | 997 | // 2^100 - 2^50 998 | for (int i = 1; i < 50; ++i) { 999 | t1 = t1.square(); 1000 | } 1001 | 1002 | // 2^100 - 2^0 1003 | t1 = t1.multiply(t0); 1004 | 1005 | // 2^101 - 2^1 1006 | t2 = t1.square(); 1007 | 1008 | // 2^200 - 2^100 1009 | for (int i = 1; i < 100; ++i) { 1010 | t2 = t2.square(); 1011 | } 1012 | 1013 | // 2^200 - 2^0 1014 | t1 = t2.multiply(t1); 1015 | 1016 | // 2^201 - 2^1 1017 | t1 = t1.square(); 1018 | 1019 | // 2^250 - 2^50 1020 | for (int i = 1; i < 50; ++i) { 1021 | t1 = t1.square(); 1022 | } 1023 | 1024 | // 2^250 - 2^0 1025 | t0 = t1.multiply(t0); 1026 | 1027 | // 2^251 - 2^1 1028 | t0 = t0.square(); 1029 | 1030 | // 2^252 - 2^2 1031 | t0 = t0.square(); 1032 | 1033 | // 2^252 - 3 1034 | return multiply(t0); 1035 | } 1036 | 1037 | /** 1038 | * Constant-time conditional move. Well, actually it is a conditional copy. 1039 | * Logic is inspired by the SUPERCOP implementation at: 1040 | * https://github.com/floodyberry/supercop/blob/master/crypto_sign/ed25519/ref10/fe_cmov.c 1041 | * 1042 | * @param val the other field element. 1043 | * @param b must be 0 or 1, otherwise results are undefined. 1044 | * @return a copy of this if $b == 0$, or a copy of val if $b == 1$. 1045 | */ 1046 | @Override 1047 | public FieldElement cmov(FieldElement val, int b) { 1048 | Ed25519FieldElement that = (Ed25519FieldElement) val; 1049 | b = -b; 1050 | int[] result = new int[10]; 1051 | for (int i = 0; i < 10; i++) { 1052 | result[i] = this.t[i]; 1053 | int x = this.t[i] ^ that.t[i]; 1054 | x &= b; 1055 | result[i] ^= x; 1056 | } 1057 | return new Ed25519FieldElement(this.f, result); 1058 | } 1059 | 1060 | @Override 1061 | public FieldElement carry() { 1062 | int x1; 1063 | int x2; 1064 | int x3; 1065 | int x4; 1066 | int x5; 1067 | int x6; 1068 | int x7; 1069 | int x8; 1070 | int x9; 1071 | int x10; 1072 | int x11; 1073 | int x12; 1074 | int x13; 1075 | int x14; 1076 | int x15; 1077 | int x16; 1078 | int x17; 1079 | int x18; 1080 | int x19; 1081 | int x20; 1082 | int x21; 1083 | int x22; 1084 | x1 = (this.t[0]); 1085 | x2 = ((x1 >>> 26) + (this.t[1])); 1086 | x3 = ((x2 >>> 25) + (this.t[2])); 1087 | x4 = ((x3 >>> 26) + (this.t[3])); 1088 | x5 = ((x4 >>> 25) + (this.t[4])); 1089 | x6 = ((x5 >>> 26) + (this.t[5])); 1090 | x7 = ((x6 >>> 25) + (this.t[6])); 1091 | x8 = ((x7 >>> 26) + (this.t[7])); 1092 | x9 = ((x8 >>> 25) + (this.t[8])); 1093 | x10 = ((x9 >>> 26) + (this.t[9])); 1094 | x11 = ((x1 & 0x3ffffff) + ((x10 >> 25) * (byte) 0x13)); 1095 | x12 = ((byte)(x11 >>> 26) + (x2 & 0x1ffffff)); 1096 | x13 = (x11 & 0x3ffffff); 1097 | x14 = (x12 & 0x1ffffff); 1098 | x15 = ((byte)(x12 >>> 25) + (x3 & 0x3ffffff)); 1099 | x16 = (x4 & 0x1ffffff); 1100 | x17 = (x5 & 0x3ffffff); 1101 | x18 = (x6 & 0x1ffffff); 1102 | x19 = (x7 & 0x3ffffff); 1103 | x20 = (x8 & 0x1ffffff); 1104 | x21 = (x9 & 0x3ffffff); 1105 | x22 = (x10 & 0x1ffffff); 1106 | int[] out1 = new int[10]; 1107 | out1[0] = x13; 1108 | out1[1] = x14; 1109 | out1[2] = x15; 1110 | out1[3] = x16; 1111 | out1[4] = x17; 1112 | out1[5] = x18; 1113 | out1[6] = x19; 1114 | out1[7] = x20; 1115 | out1[8] = x21; 1116 | out1[9] = x22; 1117 | return new Ed25519FieldElement(this.f, out1); 1118 | } 1119 | 1120 | @Override 1121 | public int hashCode() { 1122 | return Arrays.hashCode(t); 1123 | } 1124 | 1125 | @Override 1126 | public boolean equals(Object obj) { 1127 | if (!(obj instanceof Ed25519FieldElement)) 1128 | return false; 1129 | Ed25519FieldElement fe = (Ed25519FieldElement) obj; 1130 | return 1==Utils.equal(toByteArray(), fe.toByteArray()); 1131 | } 1132 | 1133 | @Override 1134 | public String toString() { 1135 | // return "[Ed25519FieldElement val="+Utils.bytesToHex(toByteArray())+"]"; 1136 | StringBuilder sb = new StringBuilder(); 1137 | for (int i : t) sb.append(i & 0xFFFF_FFFFL).append(" "); 1138 | return sb.toString(); 1139 | } 1140 | } 1141 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/muntashirakon/crypto/ed25519/Ed25519LittleEndianEncoding.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.ed25519; 8 | 9 | /** 10 | * Helper class for encoding/decoding from/to the 32 byte representation. 11 | *

12 | * Reviewed/commented by Bloody Rookie (nemproject@gmx.de) 13 | */ 14 | public class Ed25519LittleEndianEncoding { 15 | protected Ed25519Field f; 16 | 17 | public synchronized void setField(Ed25519Field f) { 18 | if (this.f != null) 19 | throw new IllegalStateException("already set"); 20 | this.f = f; 21 | } 22 | 23 | /** 24 | * Encodes a given field element in its 32 byte representation. This is done in two steps: 25 | *

    26 | *
  1. Reduce the value of the field element modulo $p$. 27 | *
  2. Convert the field element to the 32 byte representation. 28 | *

29 | * The idea for the modulo $p$ reduction algorithm is as follows: 30 | *

31 | *

Assumption:

32 | *
    33 | *
  • $p = 2^{255} - 19$ 34 | *
  • $h = h_0 + 2^{25} * h_1 + 2^{(26+25)} * h_2 + \dots + 2^{230} * h_9$ where $0 \le |h_i| \lt 2^{27}$ for all $i=0,\dots,9$. 35 | *
  • $h \cong r \mod p$, i.e. $h = r + q * p$ for some suitable $0 \le r \lt p$ and an integer $q$. 36 | *

37 | * Then $q = [2^{-255} * (h + 19 * 2^{-25} * h_9 + 1/2)]$ where $[x] = floor(x)$. 38 | *

39 | *

Proof:

40 | *

41 | * We begin with some very raw estimation for the bounds of some expressions: 42 | *

43 | * $$ 44 | * \begin{equation} 45 | * |h| \lt 2^{230} * 2^{30} = 2^{260} \Rightarrow |r + q * p| \lt 2^{260} \Rightarrow |q| \lt 2^{10}. \\ 46 | * \Rightarrow -1/4 \le a := 19^2 * 2^{-255} * q \lt 1/4. \\ 47 | * |h - 2^{230} * h_9| = |h_0 + \dots + 2^{204} * h_8| \lt 2^{204} * 2^{30} = 2^{234}. \\ 48 | * \Rightarrow -1/4 \le b := 19 * 2^{-255} * (h - 2^{230} * h_9) \lt 1/4 49 | * \end{equation} 50 | * $$ 51 | *

52 | * Therefore $0 \lt 1/2 - a - b \lt 1$. 53 | *

54 | * Set $x := r + 19 * 2^{-255} * r + 1/2 - a - b$. Then: 55 | *

56 | * $$ 57 | * 0 \le x \lt 255 - 20 + 19 + 1 = 2^{255} \\ 58 | * \Rightarrow 0 \le 2^{-255} * x \lt 1. 59 | * $$ 60 | *

61 | * Since $q$ is an integer we have 62 | *

63 | * $$ 64 | * [q + 2^{-255} * x] = q \quad (1) 65 | * $$ 66 | *

67 | * Have a closer look at $x$: 68 | *

69 | * $$ 70 | * \begin{align} 71 | * x &= h - q * (2^{255} - 19) + 19 * 2^{-255} * (h - q * (2^{255} - 19)) + 1/2 - 19^2 * 2^{-255} * q - 19 * 2^{-255} * (h - 2^{230} * h_9) \\ 72 | * &= h - q * 2^{255} + 19 * q + 19 * 2^{-255} * h - 19 * q + 19^2 * 2^{-255} * q + 1/2 - 19^2 * 2^{-255} * q - 19 * 2^{-255} * h + 19 * 2^{-25} * h_9 \\ 73 | * &= h + 19 * 2^{-25} * h_9 + 1/2 - q^{255}. 74 | * \end{align} 75 | * $$ 76 | *

77 | * Inserting the expression for $x$ into $(1)$ we get the desired expression for $q$. 78 | */ 79 | public byte[] encode(FieldElement x) { 80 | int[] h = ((Ed25519FieldElement)x).t; 81 | int h0 = h[0]; 82 | int h1 = h[1]; 83 | int h2 = h[2]; 84 | int h3 = h[3]; 85 | int h4 = h[4]; 86 | int h5 = h[5]; 87 | int h6 = h[6]; 88 | int h7 = h[7]; 89 | int h8 = h[8]; 90 | int h9 = h[9]; 91 | int q; 92 | int carry0; 93 | int carry1; 94 | int carry2; 95 | int carry3; 96 | int carry4; 97 | int carry5; 98 | int carry6; 99 | int carry7; 100 | int carry8; 101 | int carry9; 102 | 103 | // Step 1: 104 | // Calculate q 105 | q = (19 * h9 + (1 << 24)) >> 25; 106 | q = (h0 + q) >> 26; 107 | q = (h1 + q) >> 25; 108 | q = (h2 + q) >> 26; 109 | q = (h3 + q) >> 25; 110 | q = (h4 + q) >> 26; 111 | q = (h5 + q) >> 25; 112 | q = (h6 + q) >> 26; 113 | q = (h7 + q) >> 25; 114 | q = (h8 + q) >> 26; 115 | q = (h9 + q) >> 25; 116 | 117 | // r = h - q * p = h - 2^255 * q + 19 * q 118 | // First add 19 * q then discard the bit 255 119 | h0 += 19 * q; 120 | 121 | carry0 = h0 >> 26; h1 += carry0; h0 -= carry0 << 26; 122 | carry1 = h1 >> 25; h2 += carry1; h1 -= carry1 << 25; 123 | carry2 = h2 >> 26; h3 += carry2; h2 -= carry2 << 26; 124 | carry3 = h3 >> 25; h4 += carry3; h3 -= carry3 << 25; 125 | carry4 = h4 >> 26; h5 += carry4; h4 -= carry4 << 26; 126 | carry5 = h5 >> 25; h6 += carry5; h5 -= carry5 << 25; 127 | carry6 = h6 >> 26; h7 += carry6; h6 -= carry6 << 26; 128 | carry7 = h7 >> 25; h8 += carry7; h7 -= carry7 << 25; 129 | carry8 = h8 >> 26; h9 += carry8; h8 -= carry8 << 26; 130 | carry9 = h9 >> 25; h9 -= carry9 << 25; 131 | 132 | // Step 2 (straight forward conversion): 133 | byte[] s = new byte[32]; 134 | s[0] = (byte) h0; 135 | s[1] = (byte) (h0 >> 8); 136 | s[2] = (byte) (h0 >> 16); 137 | s[3] = (byte) ((h0 >> 24) | (h1 << 2)); 138 | s[4] = (byte) (h1 >> 6); 139 | s[5] = (byte) (h1 >> 14); 140 | s[6] = (byte) ((h1 >> 22) | (h2 << 3)); 141 | s[7] = (byte) (h2 >> 5); 142 | s[8] = (byte) (h2 >> 13); 143 | s[9] = (byte) ((h2 >> 21) | (h3 << 5)); 144 | s[10] = (byte) (h3 >> 3); 145 | s[11] = (byte) (h3 >> 11); 146 | s[12] = (byte) ((h3 >> 19) | (h4 << 6)); 147 | s[13] = (byte) (h4 >> 2); 148 | s[14] = (byte) (h4 >> 10); 149 | s[15] = (byte) (h4 >> 18); 150 | s[16] = (byte) h5; 151 | s[17] = (byte) (h5 >> 8); 152 | s[18] = (byte) (h5 >> 16); 153 | s[19] = (byte) ((h5 >> 24) | (h6 << 1)); 154 | s[20] = (byte) (h6 >> 7); 155 | s[21] = (byte) (h6 >> 15); 156 | s[22] = (byte) ((h6 >> 23) | (h7 << 3)); 157 | s[23] = (byte) (h7 >> 5); 158 | s[24] = (byte) (h7 >> 13); 159 | s[25] = (byte) ((h7 >> 21) | (h8 << 4)); 160 | s[26] = (byte) (h8 >> 4); 161 | s[27] = (byte) (h8 >> 12); 162 | s[28] = (byte) ((h8 >> 20) | (h9 << 6)); 163 | s[29] = (byte) (h9 >> 2); 164 | s[30] = (byte) (h9 >> 10); 165 | s[31] = (byte) (h9 >> 18); 166 | return s; 167 | } 168 | 169 | static int load_3(byte[] in, int offset) { 170 | int result = in[offset++] & 0xff; 171 | result |= (in[offset++] & 0xff) << 8; 172 | result |= (in[offset] & 0xff) << 16; 173 | return result; 174 | } 175 | 176 | static long load_4(byte[] in, int offset) { 177 | int result = in[offset++] & 0xff; 178 | result |= (in[offset++] & 0xff) << 8; 179 | result |= (in[offset++] & 0xff) << 16; 180 | result |= in[offset] << 24; 181 | return ((long)result) & 0xffffffffL; 182 | } 183 | 184 | /** 185 | * Decodes a given field element in its 10 byte $2^{25.5}$ representation. 186 | * 187 | * @param in The 32 byte representation. 188 | * @return The field element in its $2^{25.5}$ bit representation. 189 | */ 190 | public FieldElement decode(byte[] in) { 191 | long h0 = load_4(in, 0); 192 | long h1 = load_3(in, 4) << 6; 193 | long h2 = load_3(in, 7) << 5; 194 | long h3 = load_3(in, 10) << 3; 195 | long h4 = load_3(in, 13) << 2; 196 | long h5 = load_4(in, 16); 197 | long h6 = load_3(in, 20) << 7; 198 | long h7 = load_3(in, 23) << 5; 199 | long h8 = load_3(in, 26) << 4; 200 | long h9 = (load_3(in, 29) & 0x7FFFFF) << 2; 201 | long carry0; 202 | long carry1; 203 | long carry2; 204 | long carry3; 205 | long carry4; 206 | long carry5; 207 | long carry6; 208 | long carry7; 209 | long carry8; 210 | long carry9; 211 | 212 | // Remember: 2^255 congruent 19 modulo p 213 | carry9 = (h9 + (long) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; 214 | carry1 = (h1 + (long) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25; 215 | carry3 = (h3 + (long) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25; 216 | carry5 = (h5 + (long) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25; 217 | carry7 = (h7 + (long) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25; 218 | 219 | carry0 = (h0 + (long) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; 220 | carry2 = (h2 + (long) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26; 221 | carry4 = (h4 + (long) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; 222 | carry6 = (h6 + (long) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26; 223 | carry8 = (h8 + (long) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26; 224 | 225 | int[] h = new int[10]; 226 | h[0] = (int) h0; 227 | h[1] = (int) h1; 228 | h[2] = (int) h2; 229 | h[3] = (int) h3; 230 | h[4] = (int) h4; 231 | h[5] = (int) h5; 232 | h[6] = (int) h6; 233 | h[7] = (int) h7; 234 | h[8] = (int) h8; 235 | h[9] = (int) h9; 236 | return new Ed25519FieldElement(f, h); 237 | } 238 | 239 | /** 240 | * Is the FieldElement negative in this encoding? 241 | *

242 | * Return true if $x$ is in $\{1,3,5,\dots,q-2\}$
243 | * Return false if $x$ is in $\{0,2,4,\dots,q-1\}$ 244 | *

245 | * Preconditions: 246 | *

    247 | *
  • $|x|$ bounded by $1.1*2^{26},1.1*2^{25},1.1*2^{26},1.1*2^{25}$, etc. 248 | *
249 | * 250 | * @return true if $x$ is in $\{1,3,5,\dots,q-2\}$, false otherwise. 251 | */ 252 | public boolean isNegative(FieldElement x) { 253 | byte[] s = encode(x); 254 | return (s[0] & 1) != 0; 255 | } 256 | 257 | } 258 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/muntashirakon/crypto/ed25519/Ed25519ScalarOps.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.ed25519; 8 | 9 | import static io.github.muntashirakon.crypto.ed25519.Ed25519LittleEndianEncoding.load_3; 10 | import static io.github.muntashirakon.crypto.ed25519.Ed25519LittleEndianEncoding.load_4; 11 | 12 | /** 13 | * Class for reducing a huge integer modulo the group order q and 14 | * doing a combined multiply plus add plus reduce operation. 15 | *

16 | * $q = 2^{252} + 27742317777372353535851937790883648493$. 17 | *

18 | * Reviewed/commented by Bloody Rookie (nemproject@gmx.de) 19 | */ 20 | public class Ed25519ScalarOps { 21 | 22 | /** 23 | * Reduction modulo the group order $q$. 24 | *

25 | * Input: 26 | * $s[0]+256*s[1]+\dots+256^{63}*s[63] = s$ 27 | *

28 | * Output: 29 | * $s[0]+256*s[1]+\dots+256^{31}*s[31] = s \bmod q$ 30 | * where $q = 2^{252} + 27742317777372353535851937790883648493$. 31 | */ 32 | public byte[] reduce(byte[] s) { 33 | // s0,..., s22 have 21 bits, s23 has 29 bits 34 | long s0 = 0x1FFFFF & load_3(s, 0); 35 | long s1 = 0x1FFFFF & (load_4(s, 2) >> 5); 36 | long s2 = 0x1FFFFF & (load_3(s, 5) >> 2); 37 | long s3 = 0x1FFFFF & (load_4(s, 7) >> 7); 38 | long s4 = 0x1FFFFF & (load_4(s, 10) >> 4); 39 | long s5 = 0x1FFFFF & (load_3(s, 13) >> 1); 40 | long s6 = 0x1FFFFF & (load_4(s, 15) >> 6); 41 | long s7 = 0x1FFFFF & (load_3(s, 18) >> 3); 42 | long s8 = 0x1FFFFF & load_3(s, 21); 43 | long s9 = 0x1FFFFF & (load_4(s, 23) >> 5); 44 | long s10 = 0x1FFFFF & (load_3(s, 26) >> 2); 45 | long s11 = 0x1FFFFF & (load_4(s, 28) >> 7); 46 | long s12 = 0x1FFFFF & (load_4(s, 31) >> 4); 47 | long s13 = 0x1FFFFF & (load_3(s, 34) >> 1); 48 | long s14 = 0x1FFFFF & (load_4(s, 36) >> 6); 49 | long s15 = 0x1FFFFF & (load_3(s, 39) >> 3); 50 | long s16 = 0x1FFFFF & load_3(s, 42); 51 | long s17 = 0x1FFFFF & (load_4(s, 44) >> 5); 52 | long s18 = 0x1FFFFF & (load_3(s, 47) >> 2); 53 | long s19 = 0x1FFFFF & (load_4(s, 49) >> 7); 54 | long s20 = 0x1FFFFF & (load_4(s, 52) >> 4); 55 | long s21 = 0x1FFFFF & (load_3(s, 55) >> 1); 56 | long s22 = 0x1FFFFF & (load_4(s, 57) >> 6); 57 | long s23 = (load_4(s, 60) >> 3); 58 | long carry0; 59 | long carry1; 60 | long carry2; 61 | long carry3; 62 | long carry4; 63 | long carry5; 64 | long carry6; 65 | long carry7; 66 | long carry8; 67 | long carry9; 68 | long carry10; 69 | long carry11; 70 | long carry12; 71 | long carry13; 72 | long carry14; 73 | long carry15; 74 | long carry16; 75 | 76 | /** 77 | * Lots of magic numbers :) 78 | * To understand what's going on below, note that 79 | * 80 | * (1) q = 2^252 + q0 where q0 = 27742317777372353535851937790883648493. 81 | * (2) s11 is the coefficient of 2^(11*21), s23 is the coefficient of 2^(^23*21) and 2^252 = 2^((23-11) * 21)). 82 | * (3) 2^252 congruent -q0 modulo q. 83 | * (4) -q0 = 666643 * 2^0 + 470296 * 2^21 + 654183 * 2^(2*21) - 997805 * 2^(3*21) + 136657 * 2^(4*21) - 683901 * 2^(5*21) 84 | * 85 | * Thus 86 | * s23 * 2^(23*11) = s23 * 2^(12*21) * 2^(11*21) = s3 * 2^252 * 2^(11*21) congruent 87 | * s23 * (666643 * 2^0 + 470296 * 2^21 + 654183 * 2^(2*21) - 997805 * 2^(3*21) + 136657 * 2^(4*21) - 683901 * 2^(5*21)) * 2^(11*21) modulo q = 88 | * s23 * (666643 * 2^(11*21) + 470296 * 2^(12*21) + 654183 * 2^(13*21) - 997805 * 2^(14*21) + 136657 * 2^(15*21) - 683901 * 2^(16*21)). 89 | * 90 | * The same procedure is then applied for s22,...,s18. 91 | */ 92 | s11 += s23 * 666643; 93 | s12 += s23 * 470296; 94 | s13 += s23 * 654183; 95 | s14 -= s23 * 997805; 96 | s15 += s23 * 136657; 97 | s16 -= s23 * 683901; 98 | // not used again 99 | //s23 = 0; 100 | 101 | s10 += s22 * 666643; 102 | s11 += s22 * 470296; 103 | s12 += s22 * 654183; 104 | s13 -= s22 * 997805; 105 | s14 += s22 * 136657; 106 | s15 -= s22 * 683901; 107 | // not used again 108 | //s22 = 0; 109 | 110 | s9 += s21 * 666643; 111 | s10 += s21 * 470296; 112 | s11 += s21 * 654183; 113 | s12 -= s21 * 997805; 114 | s13 += s21 * 136657; 115 | s14 -= s21 * 683901; 116 | // not used again 117 | //s21 = 0; 118 | 119 | s8 += s20 * 666643; 120 | s9 += s20 * 470296; 121 | s10 += s20 * 654183; 122 | s11 -= s20 * 997805; 123 | s12 += s20 * 136657; 124 | s13 -= s20 * 683901; 125 | // not used again 126 | //s20 = 0; 127 | 128 | s7 += s19 * 666643; 129 | s8 += s19 * 470296; 130 | s9 += s19 * 654183; 131 | s10 -= s19 * 997805; 132 | s11 += s19 * 136657; 133 | s12 -= s19 * 683901; 134 | // not used again 135 | //s19 = 0; 136 | 137 | s6 += s18 * 666643; 138 | s7 += s18 * 470296; 139 | s8 += s18 * 654183; 140 | s9 -= s18 * 997805; 141 | s10 += s18 * 136657; 142 | s11 -= s18 * 683901; 143 | // not used again 144 | //s18 = 0; 145 | 146 | /** 147 | * Time to reduce the coefficient in order not to get an overflow. 148 | */ 149 | carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; 150 | carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; 151 | carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; 152 | carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; 153 | carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; 154 | carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; 155 | 156 | carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; 157 | carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; 158 | carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; 159 | carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; 160 | carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; 161 | 162 | /** 163 | * Continue with above procedure. 164 | */ 165 | s5 += s17 * 666643; 166 | s6 += s17 * 470296; 167 | s7 += s17 * 654183; 168 | s8 -= s17 * 997805; 169 | s9 += s17 * 136657; 170 | s10 -= s17 * 683901; 171 | // not used again 172 | //s17 = 0; 173 | 174 | s4 += s16 * 666643; 175 | s5 += s16 * 470296; 176 | s6 += s16 * 654183; 177 | s7 -= s16 * 997805; 178 | s8 += s16 * 136657; 179 | s9 -= s16 * 683901; 180 | // not used again 181 | //s16 = 0; 182 | 183 | s3 += s15 * 666643; 184 | s4 += s15 * 470296; 185 | s5 += s15 * 654183; 186 | s6 -= s15 * 997805; 187 | s7 += s15 * 136657; 188 | s8 -= s15 * 683901; 189 | // not used again 190 | //s15 = 0; 191 | 192 | s2 += s14 * 666643; 193 | s3 += s14 * 470296; 194 | s4 += s14 * 654183; 195 | s5 -= s14 * 997805; 196 | s6 += s14 * 136657; 197 | s7 -= s14 * 683901; 198 | // not used again 199 | //s14 = 0; 200 | 201 | s1 += s13 * 666643; 202 | s2 += s13 * 470296; 203 | s3 += s13 * 654183; 204 | s4 -= s13 * 997805; 205 | s5 += s13 * 136657; 206 | s6 -= s13 * 683901; 207 | // not used again 208 | //s13 = 0; 209 | 210 | s0 += s12 * 666643; 211 | s1 += s12 * 470296; 212 | s2 += s12 * 654183; 213 | s3 -= s12 * 997805; 214 | s4 += s12 * 136657; 215 | s5 -= s12 * 683901; 216 | // set below 217 | //s12 = 0; 218 | 219 | /** 220 | * Reduce coefficients again. 221 | */ 222 | carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; 223 | carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; 224 | carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; 225 | carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; 226 | carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; 227 | carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; 228 | 229 | carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; 230 | carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; 231 | carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; 232 | carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; 233 | carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; 234 | //carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; 235 | carry11 = (s11 + (1<<20)) >> 21; s12 = carry11; s11 -= carry11 << 21; 236 | 237 | s0 += s12 * 666643; 238 | s1 += s12 * 470296; 239 | s2 += s12 * 654183; 240 | s3 -= s12 * 997805; 241 | s4 += s12 * 136657; 242 | s5 -= s12 * 683901; 243 | // set below 244 | //s12 = 0; 245 | 246 | carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; 247 | carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; 248 | carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; 249 | carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; 250 | carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; 251 | carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; 252 | carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; 253 | carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; 254 | carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; 255 | carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; 256 | carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; 257 | //carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; 258 | carry11 = s11 >> 21; s12 = carry11; s11 -= carry11 << 21; 259 | 260 | // TODO-CR BR: Is it really needed to do it TWO times? (it doesn't hurt, just a question). 261 | s0 += s12 * 666643; 262 | s1 += s12 * 470296; 263 | s2 += s12 * 654183; 264 | s3 -= s12 * 997805; 265 | s4 += s12 * 136657; 266 | s5 -= s12 * 683901; 267 | // not used again 268 | //s12 = 0; 269 | 270 | carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; 271 | carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; 272 | carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; 273 | carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; 274 | carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; 275 | carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; 276 | carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; 277 | carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; 278 | carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; 279 | carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; 280 | carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; 281 | 282 | // s0, ..., s11 got 21 bits each. 283 | byte[] result = new byte[32]; 284 | result[0] = (byte) s0; 285 | result[1] = (byte) (s0 >> 8); 286 | result[2] = (byte) ((s0 >> 16) | (s1 << 5)); 287 | result[3] = (byte) (s1 >> 3); 288 | result[4] = (byte) (s1 >> 11); 289 | result[5] = (byte) ((s1 >> 19) | (s2 << 2)); 290 | result[6] = (byte) (s2 >> 6); 291 | result[7] = (byte) ((s2 >> 14) | (s3 << 7)); 292 | result[8] = (byte) (s3 >> 1); 293 | result[9] = (byte) (s3 >> 9); 294 | result[10] = (byte) ((s3 >> 17) | (s4 << 4)); 295 | result[11] = (byte) (s4 >> 4); 296 | result[12] = (byte) (s4 >> 12); 297 | result[13] = (byte) ((s4 >> 20) | (s5 << 1)); 298 | result[14] = (byte) (s5 >> 7); 299 | result[15] = (byte) ((s5 >> 15) | (s6 << 6)); 300 | result[16] = (byte) (s6 >> 2); 301 | result[17] = (byte) (s6 >> 10); 302 | result[18] = (byte) ((s6 >> 18) | (s7 << 3)); 303 | result[19] = (byte) (s7 >> 5); 304 | result[20] = (byte) (s7 >> 13); 305 | result[21] = (byte) s8; 306 | result[22] = (byte) (s8 >> 8); 307 | result[23] = (byte) ((s8 >> 16) | (s9 << 5)); 308 | result[24] = (byte) (s9 >> 3); 309 | result[25] = (byte) (s9 >> 11); 310 | result[26] = (byte) ((s9 >> 19) | (s10 << 2)); 311 | result[27] = (byte) (s10 >> 6); 312 | result[28] = (byte) ((s10 >> 14) | (s11 << 7)); 313 | result[29] = (byte) (s11 >> 1); 314 | result[30] = (byte) (s11 >> 9); 315 | result[31] = (byte) (s11 >> 17); 316 | return result; 317 | } 318 | 319 | 320 | /** 321 | * $(ab+c) \bmod q$ 322 | *

323 | * Input: 324 | *

    325 | *
  • $a[0]+256*a[1]+\dots+256^{31}*a[31] = a$ 326 | *
  • $b[0]+256*b[1]+\dots+256^{31}*b[31] = b$ 327 | *
  • $c[0]+256*c[1]+\dots+256^{31}*c[31] = c$ 328 | *

329 | * Output: 330 | * $result[0]+256*result[1]+\dots+256^{31}*result[31] = (ab+c) \bmod q$ 331 | * where $q = 2^{252} + 27742317777372353535851937790883648493$. 332 | *

333 | * See the comments in {@link #reduce(byte[])} for an explanation of the algorithm. 334 | */ 335 | public byte[] multiplyAndAdd(byte[] a, byte[] b, byte[] c) { 336 | long a0 = 0x1FFFFF & load_3(a, 0); 337 | long a1 = 0x1FFFFF & (load_4(a, 2) >> 5); 338 | long a2 = 0x1FFFFF & (load_3(a, 5) >> 2); 339 | long a3 = 0x1FFFFF & (load_4(a, 7) >> 7); 340 | long a4 = 0x1FFFFF & (load_4(a, 10) >> 4); 341 | long a5 = 0x1FFFFF & (load_3(a, 13) >> 1); 342 | long a6 = 0x1FFFFF & (load_4(a, 15) >> 6); 343 | long a7 = 0x1FFFFF & (load_3(a, 18) >> 3); 344 | long a8 = 0x1FFFFF & load_3(a, 21); 345 | long a9 = 0x1FFFFF & (load_4(a, 23) >> 5); 346 | long a10 = 0x1FFFFF & (load_3(a, 26) >> 2); 347 | long a11 = (load_4(a, 28) >> 7); 348 | long b0 = 0x1FFFFF & load_3(b, 0); 349 | long b1 = 0x1FFFFF & (load_4(b, 2) >> 5); 350 | long b2 = 0x1FFFFF & (load_3(b, 5) >> 2); 351 | long b3 = 0x1FFFFF & (load_4(b, 7) >> 7); 352 | long b4 = 0x1FFFFF & (load_4(b, 10) >> 4); 353 | long b5 = 0x1FFFFF & (load_3(b, 13) >> 1); 354 | long b6 = 0x1FFFFF & (load_4(b, 15) >> 6); 355 | long b7 = 0x1FFFFF & (load_3(b, 18) >> 3); 356 | long b8 = 0x1FFFFF & load_3(b, 21); 357 | long b9 = 0x1FFFFF & (load_4(b, 23) >> 5); 358 | long b10 = 0x1FFFFF & (load_3(b, 26) >> 2); 359 | long b11 = (load_4(b, 28) >> 7); 360 | long c0 = 0x1FFFFF & load_3(c, 0); 361 | long c1 = 0x1FFFFF & (load_4(c, 2) >> 5); 362 | long c2 = 0x1FFFFF & (load_3(c, 5) >> 2); 363 | long c3 = 0x1FFFFF & (load_4(c, 7) >> 7); 364 | long c4 = 0x1FFFFF & (load_4(c, 10) >> 4); 365 | long c5 = 0x1FFFFF & (load_3(c, 13) >> 1); 366 | long c6 = 0x1FFFFF & (load_4(c, 15) >> 6); 367 | long c7 = 0x1FFFFF & (load_3(c, 18) >> 3); 368 | long c8 = 0x1FFFFF & load_3(c, 21); 369 | long c9 = 0x1FFFFF & (load_4(c, 23) >> 5); 370 | long c10 = 0x1FFFFF & (load_3(c, 26) >> 2); 371 | long c11 = (load_4(c, 28) >> 7); 372 | long s0; 373 | long s1; 374 | long s2; 375 | long s3; 376 | long s4; 377 | long s5; 378 | long s6; 379 | long s7; 380 | long s8; 381 | long s9; 382 | long s10; 383 | long s11; 384 | long s12; 385 | long s13; 386 | long s14; 387 | long s15; 388 | long s16; 389 | long s17; 390 | long s18; 391 | long s19; 392 | long s20; 393 | long s21; 394 | long s22; 395 | long s23; 396 | long carry0; 397 | long carry1; 398 | long carry2; 399 | long carry3; 400 | long carry4; 401 | long carry5; 402 | long carry6; 403 | long carry7; 404 | long carry8; 405 | long carry9; 406 | long carry10; 407 | long carry11; 408 | long carry12; 409 | long carry13; 410 | long carry14; 411 | long carry15; 412 | long carry16; 413 | long carry17; 414 | long carry18; 415 | long carry19; 416 | long carry20; 417 | long carry21; 418 | long carry22; 419 | 420 | s0 = c0 + a0*b0; 421 | s1 = c1 + a0*b1 + a1*b0; 422 | s2 = c2 + a0*b2 + a1*b1 + a2*b0; 423 | s3 = c3 + a0*b3 + a1*b2 + a2*b1 + a3*b0; 424 | s4 = c4 + a0*b4 + a1*b3 + a2*b2 + a3*b1 + a4*b0; 425 | s5 = c5 + a0*b5 + a1*b4 + a2*b3 + a3*b2 + a4*b1 + a5*b0; 426 | s6 = c6 + a0*b6 + a1*b5 + a2*b4 + a3*b3 + a4*b2 + a5*b1 + a6*b0; 427 | s7 = c7 + a0*b7 + a1*b6 + a2*b5 + a3*b4 + a4*b3 + a5*b2 + a6*b1 + a7*b0; 428 | s8 = c8 + a0*b8 + a1*b7 + a2*b6 + a3*b5 + a4*b4 + a5*b3 + a6*b2 + a7*b1 + a8*b0; 429 | s9 = c9 + a0*b9 + a1*b8 + a2*b7 + a3*b6 + a4*b5 + a5*b4 + a6*b3 + a7*b2 + a8*b1 + a9*b0; 430 | s10 = c10 + a0*b10 + a1*b9 + a2*b8 + a3*b7 + a4*b6 + a5*b5 + a6*b4 + a7*b3 + a8*b2 + a9*b1 + a10*b0; 431 | s11 = c11 + a0*b11 + a1*b10 + a2*b9 + a3*b8 + a4*b7 + a5*b6 + a6*b5 + a7*b4 + a8*b3 + a9*b2 + a10*b1 + a11*b0; 432 | s12 = a1*b11 + a2*b10 + a3*b9 + a4*b8 + a5*b7 + a6*b6 + a7*b5 + a8*b4 + a9*b3 + a10*b2 + a11*b1; 433 | s13 = a2*b11 + a3*b10 + a4*b9 + a5*b8 + a6*b7 + a7*b6 + a8*b5 + a9*b4 + a10*b3 + a11*b2; 434 | s14 = a3*b11 + a4*b10 + a5*b9 + a6*b8 + a7*b7 + a8*b6 + a9*b5 + a10*b4 + a11*b3; 435 | s15 = a4*b11 + a5*b10 + a6*b9 + a7*b8 + a8*b7 + a9*b6 + a10*b5 + a11*b4; 436 | s16 = a5*b11 + a6*b10 + a7*b9 + a8*b8 + a9*b7 + a10*b6 + a11*b5; 437 | s17 = a6*b11 + a7*b10 + a8*b9 + a9*b8 + a10*b7 + a11*b6; 438 | s18 = a7*b11 + a8*b10 + a9*b9 + a10*b8 + a11*b7; 439 | s19 = a8*b11 + a9*b10 + a10*b9 + a11*b8; 440 | s20 = a9*b11 + a10*b10 + a11*b9; 441 | s21 = a10*b11 + a11*b10; 442 | s22 = a11*b11; 443 | // set below 444 | //s23 = 0; 445 | 446 | carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; 447 | carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; 448 | carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; 449 | carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; 450 | carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; 451 | carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; 452 | carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; 453 | carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; 454 | carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; 455 | carry18 = (s18 + (1<<20)) >> 21; s19 += carry18; s18 -= carry18 << 21; 456 | carry20 = (s20 + (1<<20)) >> 21; s21 += carry20; s20 -= carry20 << 21; 457 | //carry22 = (s22 + (1<<20)) >> 21; s23 += carry22; s22 -= carry22 << 21; 458 | carry22 = (s22 + (1<<20)) >> 21; s23 = carry22; s22 -= carry22 << 21; 459 | 460 | carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; 461 | carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; 462 | carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; 463 | carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; 464 | carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; 465 | carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; 466 | carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; 467 | carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; 468 | carry17 = (s17 + (1<<20)) >> 21; s18 += carry17; s17 -= carry17 << 21; 469 | carry19 = (s19 + (1<<20)) >> 21; s20 += carry19; s19 -= carry19 << 21; 470 | carry21 = (s21 + (1<<20)) >> 21; s22 += carry21; s21 -= carry21 << 21; 471 | 472 | s11 += s23 * 666643; 473 | s12 += s23 * 470296; 474 | s13 += s23 * 654183; 475 | s14 -= s23 * 997805; 476 | s15 += s23 * 136657; 477 | s16 -= s23 * 683901; 478 | // not used again 479 | //s23 = 0; 480 | 481 | s10 += s22 * 666643; 482 | s11 += s22 * 470296; 483 | s12 += s22 * 654183; 484 | s13 -= s22 * 997805; 485 | s14 += s22 * 136657; 486 | s15 -= s22 * 683901; 487 | // not used again 488 | //s22 = 0; 489 | 490 | s9 += s21 * 666643; 491 | s10 += s21 * 470296; 492 | s11 += s21 * 654183; 493 | s12 -= s21 * 997805; 494 | s13 += s21 * 136657; 495 | s14 -= s21 * 683901; 496 | // not used again 497 | //s21 = 0; 498 | 499 | s8 += s20 * 666643; 500 | s9 += s20 * 470296; 501 | s10 += s20 * 654183; 502 | s11 -= s20 * 997805; 503 | s12 += s20 * 136657; 504 | s13 -= s20 * 683901; 505 | // not used again 506 | //s20 = 0; 507 | 508 | s7 += s19 * 666643; 509 | s8 += s19 * 470296; 510 | s9 += s19 * 654183; 511 | s10 -= s19 * 997805; 512 | s11 += s19 * 136657; 513 | s12 -= s19 * 683901; 514 | // not used again 515 | //s19 = 0; 516 | 517 | s6 += s18 * 666643; 518 | s7 += s18 * 470296; 519 | s8 += s18 * 654183; 520 | s9 -= s18 * 997805; 521 | s10 += s18 * 136657; 522 | s11 -= s18 * 683901; 523 | // not used again 524 | //s18 = 0; 525 | 526 | carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; 527 | carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; 528 | carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; 529 | carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; 530 | carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; 531 | carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; 532 | 533 | carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; 534 | carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; 535 | carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; 536 | carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; 537 | carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; 538 | 539 | s5 += s17 * 666643; 540 | s6 += s17 * 470296; 541 | s7 += s17 * 654183; 542 | s8 -= s17 * 997805; 543 | s9 += s17 * 136657; 544 | s10 -= s17 * 683901; 545 | // not used again 546 | //s17 = 0; 547 | 548 | s4 += s16 * 666643; 549 | s5 += s16 * 470296; 550 | s6 += s16 * 654183; 551 | s7 -= s16 * 997805; 552 | s8 += s16 * 136657; 553 | s9 -= s16 * 683901; 554 | // not used again 555 | //s16 = 0; 556 | 557 | s3 += s15 * 666643; 558 | s4 += s15 * 470296; 559 | s5 += s15 * 654183; 560 | s6 -= s15 * 997805; 561 | s7 += s15 * 136657; 562 | s8 -= s15 * 683901; 563 | // not used again 564 | //s15 = 0; 565 | 566 | s2 += s14 * 666643; 567 | s3 += s14 * 470296; 568 | s4 += s14 * 654183; 569 | s5 -= s14 * 997805; 570 | s6 += s14 * 136657; 571 | s7 -= s14 * 683901; 572 | // not used again 573 | //s14 = 0; 574 | 575 | s1 += s13 * 666643; 576 | s2 += s13 * 470296; 577 | s3 += s13 * 654183; 578 | s4 -= s13 * 997805; 579 | s5 += s13 * 136657; 580 | s6 -= s13 * 683901; 581 | // not used again 582 | //s13 = 0; 583 | 584 | s0 += s12 * 666643; 585 | s1 += s12 * 470296; 586 | s2 += s12 * 654183; 587 | s3 -= s12 * 997805; 588 | s4 += s12 * 136657; 589 | s5 -= s12 * 683901; 590 | // set below 591 | //s12 = 0; 592 | 593 | carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; 594 | carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; 595 | carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; 596 | carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; 597 | carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; 598 | carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; 599 | 600 | carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; 601 | carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; 602 | carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; 603 | carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; 604 | carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; 605 | //carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; 606 | carry11 = (s11 + (1<<20)) >> 21; s12 = carry11; s11 -= carry11 << 21; 607 | 608 | s0 += s12 * 666643; 609 | s1 += s12 * 470296; 610 | s2 += s12 * 654183; 611 | s3 -= s12 * 997805; 612 | s4 += s12 * 136657; 613 | s5 -= s12 * 683901; 614 | // set below 615 | //s12 = 0; 616 | 617 | carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; 618 | carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; 619 | carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; 620 | carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; 621 | carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; 622 | carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; 623 | carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; 624 | carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; 625 | carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; 626 | carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; 627 | carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; 628 | //carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; 629 | carry11 = s11 >> 21; s12 = carry11; s11 -= carry11 << 21; 630 | 631 | s0 += s12 * 666643; 632 | s1 += s12 * 470296; 633 | s2 += s12 * 654183; 634 | s3 -= s12 * 997805; 635 | s4 += s12 * 136657; 636 | s5 -= s12 * 683901; 637 | // not used again 638 | //s12 = 0; 639 | 640 | carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; 641 | carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; 642 | carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; 643 | carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; 644 | carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; 645 | carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; 646 | carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; 647 | carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; 648 | carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; 649 | carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; 650 | carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; 651 | 652 | byte[] result = new byte[32]; 653 | result[0] = (byte) s0; 654 | result[1] = (byte) (s0 >> 8); 655 | result[2] = (byte) ((s0 >> 16) | (s1 << 5)); 656 | result[3] = (byte) (s1 >> 3); 657 | result[4] = (byte) (s1 >> 11); 658 | result[5] = (byte) ((s1 >> 19) | (s2 << 2)); 659 | result[6] = (byte) (s2 >> 6); 660 | result[7] = (byte) ((s2 >> 14) | (s3 << 7)); 661 | result[8] = (byte) (s3 >> 1); 662 | result[9] = (byte) (s3 >> 9); 663 | result[10] = (byte) ((s3 >> 17) | (s4 << 4)); 664 | result[11] = (byte) (s4 >> 4); 665 | result[12] = (byte) (s4 >> 12); 666 | result[13] = (byte) ((s4 >> 20) | (s5 << 1)); 667 | result[14] = (byte) (s5 >> 7); 668 | result[15] = (byte) ((s5 >> 15) | (s6 << 6)); 669 | result[16] = (byte) (s6 >> 2); 670 | result[17] = (byte) (s6 >> 10); 671 | result[18] = (byte) ((s6 >> 18) | (s7 << 3)); 672 | result[19] = (byte) (s7 >> 5); 673 | result[20] = (byte) (s7 >> 13); 674 | result[21] = (byte) s8; 675 | result[22] = (byte) (s8 >> 8); 676 | result[23] = (byte) ((s8 >> 16) | (s9 << 5)); 677 | result[24] = (byte) (s9 >> 3); 678 | result[25] = (byte) (s9 >> 11); 679 | result[26] = (byte) ((s9 >> 19) | (s10 << 2)); 680 | result[27] = (byte) (s10 >> 6); 681 | result[28] = (byte) ((s10 >> 14) | (s11 << 7)); 682 | result[29] = (byte) (s11 >> 1); 683 | result[30] = (byte) (s11 >> 9); 684 | result[31] = (byte) (s11 >> 17); 685 | return result; 686 | } 687 | } 688 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/muntashirakon/crypto/ed25519/FieldElement.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.ed25519; 8 | 9 | import java.io.Serializable; 10 | 11 | public abstract class FieldElement implements Serializable { 12 | private static final long serialVersionUID = 1239527465875676L; 13 | 14 | protected final Ed25519Field f; 15 | 16 | public FieldElement(Ed25519Field f) { 17 | if (null == f) { 18 | throw new IllegalArgumentException("field cannot be null"); 19 | } 20 | this.f = f; 21 | } 22 | 23 | /** 24 | * Encode a FieldElement in its $(b-1)$-bit encoding. 25 | * @return the $(b-1)$-bit encoding of this FieldElement. 26 | */ 27 | public byte[] toByteArray() { 28 | return f.getEncoding().encode(this); 29 | } 30 | 31 | public abstract boolean isNonZero(); 32 | 33 | public boolean isNegative() { 34 | return f.getEncoding().isNegative(this); 35 | } 36 | 37 | public abstract FieldElement add(FieldElement val); 38 | 39 | public FieldElement addOne() { 40 | return add(f.ONE); 41 | } 42 | 43 | public abstract FieldElement subtract(FieldElement val); 44 | 45 | public FieldElement subtractOne() { 46 | return subtract(f.ONE); 47 | } 48 | 49 | public abstract FieldElement negate(); 50 | 51 | public FieldElement divide(FieldElement val) { 52 | return multiply(val.invert()); 53 | } 54 | 55 | public abstract FieldElement multiply(FieldElement val); 56 | 57 | public abstract FieldElement square(); 58 | 59 | public abstract FieldElement squareAndDouble(); 60 | 61 | public abstract FieldElement invert(); 62 | 63 | public abstract FieldElement pow22523(); 64 | 65 | public abstract FieldElement cmov(FieldElement val, final int b); 66 | 67 | public abstract FieldElement carry(); 68 | 69 | @Override 70 | public abstract boolean equals(Object o); 71 | 72 | @Override 73 | public abstract int hashCode(); 74 | } 75 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/muntashirakon/crypto/ed25519/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.ed25519; 8 | 9 | /** 10 | * Basic utilities for Ed25519. 11 | * Not for external use, not maintained as a public API. 12 | */ 13 | public class Utils { 14 | /** 15 | * Constant-time byte comparison. 16 | * @param b a byte 17 | * @param c a byte 18 | * @return 1 if b and c are equal, 0 otherwise. 19 | */ 20 | public static int equal(int b, int c) { 21 | int result = 0; 22 | int xor = b ^ c; 23 | for (int i = 0; i < 8; i++) { 24 | result |= xor >> i; 25 | } 26 | return (result ^ 0x01) & 0x01; 27 | } 28 | 29 | /** 30 | * Constant-time byte[] comparison. 31 | * @param b a byte[] 32 | * @param c a byte[] 33 | * @return 1 if b and c are equal, 0 otherwise. 34 | */ 35 | public static int equal(byte[] b, byte[] c) { 36 | int result = 0; 37 | for (int i = 0; i < 32; i++) { 38 | result |= b[i] ^ c[i]; 39 | } 40 | 41 | return equal(result, 0); 42 | } 43 | 44 | /** 45 | * Constant-time determine if byte is negative. 46 | * @param b the byte to check. 47 | * @return 1 if the byte is negative, 0 otherwise. 48 | */ 49 | public static int negative(int b) { 50 | return (b >> 8) & 1; 51 | } 52 | 53 | /** 54 | * Get the i'th bit of a byte array. 55 | * @param h the byte array. 56 | * @param i the bit index. 57 | * @return 0 or 1, the value of the i'th bit in h 58 | */ 59 | public static int bit(byte[] h, int i) { 60 | return (h[i >> 3] >> (i & 7)) & 1; 61 | } 62 | 63 | /** 64 | * Converts a hex string to bytes. 65 | * @param s the hex string to be converted. 66 | * @return the byte[] 67 | */ 68 | public static byte[] hexToBytes(String s) { 69 | int len = s.length(); 70 | if (len % 2 != 0) { 71 | throw new IllegalArgumentException("Hex string must have an even length"); 72 | } 73 | byte[] data = new byte[len / 2]; 74 | for (int i = 0; i < len; i += 2) { 75 | data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) 76 | + Character.digit(s.charAt(i+1), 16)); 77 | } 78 | return data; 79 | } 80 | 81 | /** 82 | * Converts bytes to a hex string. 83 | * @param raw the byte[] to be converted. 84 | * @return the hex representation as a string. 85 | */ 86 | public static String bytesToHex(byte[] raw) { 87 | if ( raw == null ) { 88 | return null; 89 | } 90 | final StringBuilder hex = new StringBuilder(2 * raw.length); 91 | for (final byte b : raw) { 92 | hex.append(Character.forDigit((b & 0xF0) >> 4, 16)) 93 | .append(Character.forDigit((b & 0x0F), 16)); 94 | } 95 | return hex.toString(); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/muntashirakon/crypto/spake2/Spake2Context.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.spake2; 8 | 9 | import java.security.MessageDigest; 10 | import java.security.NoSuchAlgorithmException; 11 | import java.security.SecureRandom; 12 | import java.util.Arrays; 13 | 14 | import javax.security.auth.Destroyable; 15 | 16 | import io.github.muntashirakon.crypto.ed25519.Curve; 17 | import io.github.muntashirakon.crypto.ed25519.Ed25519; 18 | import io.github.muntashirakon.crypto.ed25519.Ed25519CurveParameterSpec; 19 | import io.github.muntashirakon.crypto.ed25519.Ed25519Field; 20 | import io.github.muntashirakon.crypto.ed25519.Ed25519ScalarOps; 21 | import io.github.muntashirakon.crypto.ed25519.FieldElement; 22 | import io.github.muntashirakon.crypto.ed25519.GroupElement; 23 | import io.github.muntashirakon.crypto.ed25519.Utils; 24 | 25 | @SuppressWarnings("unused") 26 | public class Spake2Context implements Destroyable { 27 | /** 28 | * Maximum message size in bytes 29 | */ 30 | public static final int MAX_MSG_SIZE = 32; 31 | /** 32 | * Maximum key size in bytes 33 | */ 34 | public static final int MAX_KEY_SIZE = 64; 35 | 36 | private static byte[] intToByteArray(int[] ints) { 37 | byte[] bytes = new byte[ints.length]; 38 | for (int i = 0; i < bytes.length; ++i) { 39 | bytes[i] = (byte) ints[i]; 40 | } 41 | return bytes; 42 | } 43 | 44 | // Package private for testing purposes 45 | static GroupElement[] getGEFromTable(Curve curve, int[] intTable) { 46 | byte[] table = intToByteArray(intTable); 47 | Ed25519Field f = curve.getField(); 48 | GroupElement[] ge = new GroupElement[table.length / (3 * 32)]; 49 | byte[] bytes = new byte[32]; 50 | for (int i = 0; i < ge.length; ++i) { 51 | System.arraycopy(table, i * 96, bytes, 0, 32); 52 | FieldElement ypx = f.fromByteArray(bytes); 53 | System.arraycopy(table, i * 96 + 32, bytes, 0, 32); 54 | FieldElement ymx = f.fromByteArray(bytes); 55 | System.arraycopy(table, i * 96 + 64, bytes, 0, 32); 56 | FieldElement xy2d = f.fromByteArray(bytes); 57 | ge[i] = GroupElement.precomp(curve, ypx, ymx, xy2d); 58 | } 59 | return ge; 60 | } 61 | 62 | // https://datatracker.ietf.org/doc/html/draft-ietf-kitten-krb-spake-preauth-01#appendix-B 63 | private static final String SEED_N = "edwards25519 point generation seed (N)"; 64 | private static final String SEED_M = "edwards25519 point generation seed (M)"; 65 | 66 | private static final int[] PRECOMP_TABLE_N = new int[]{ 67 | 0x43, 0xFE, 0xA4, 0xBE, 0x26, 0x95, 0xFF, 0x8A, 0xDD, 0xD3, 0x71, 0x28, 0x63, 0x86, 0x31, 68 | 0xB0, 0x32, 0x02, 0xA5, 0xD1, 0x95, 0x80, 0x6A, 0xCF, 0xFB, 0x4C, 0x07, 0xFC, 0xB6, 0x3B, 69 | 0x24, 0x67, 0xF0, 0xC7, 0x1A, 0x57, 0x9F, 0x66, 0x1D, 0x6A, 0x55, 0x97, 0x8B, 0xC1, 0x05, 70 | 0x07, 0xB3, 0x70, 0x47, 0x78, 0x14, 0xAA, 0x77, 0xDD, 0x66, 0x4B, 0xE6, 0x17, 0x2D, 0x27, 71 | 0x1B, 0x3B, 0x6A, 0x0A, 0x81, 0x39, 0x04, 0x13, 0x80, 0x6B, 0x54, 0x7E, 0x26, 0xF3, 0xB0, 72 | 0xA2, 0xCA, 0x96, 0x32, 0x8E, 0x98, 0x44, 0x30, 0xFF, 0x8C, 0x2D, 0x67, 0x77, 0x16, 0xF1, 73 | 0xE9, 0x96, 0x8B, 0x33, 0x06, 0x61, 0xFB, 0x78, 0x6B, 0xFB, 0xD8, 0x00, 0x66, 0x8B, 0x17, 74 | 0x5A, 0x26, 0x2D, 0x71, 0xCD, 0x03, 0xA0, 0x14, 0x93, 0xB3, 0xFF, 0xED, 0x68, 0x29, 0xE4, 75 | 0x84, 0x37, 0x81, 0xBB, 0xCD, 0x06, 0x30, 0x40, 0xC2, 0x95, 0xBB, 0xCC, 0xFB, 0x1F, 0x88, 76 | 0x00, 0xA9, 0xC5, 0x3D, 0x15, 0x41, 0x2B, 0x19, 0x00, 0x46, 0x1A, 0xFB, 0x25, 0xE4, 0x8D, 77 | 0x87, 0x0C, 0xEF, 0x3F, 0xC9, 0xBB, 0xCD, 0x2F, 0xDE, 0x78, 0x7F, 0xA8, 0x5C, 0xA8, 0xF3, 78 | 0x4D, 0x18, 0xE9, 0xF4, 0x80, 0x8C, 0x6C, 0x1F, 0x5B, 0xFF, 0xFD, 0x33, 0xDF, 0x6E, 0xFC, 79 | 0xE1, 0xA2, 0x1E, 0xF2, 0xC1, 0xA8, 0x2E, 0x60, 0x50, 0x2B, 0x1C, 0x7B, 0x5C, 0x00, 0xCC, 80 | 0x18, 0x35, 0x4C, 0x36, 0xF8, 0x19, 0xC9, 0x07, 0xF4, 0x99, 0xB9, 0x88, 0xEF, 0x58, 0x71, 81 | 0x33, 0xCB, 0x98, 0x9C, 0xC6, 0xF3, 0x5E, 0x21, 0x68, 0xAC, 0x8C, 0xA0, 0x2A, 0x0A, 0xAA, 82 | 0xB1, 0x9C, 0xDE, 0x59, 0x43, 0xAB, 0x7B, 0x1A, 0x9E, 0x62, 0x8A, 0xFF, 0xA5, 0x5D, 0x6E, 83 | 0x77, 0xC9, 0x73, 0xED, 0x77, 0x08, 0x00, 0xFC, 0xC1, 0x21, 0xE7, 0x5D, 0xFE, 0x48, 0xD4, 84 | 0x3C, 0x8D, 0x18, 0x5C, 0xE6, 0xCB, 0x41, 0xC7, 0x61, 0x03, 0x23, 0x53, 0x96, 0x08, 0x26, 85 | 0xC3, 0x7C, 0x0B, 0x8E, 0x55, 0xC4, 0xF9, 0x21, 0x44, 0x72, 0x56, 0xBE, 0xA3, 0xF0, 0x37, 86 | 0xF1, 0x25, 0x54, 0xA6, 0x3B, 0xB1, 0x4D, 0xF9, 0x8B, 0xE4, 0x1F, 0x77, 0x28, 0x39, 0x9C, 87 | 0x83, 0x9B, 0xC3, 0xE4, 0xAA, 0x46, 0x8F, 0x14, 0x14, 0x6B, 0x9E, 0x8C, 0x9C, 0xF6, 0x3D, 88 | 0x9D, 0xDC, 0x5C, 0xC3, 0x61, 0x64, 0x15, 0xAE, 0xD1, 0x2A, 0x2E, 0x88, 0xF5, 0x8F, 0x0C, 89 | 0xB5, 0x50, 0x39, 0x29, 0x51, 0x6A, 0xCE, 0x29, 0x7C, 0x99, 0x42, 0xF3, 0xCE, 0xCF, 0x83, 90 | 0x29, 0x27, 0xD6, 0x8B, 0x20, 0x27, 0x79, 0x62, 0xF4, 0x71, 0xD1, 0xBA, 0x68, 0x8C, 0x31, 91 | 0x32, 0xAC, 0xE6, 0x16, 0xF7, 0x8F, 0x18, 0x70, 0x75, 0x11, 0x81, 0xDA, 0x82, 0xD1, 0x69, 92 | 0xC8, 0x2E, 0xB2, 0xBE, 0x0E, 0x5B, 0x73, 0xAB, 0x2A, 0x37, 0x6E, 0x2C, 0x1D, 0x69, 0xEA, 93 | 0x9D, 0x0D, 0x92, 0x96, 0xC9, 0xDD, 0x14, 0xEF, 0x0D, 0x2B, 0x96, 0xD0, 0xCF, 0x43, 0xC8, 94 | 0x48, 0xDD, 0x69, 0x78, 0x2F, 0x6B, 0xAD, 0x5C, 0xEB, 0x38, 0x6E, 0xC3, 0x1D, 0xC9, 0xB2, 95 | 0xE4, 0xAC, 0x7F, 0x88, 0x9E, 0x83, 0x5E, 0xB1, 0xC8, 0x4C, 0x7E, 0x13, 0x8E, 0x92, 0x95, 96 | 0x51, 0x8E, 0x3D, 0x91, 0x92, 0x0D, 0xFC, 0xB6, 0x64, 0x82, 0xC8, 0xAD, 0x0A, 0xE5, 0x52, 97 | 0xB5, 0x33, 0x3F, 0x52, 0x77, 0x9E, 0x1F, 0xEA, 0x93, 0x83, 0xBD, 0x38, 0xB5, 0x4F, 0xD1, 98 | 0x9E, 0x56, 0xDA, 0x9E, 0x6B, 0x6A, 0xDA, 0x00, 0xC5, 0x1B, 0xEC, 0x7B, 0xE2, 0x8B, 0x78, 99 | 0x86, 0x61, 0x66, 0x29, 0xDE, 0x54, 0xA9, 0xCF, 0xC8, 0x59, 0x66, 0xCF, 0xB5, 0xA2, 0x61, 100 | 0x99, 0x81, 0x6D, 0xD1, 0xAE, 0xAD, 0xE3, 0xC6, 0x09, 0x84, 0x5D, 0xF0, 0x82, 0xB7, 0x05, 101 | 0x34, 0x39, 0x67, 0x75, 0x75, 0x92, 0x90, 0x39, 0xB7, 0x10, 0x78, 0x43, 0xB0, 0xB6, 0x78, 102 | 0xD9, 0x0B, 0xF6, 0x4C, 0xE8, 0xF2, 0x8B, 0x0D, 0x69, 0xC4, 0x5D, 0x40, 0xDD, 0xBC, 0xC5, 103 | 0x82, 0x35, 0x30, 0x6E, 0xA9, 0x4F, 0x2E, 0x5F, 0x7A, 0x08, 0x9E, 0xFE, 0x44, 0xAE, 0xB9, 104 | 0x81, 0x13, 0x0C, 0x75, 0x77, 0xE5, 0x48, 0x54, 0x38, 0x8F, 0x8F, 0x87, 0x9B, 0xA8, 0xEF, 105 | 0x62, 0x60, 0x56, 0x90, 0x9F, 0x08, 0x6A, 0x5D, 0xF5, 0xF5, 0xE3, 0x6E, 0xAA, 0xC8, 0x34, 106 | 0x1B, 0xBF, 0xFD, 0x65, 0xCD, 0x61, 0x93, 0xCF, 0x6E, 0x2C, 0x97, 0x22, 0x7A, 0x23, 0x91, 107 | 0x02, 0xF6, 0x1E, 0x3E, 0x21, 0xC6, 0xEB, 0x66, 0xCC, 0x2D, 0x29, 0x6F, 0x41, 0x9D, 0xFF, 108 | 0x50, 0x06, 0x49, 0x90, 0xB1, 0x58, 0x91, 0x3C, 0x23, 0xD6, 0xC3, 0xCC, 0x19, 0xE0, 0x43, 109 | 0xC1, 0xBA, 0xEA, 0xAD, 0xED, 0x04, 0x3D, 0xF0, 0x2E, 0x6E, 0xEF, 0xF2, 0xD2, 0x28, 0xA5, 110 | 0xE3, 0x13, 0xCB, 0xBE, 0xEE, 0xE6, 0xC4, 0x23, 0xE0, 0xEA, 0xAF, 0xB3, 0xEF, 0x87, 0x83, 111 | 0x51, 0xC2, 0x5A, 0xCC, 0xC5, 0x38, 0xA5, 0xA1, 0xE5, 0xF5, 0x0A, 0x77, 0x46, 0xF1, 0xA0, 112 | 0x6A, 0x27, 0xFC, 0xB8, 0xA1, 0x37, 0x62, 0x21, 0x47, 0xD1, 0xCE, 0x24, 0xB6, 0x84, 0x3E, 113 | 0x73, 0xFA, 0x11, 0x10, 0x43, 0xB7, 0x1E, 0x4E, 0x4E, 0xB3, 0xF7, 0xE7, 0x15, 0x1F, 0xD7, 114 | 0xE9, 0x37, 0x90, 0x76, 0x65, 0xDA, 0xA3, 0xB7, 0x7A, 0x66, 0x90, 0x47, 0x9F, 0xCF, 0xAB, 115 | 0x9D, 0x64, 0xE6, 0x75, 0xAB, 0xEE, 0xBF, 0x07, 0xF0, 0xAC, 0xE3, 0x50, 0xBC, 0x78, 0x86, 116 | 0x25, 0x41, 0x26, 0x27, 0x53, 0xE4, 0xF8, 0x12, 0xE6, 0x89, 0x7B, 0x19, 0x7E, 0xC3, 0x1C, 117 | 0x39, 0x91, 0x04, 0xF6, 0xC3, 0x70, 0x28, 0xE3, 0xA6, 0x18, 0xC8, 0xB9, 0xB4, 0xF0, 0x6A, 118 | 0x88, 0xD6, 0x6A, 0xA8, 0xFE, 0x56, 0x63, 0xF4, 0x23, 0xEB, 0x2E, 0x62, 0x21, 0x54, 0x16, 119 | 0xD4, 0xFC, 0x72, 0xB7, 0xBF, 0x10, 0x13, 0xE3, 0x04, 0x81, 0x1B, 0x4B, 0xF6, 0x2A, 0x1A, 120 | 0x02, 0xE2, 0xD6, 0x59, 0x3F, 0x1D, 0x33, 0xCA, 0xC5, 0xBE, 0x85, 0x93, 0xDA, 0xB4, 0xA4, 121 | 0xB9, 0x77, 0x5E, 0x37, 0xB4, 0x82, 0x1E, 0x2B, 0x14, 0x03, 0xB3, 0x06, 0x9E, 0x99, 0x46, 122 | 0xB0, 0x2F, 0x52, 0xE7, 0x03, 0x76, 0x5C, 0x19, 0xC0, 0xF7, 0x94, 0xC5, 0x97, 0x1B, 0xE3, 123 | 0xF4, 0xA0, 0x2D, 0xE5, 0x11, 0xA2, 0x9A, 0x5C, 0xFA, 0x2A, 0x55, 0x00, 0x9F, 0xC6, 0xAA, 124 | 0x69, 0xDD, 0x38, 0xBB, 0x28, 0x78, 0x24, 0xB0, 0x18, 0x16, 0xD6, 0x38, 0x65, 0x2F, 0xEE, 125 | 0x6A, 0xC2, 0x7A, 0x56, 0xA7, 0x7B, 0xE7, 0xEE, 0xC0, 0xC7, 0x5D, 0x0A, 0x43, 0x42, 0x03, 126 | 0x59, 0x54, 0x4B, 0x1B, 0x2C, 0xDD, 0xCA, 0x2C, 0x4B, 0x9C, 0x45, 0xD6, 0xDC, 0x51, 0xF0, 127 | 0xF7, 0xE0, 0x1B, 0x4B, 0xD0, 0xFF, 0xF5, 0x1A, 0x05, 0xC3, 0xBE, 0xC0, 0xFE, 0x17, 0x7D, 128 | 0x39, 0xD2, 0x8B, 0x67, 0x9F, 0xD8, 0x7B, 0xF7, 0x7E, 0x64, 0x68, 0x05, 0x21, 0xF4, 0x22, 129 | 0x72, 0x9D, 0xE5, 0xE0, 0x15, 0xB9, 0xA7, 0x80, 0xD9, 0x39, 0x7A, 0xAD, 0xCA, 0x6C, 0x07, 130 | 0x9A, 0x8F, 0xFD, 0x97, 0xB7, 0x50, 0x04, 0x69, 0xD8, 0x01, 0xDD, 0x77, 0x18, 0xA6, 0x60, 131 | 0x1E, 0x86, 0x88, 0x2D, 0x2D, 0xCA, 0xA9, 0xC6, 0xB6, 0x49, 0xAD, 0x15, 0x84, 0xEA, 0xAD, 132 | 0x3F, 0x0A, 0xF8, 0xCD, 0xD1, 0x4B, 0xFD, 0xA8, 0x71, 0xCB, 0xA7, 0xB5, 0xD1, 0xBC, 0x51, 133 | 0x4B, 0x01, 0x09, 0xD3, 0x36, 0xD2, 0x0A, 0xCF, 0x82, 0x72, 0x94, 0xAB, 0xEF, 0x95, 0x6F, 134 | 0x46, 0x34, 0xD8, 0xA5, 0x32, 0x27, 0xFD, 0x5B, 0xEC, 0x21, 0xA5, 0x7C, 0x21, 0xAB, 0xFE, 135 | 0xFC, 0xA6, 0x60, 0x2B, 0x05, 0x17, 0x84, 0xFE, 0xFA, 0x65, 0x21, 0x87, 0x5D, 0xC0, 0x8E, 136 | 0xC2, 0x01, 0xE5, 0x9E, 0xE5, 0x1D, 0x6D, 0x4A, 0xF0, 0x09, 0x4B, 0x0D, 0xE9, 0x9D, 0xA6, 137 | 0xA4, 0xCB, 0x56, 0xFB, 0xCC, 0x55, 0x84, 0x0E, 0x20, 0x6E, 0x2F, 0x5F, 0x7C, 0x49, 0xCA, 138 | 0x46, 0xF6, 0x16, 0x15, 0x7D, 0xE7, 0xF9, 0x73, 0xDD, 0x86, 0xFD, 0xA5, 0x01, 0xE5, 0x68, 139 | 0x5F, 0x0B, 0xA0, 0xBE, 0x38, 0xBF, 0xF5, 0x00, 0x95, 0xE4, 0xF7, 0xC7, 0xA3, 0xE8, 0x8B, 140 | 0x8B, 0xBE, 0xCA, 0x83, 0x9D, 0x66, 0xA7, 0x42, 0x15, 0xBD, 0x6A, 0x74, 0x81, 0xA0, 0xBA, 141 | 0xC4, 0xD4, 0xF0, 0x83, 0xE6, 0x24, 0x7E, 0x2C, 0x12, 0x57, 0x2E, 0x2E, 0x9B, 0xDB, 0xCA, 142 | 0x04, 0x38, 0x9A, 0x49, 0xAB, 0x82, 0x7D, 0x23, 0xB4, 0xC1, 0x1E, 0xCC, 0xC6, 0x55, 0x37, 143 | 0x41, 0x4B, 0x0C, 0xE5, 0xB5, 0x7D, 0xDE, 0xAB, 0xF5, 0xA9, 0xDF, 0x16, 0x0D, 0xE4, 0x9A, 144 | 0x0B, 0xA4, 0x6D, 0x14, 0x3E, 0x55, 0xB3, 0xE7, 0x67, 0x25, 0x8E, 0x31, 0xAF, 0x35, 0x81, 145 | 0x50, 0x3E, 0x5F, 0xAD, 0xAC, 0x76, 0x12, 0x01, 0x3B, 0xBC, 0x94, 0x21, 0xE4, 0x46, 0x5F, 146 | 0x15, 0x51, 0x84, 0x9F, 0x06, 0x1E, 0x79, 0xC8, 0xB8, 0xF8, 0x45, 0x53, 0x5D, 0xC1, 0x60, 147 | 0x01, 0xA2, 0xDF, 0xE5, 0x69, 0x87, 0x21, 0x19, 0x45, 0xB4, 0x53, 0x91, 0x69, 0x05, 0x4C, 148 | 0x3E, 0x45, 0x93, 0x97, 0x2F, 0xE1, 0x24, 0xD0, 0xD6, 0xB3, 0x12, 0xBE, 0x5B, 0xC6, 0x63, 149 | 0x11, 0xD0, 0x23, 0xF9, 0x51, 0x9B, 0xD2, 0xFA, 0xF1, 0x7B, 0x02, 0x71, 0x5E, 0xA3, 0x3C, 150 | 0xF7, 0x71, 0x41, 0xD6, 0x72, 0x90, 0x25, 0x85, 0x82, 0x75, 0x17, 0xF5, 0x31, 0x9B, 0xC8, 151 | 0x60, 0xEE, 0xAD, 0xB4, 0xF6, 0x81, 0x75, 0x95, 0xDE, 0x7D, 0xB3, 0x14, 0x82, 0x9A, 0x69, 152 | 0x21, 0x42, 0x11, 0x01, 0x13, 0x1F, 0xEC, 0xD9, 0x93, 0xA9, 0x6B, 0x07, 0xB4, 0xCE, 0xE8, 153 | 0xEE, 0xF6, 0x16, 0xB0, 0xCF, 0x71, 0xA8, 0x68, 0x9E, 0xDD, 0xD6, 0x0C, 0xA0, 0x4D, 0xFE, 154 | 0xEA, 0x95, 0xAA, 0x3D, 0x3B, 0x58, 0x74, 0xAC, 0x76, 0xA7, 0x2E, 0x66, 0x42, 0x94, 0xB0, 155 | 0xAD, 0x45, 0x12, 0xFD, 0x26, 0x6C, 0x2A, 0xFE, 0xC1, 0x98, 0x5B, 0xFB, 0x31, 0x88, 0xE1, 156 | 0xD2, 0xF1, 0x3B, 0x56, 0xB3, 0x49, 0x97, 0x34, 0x61, 0xD3, 0xE9, 0x52, 0x06, 0x10, 0xAB, 157 | 0x40, 0xC0, 0xC7, 0x72, 0x4D, 0xB8, 0xFF, 0x68, 0x3B, 0x54, 0x72, 0x4C, 0xFE, 0xBA, 0x01, 158 | 0x0C, 0x77, 0xBF, 0x94, 0x5D, 0xC8, 0x70, 0x82, 0x79, 0x70, 0x01, 0x2C, 0xFE, 0x52, 0x44, 159 | 0xA2, 0x0E, 0x59, 0xFA, 0x57, 0x39, 0x48, 0xE6, 0xC1, 0xC3, 0xC1, 0x44, 0x13, 0xD2, 0xC9, 160 | 0x7F, 0xE3, 0x80, 0x91, 0x4E, 0x4F, 0x5A, 0x20, 0x9F, 0x1F, 0x70, 0x43, 0x61, 0x6B, 0x0B, 161 | 0x08, 0x7B, 0xB0, 0x83, 0x18, 0xA7, 0x61, 0x46, 0x21, 0x78, 0xD1, 0x9A, 0xC6, 0xC3, 0xE6, 162 | 0x59, 0xDF, 0x0A, 0x4D, 0xE2, 0x35, 0x31, 0x9F, 0x3A, 0x08, 0x86, 0xE8, 0x08, 0xC4, 0x60, 163 | }; 164 | 165 | private static final int[] PRECOMP_TABLE_M = new int[]{ 166 | 0x22, 0x81, 0xE2, 0x10, 0x8E, 0xCF, 0xC8, 0xEE, 0x61, 0xC5, 0xAF, 0x20, 0x39, 0x8B, 0x9D, 167 | 0xC8, 0xC6, 0xCD, 0x8A, 0x1B, 0x61, 0x7A, 0xCA, 0x71, 0xE0, 0x6B, 0x02, 0x20, 0x31, 0x67, 168 | 0x41, 0x74, 0x7F, 0x33, 0x1B, 0x86, 0x5E, 0xEC, 0xEA, 0x6C, 0x0B, 0x00, 0x2B, 0x44, 0xED, 169 | 0xAC, 0x38, 0xF0, 0xDB, 0x74, 0x3C, 0x74, 0xE5, 0x14, 0x54, 0x35, 0xBE, 0x33, 0x87, 0x16, 170 | 0xDF, 0x75, 0x62, 0x69, 0x9F, 0x80, 0x69, 0x41, 0x2F, 0x5B, 0xF3, 0x5A, 0xD8, 0x22, 0x94, 171 | 0x18, 0x7B, 0x00, 0xDF, 0x75, 0x27, 0x4C, 0x8F, 0x7D, 0x9F, 0x7B, 0x66, 0x37, 0xC2, 0x5D, 172 | 0x3E, 0xA8, 0xE0, 0x53, 0x1E, 0x09, 0x61, 0x35, 0x5B, 0xA6, 0x9C, 0x02, 0x4F, 0x6B, 0x8A, 173 | 0x7C, 0x82, 0x9A, 0x78, 0x81, 0xB4, 0xB0, 0xB5, 0xFD, 0x1F, 0x19, 0x57, 0x5F, 0xF0, 0x47, 174 | 0x0D, 0x8A, 0x50, 0x3C, 0xCF, 0xDF, 0x86, 0x62, 0x48, 0x23, 0xFC, 0x24, 0xD9, 0xA7, 0x12, 175 | 0x00, 0xB4, 0x5E, 0x7C, 0xDA, 0x08, 0x8A, 0x68, 0x37, 0x2C, 0x81, 0x06, 0x07, 0x42, 0x41, 176 | 0x44, 0x94, 0x7E, 0x4D, 0x83, 0xC0, 0x62, 0xA5, 0x87, 0x58, 0x9E, 0xD3, 0x1F, 0x9E, 0x5F, 177 | 0xC3, 0xF1, 0x0B, 0xD6, 0x87, 0xAE, 0xDC, 0x62, 0x6F, 0x8E, 0x8E, 0x9E, 0x09, 0x19, 0xE4, 178 | 0x8A, 0x8D, 0x66, 0xA9, 0x96, 0x4D, 0x4E, 0x21, 0xEF, 0x6E, 0x2E, 0x66, 0xAB, 0x3F, 0xDE, 179 | 0xCD, 0x85, 0x32, 0x31, 0x3E, 0x4C, 0x48, 0x30, 0xFC, 0x80, 0x7A, 0xCC, 0xD9, 0x5B, 0x6C, 180 | 0xC8, 0xB1, 0x5B, 0xA4, 0x8C, 0x1F, 0x0A, 0xB7, 0xEF, 0x5A, 0x24, 0x20, 0x58, 0x67, 0xE9, 181 | 0x65, 0x8B, 0x79, 0x2E, 0xD0, 0xAC, 0xCC, 0x55, 0x71, 0x53, 0xEA, 0x69, 0x06, 0x2F, 0x88, 182 | 0xC6, 0x38, 0x08, 0x7B, 0x2F, 0xC2, 0x40, 0xE3, 0x6B, 0xEF, 0x22, 0x1E, 0xAE, 0x58, 0x81, 183 | 0x6B, 0x0E, 0xF2, 0xA7, 0xC5, 0xA2, 0x11, 0x5B, 0x59, 0x4B, 0x71, 0x08, 0x80, 0x0E, 0x04, 184 | 0x65, 0x20, 0x4C, 0x61, 0xB4, 0x2F, 0x54, 0xF2, 0xED, 0xE6, 0x19, 0x0A, 0x1F, 0x84, 0xE2, 185 | 0x87, 0x69, 0x39, 0x75, 0xDE, 0x04, 0x6C, 0x10, 0x8D, 0x7E, 0x73, 0xD5, 0xF2, 0x85, 0x86, 186 | 0x48, 0xBB, 0xC5, 0x1B, 0x8C, 0xE4, 0x1F, 0xD5, 0x97, 0x35, 0x5F, 0x18, 0x94, 0xAD, 0xA3, 187 | 0x50, 0x65, 0xDC, 0x7D, 0x33, 0x9C, 0x3A, 0xE6, 0xA4, 0x6A, 0x83, 0xD3, 0x21, 0x09, 0x88, 188 | 0xB8, 0x78, 0x36, 0xBF, 0xE9, 0xBA, 0xA6, 0xB7, 0x5F, 0xBF, 0xFA, 0x71, 0x94, 0xC5, 0x50, 189 | 0x49, 0x13, 0x98, 0x6F, 0xA1, 0xBB, 0x41, 0xAE, 0x41, 0xF2, 0xD0, 0x11, 0x33, 0x87, 0xC1, 190 | 0xF6, 0xB2, 0x9F, 0x33, 0xC8, 0x30, 0x90, 0x4D, 0x9E, 0x67, 0xED, 0x6A, 0xEC, 0xAE, 0x44, 191 | 0xD2, 0x48, 0x22, 0x77, 0x3B, 0xE6, 0xF4, 0x6D, 0x71, 0xF7, 0x4A, 0x88, 0x9A, 0x49, 0x96, 192 | 0x29, 0xFC, 0x29, 0x8B, 0xF7, 0x2D, 0xD7, 0xC9, 0x6D, 0xB1, 0xDC, 0xEA, 0x26, 0xF5, 0x23, 193 | 0x1C, 0x61, 0xD5, 0xFD, 0xE2, 0x23, 0xAC, 0xC4, 0xD4, 0x17, 0x0B, 0xF7, 0x11, 0x07, 0x08, 194 | 0x7D, 0x9E, 0xD3, 0xC7, 0x3F, 0xAA, 0x2C, 0x90, 0xFC, 0xDF, 0xAC, 0xC5, 0x83, 0x69, 0xC3, 195 | 0xD1, 0xA7, 0x37, 0x01, 0xC4, 0x62, 0x16, 0xB7, 0xBE, 0x98, 0x36, 0x9F, 0x32, 0x56, 0x24, 196 | 0xBE, 0x34, 0x45, 0x25, 0xCE, 0x51, 0xF2, 0x41, 0x19, 0x17, 0x46, 0xDE, 0x9D, 0x0F, 0x63, 197 | 0xA8, 0xE9, 0x33, 0xE2, 0xE9, 0x75, 0x00, 0x6E, 0x31, 0xFE, 0x4D, 0x5C, 0x0F, 0x43, 0x6C, 198 | 0xA7, 0x5E, 0xE3, 0xF0, 0xB5, 0x1E, 0xC6, 0xE6, 0x4E, 0xD3, 0x43, 0x5B, 0xC5, 0x1D, 0xA7, 199 | 0x30, 0x03, 0xF0, 0x64, 0x9C, 0x1D, 0xEC, 0xA1, 0xED, 0xA1, 0x5C, 0x5F, 0x75, 0x2C, 0x63, 200 | 0x9F, 0x00, 0x11, 0xB4, 0xB6, 0xA5, 0xFE, 0xDF, 0x9A, 0xA0, 0xFE, 0x66, 0xD3, 0x15, 0x42, 201 | 0xB5, 0xF7, 0xFC, 0xE0, 0xD4, 0x17, 0x20, 0x89, 0xB2, 0x96, 0xF9, 0x4A, 0x9C, 0xD4, 0x68, 202 | 0x15, 0x86, 0xD5, 0x21, 0x4C, 0xBE, 0xE5, 0x8C, 0xFC, 0xA2, 0x91, 0x69, 0xF4, 0xC0, 0x51, 203 | 0xF8, 0xB9, 0x40, 0x64, 0x91, 0x86, 0xBA, 0xEE, 0x1F, 0xB6, 0x26, 0x19, 0xB6, 0xEE, 0xA0, 204 | 0x8B, 0x7A, 0x4C, 0x45, 0x1A, 0x02, 0x65, 0x57, 0x63, 0xF8, 0x85, 0xAA, 0xBF, 0x2E, 0x04, 205 | 0x26, 0x74, 0xB2, 0x53, 0x56, 0x00, 0x38, 0x1D, 0xF7, 0xCE, 0x7C, 0x78, 0x2C, 0x7F, 0xDB, 206 | 0xF0, 0x76, 0x5C, 0x83, 0x2E, 0xDB, 0x8B, 0x3C, 0x16, 0xB7, 0xC7, 0x89, 0x76, 0x67, 0x12, 207 | 0x1F, 0x2B, 0x09, 0x9C, 0x99, 0x80, 0x14, 0x77, 0xAF, 0x37, 0x7A, 0xE8, 0x4D, 0x52, 0x8A, 208 | 0xE1, 0x60, 0xD4, 0x3B, 0xA8, 0x6C, 0x43, 0x44, 0xD8, 0x63, 0x76, 0xFC, 0x37, 0xAA, 0x13, 209 | 0x26, 0xB8, 0x5A, 0x66, 0x58, 0x09, 0xE4, 0xC2, 0x04, 0x03, 0xCD, 0x11, 0xBA, 0x0C, 0xE4, 210 | 0x16, 0x80, 0x2A, 0xE9, 0xF1, 0x4D, 0xD7, 0x0B, 0x4E, 0xF6, 0xC2, 0x35, 0x12, 0xC3, 0xAD, 211 | 0xC3, 0xFE, 0x50, 0x01, 0x19, 0x80, 0x1A, 0x9E, 0xC3, 0x3E, 0xEB, 0xD8, 0x19, 0xA2, 0x1D, 212 | 0x29, 0xCA, 0xEA, 0xC8, 0xBB, 0xD9, 0xDD, 0xAC, 0x00, 0xC4, 0xE8, 0x57, 0x56, 0x0A, 0x7D, 213 | 0x55, 0x9D, 0x1F, 0xCE, 0xBB, 0xC4, 0x38, 0x28, 0xD7, 0xE1, 0x06, 0x11, 0xD3, 0x24, 0x06, 214 | 0x70, 0x46, 0xEC, 0x48, 0xC9, 0x7D, 0x06, 0x41, 0x7C, 0x3D, 0x89, 0x1E, 0xD1, 0x98, 0xAF, 215 | 0x12, 0x0E, 0x57, 0x09, 0x17, 0xA6, 0x09, 0xA2, 0xFA, 0xAE, 0x8A, 0x03, 0x60, 0xA5, 0x4E, 216 | 0x13, 0x1B, 0x22, 0xC3, 0xD5, 0x61, 0xDA, 0x03, 0xA2, 0x04, 0x71, 0xD1, 0xE6, 0x82, 0xB1, 217 | 0x9E, 0x95, 0x39, 0x34, 0x1E, 0x0D, 0x6F, 0x2D, 0x2E, 0x69, 0x3A, 0x86, 0x89, 0x5C, 0x81, 218 | 0xF7, 0xBE, 0xDA, 0xB1, 0xC6, 0xB8, 0x5F, 0x0B, 0x48, 0xF6, 0x02, 0x67, 0xD4, 0x78, 0x81, 219 | 0x59, 0x62, 0xCA, 0xC7, 0x0C, 0x56, 0x2C, 0x18, 0x18, 0xA6, 0x7E, 0x46, 0xDB, 0xBA, 0xAC, 220 | 0x79, 0x9E, 0x6D, 0x40, 0xFF, 0x29, 0x26, 0x50, 0x90, 0x7A, 0x69, 0x40, 0xE2, 0xDA, 0xD1, 221 | 0x0F, 0x68, 0xC9, 0xD0, 0xEF, 0x93, 0x17, 0x6F, 0xDB, 0x45, 0x13, 0x26, 0x8D, 0x61, 0x62, 222 | 0x50, 0x2A, 0x7B, 0x84, 0x17, 0xF3, 0x98, 0x52, 0x14, 0x82, 0xDF, 0xC3, 0x4F, 0xA5, 0x62, 223 | 0x82, 0x3F, 0xA2, 0x0B, 0x6C, 0x5A, 0x0C, 0x86, 0x4F, 0x3B, 0x07, 0x08, 0xCE, 0xFA, 0x2A, 224 | 0x7B, 0x2A, 0x63, 0x64, 0xAD, 0x3A, 0xB6, 0x5E, 0xE4, 0xC9, 0xAC, 0x0E, 0xF9, 0x14, 0x51, 225 | 0x21, 0xE1, 0x72, 0x1E, 0x23, 0x1F, 0xCF, 0x62, 0xFB, 0xF6, 0x62, 0x82, 0x78, 0x2A, 0x2D, 226 | 0xF5, 0x88, 0x02, 0xDE, 0x67, 0x63, 0xC2, 0x63, 0x7B, 0xA0, 0xF2, 0xF3, 0x07, 0x98, 0xF5, 227 | 0x23, 0xEF, 0x96, 0x02, 0xCF, 0x13, 0xB6, 0xA2, 0xB1, 0xC3, 0xE3, 0xC2, 0x55, 0x72, 0xC0, 228 | 0x03, 0x74, 0x4D, 0xD3, 0xF6, 0xF4, 0x5D, 0x1F, 0x60, 0x13, 0xD2, 0x06, 0x77, 0x6B, 0x84, 229 | 0x14, 0x10, 0xA7, 0xEF, 0x54, 0x66, 0x73, 0xFA, 0xF4, 0x5B, 0xF1, 0x1E, 0xE3, 0x0C, 0x73, 230 | 0x0E, 0xB3, 0xB7, 0xAC, 0xC3, 0x9D, 0xDC, 0x9B, 0x30, 0xE4, 0x45, 0x4A, 0xA3, 0xF7, 0xE1, 231 | 0xC5, 0xCC, 0x54, 0x55, 0x54, 0xFB, 0x3E, 0xD4, 0x73, 0xEB, 0xBC, 0xAD, 0xC9, 0xA4, 0xEB, 232 | 0xC0, 0x37, 0xF9, 0xED, 0x6B, 0x51, 0x6E, 0x19, 0x25, 0xC4, 0xAD, 0xE6, 0x26, 0xF7, 0x5B, 233 | 0x41, 0x2F, 0xAA, 0xD0, 0x12, 0xFC, 0x30, 0xAB, 0xCA, 0xB6, 0x4E, 0xDC, 0x62, 0x9F, 0x49, 234 | 0x9E, 0xC2, 0x28, 0x4D, 0x8C, 0x7C, 0xEB, 0xA0, 0xD3, 0xC7, 0x17, 0xE8, 0x6B, 0x1F, 0xB0, 235 | 0x11, 0x09, 0xD0, 0x08, 0x80, 0x22, 0xC9, 0x88, 0xC6, 0xD2, 0x1D, 0xD1, 0x69, 0xD8, 0x6B, 236 | 0x67, 0x80, 0x29, 0x69, 0x46, 0x42, 0x91, 0x7F, 0x9F, 0x9F, 0x3A, 0x81, 0x94, 0x5F, 0xE3, 237 | 0x7D, 0xEE, 0xF2, 0xAF, 0xB1, 0xAC, 0x9C, 0x7B, 0x21, 0x3A, 0x71, 0x03, 0xCF, 0x74, 0x1C, 238 | 0x61, 0xB8, 0x71, 0xB0, 0xE7, 0xDE, 0xD9, 0x2D, 0xE4, 0xB6, 0xE0, 0xF5, 0x0F, 0x45, 0x1F, 239 | 0x3A, 0x51, 0x6B, 0x6C, 0x06, 0x8E, 0x69, 0x6A, 0x93, 0x50, 0x27, 0x68, 0x9C, 0x54, 0x02, 240 | 0x0E, 0x41, 0xC8, 0xE3, 0xF1, 0x36, 0x26, 0x60, 0x35, 0x75, 0x0F, 0xAF, 0x2E, 0x9F, 0x7E, 241 | 0x4E, 0x4B, 0x6D, 0x07, 0x90, 0x23, 0x7C, 0xC7, 0x4F, 0x46, 0xC3, 0x1B, 0xA7, 0xA8, 0x52, 242 | 0x8B, 0x47, 0xC3, 0x37, 0x35, 0xD3, 0x60, 0xF3, 0x83, 0x29, 0x41, 0x63, 0xF1, 0xB5, 0xB4, 243 | 0xDF, 0x69, 0x4A, 0x3D, 0xC8, 0xDF, 0xD9, 0x27, 0xD8, 0xA9, 0xA2, 0x72, 0xBB, 0xE4, 0xE6, 244 | 0x3E, 0x18, 0x9F, 0x5E, 0x21, 0x8A, 0xC4, 0x0E, 0xB8, 0x14, 0x9F, 0x62, 0x3F, 0x61, 0x43, 245 | 0x76, 0x53, 0xBB, 0xB2, 0x70, 0x25, 0x34, 0x0F, 0xEC, 0x7F, 0x6B, 0xD9, 0x7D, 0x50, 0xFF, 246 | 0x23, 0x7D, 0xF6, 0x18, 0x9D, 0x4A, 0xD5, 0x19, 0xFF, 0x1B, 0x7F, 0xFB, 0xF4, 0x96, 0x53, 247 | 0x27, 0xCC, 0x7B, 0x91, 0x63, 0xE8, 0xB6, 0x46, 0xCA, 0x47, 0x99, 0xCD, 0x41, 0x49, 0x9A, 248 | 0xA4, 0xF9, 0x50, 0x11, 0x72, 0xC1, 0x59, 0x3E, 0x09, 0xB6, 0xA7, 0x1E, 0xF6, 0xEF, 0xCB, 249 | 0x79, 0xA5, 0x0D, 0xAB, 0x38, 0x9D, 0xF2, 0x30, 0x74, 0xE1, 0x41, 0xE5, 0x46, 0x34, 0x77, 250 | 0x3A, 0x92, 0x6D, 0xD2, 0x09, 0xDB, 0xDD, 0xF7, 0x0D, 0xBB, 0x83, 0x24, 0x8A, 0xFB, 0x5B, 251 | 0x53, 0xFD, 0x00, 0xED, 0x5A, 0xC3, 0xFD, 0x3E, 0x5E, 0xA7, 0x7E, 0x1E, 0x2A, 0x86, 0xD0, 252 | 0x06, 0x9A, 0x06, 0x7E, 0xB2, 0xDB, 0x8B, 0xD6, 0x0E, 0xCC, 0x57, 0x67, 0x5B, 0x48, 0xF6, 253 | 0x5F, 0x27, 0x5C, 0xBD, 0xEB, 0x75, 0x2B, 0x7C, 0x38, 0x74, 0x1A, 0xE2, 0x5E, 0x2C, 0x3C, 254 | 0x8A, 0xCC, 0x06, 0x4A, 0xD5, 0x88, 0xE2, 0x71, 0x05, 0x14, 0xE2, 0xC5, 0x6C, 0xF7, 0xC6, 255 | 0xCF, 0x20, 0xD0, 0xB9, 0x4C, 0x65, 0x72, 0x23, 0x29, 0x6C, 0xF6, 0xC7, 0x2C, 0xC5, 0xAA, 256 | 0xF6, 0xE9, 0xF1, 0xF9, 0xF3, 0x3C, 0xA2, 0x3A, 0xD3, 0x2B, 0x2A, 0x71, 0x15, 0x16, 0xFD, 257 | 0xAD, 0xFC, 0xE6, 0xF2, 0xBD, 0xA8, 0xC9, 0x97, 0xE8, 0x21, 0x1A, 0x73, 0x3A, 0xEE, 0x48, 258 | 0x71, 0xB7, 0x23, 0x16, 0xE6, 0x4A, 0xD0, 0xAD, 0x3E, 0x76, 0x5A, 0x87, 0x08, 0x18, 0xDD, 259 | 0xCC, 0x4F, 0x3F, 0x06, 0x6A, 0x71, 0x0D, 0x28, 0xC6, 0x90, 0xA3, 0x51, 0x72, 0xA7, 0xDD, 260 | 0x1D, 0x10, 0xAC, 0xF6, 0x4B, 0xE2, 0xAF, 0x57, 0xFF, 0xBD, 0x93, 0xA8, 0x87, 0xD9, 0x75, 261 | 0xE1, 0x30, 0x95, 0xE4, 0x2A, 0x47, 0xBE, 0xA7, 0x76, 0x8C, 0x0F, 0xF2, 0x0E, 0x62, 0x60, 262 | }; 263 | 264 | static final GroupElement[] SPAKE_N_SMALL_PRECOMP; 265 | static final GroupElement[] SPAKE_M_SMALL_PRECOMP; 266 | 267 | static { 268 | Ed25519CurveParameterSpec spec = Ed25519.getSpec(); 269 | SPAKE_N_SMALL_PRECOMP = getGEFromTable(spec.getCurve(), PRECOMP_TABLE_N); 270 | SPAKE_M_SMALL_PRECOMP = getGEFromTable(spec.getCurve(), PRECOMP_TABLE_M); 271 | } 272 | 273 | private final byte[] myName; 274 | private final byte[] theirName; 275 | private final Spake2Role myRole; 276 | private final byte[] privateKey = new byte[32]; 277 | private final byte[] myMsg = new byte[32]; 278 | private final byte[] passwordScalar = new byte[32]; 279 | private final byte[] passwordHash = new byte[64]; 280 | private final Ed25519CurveParameterSpec curveSpec; 281 | 282 | private State state; 283 | private boolean disablePasswordScalarHack; 284 | private boolean isDestroyed = false; 285 | 286 | public Spake2Context(Spake2Role myRole, 287 | final byte[] myName, 288 | final byte[] theirName) { 289 | this.myRole = myRole; 290 | this.myName = new byte[myName.length]; 291 | this.theirName = new byte[theirName.length]; 292 | this.state = State.Init; 293 | 294 | System.arraycopy(myName, 0, this.myName, 0, myName.length); 295 | System.arraycopy(theirName, 0, this.theirName, 0, theirName.length); 296 | 297 | curveSpec = Ed25519.getSpec(); 298 | } 299 | 300 | public void setDisablePasswordScalarHack(boolean disablePasswordScalarHack) { 301 | this.disablePasswordScalarHack = disablePasswordScalarHack; 302 | } 303 | 304 | public boolean isDisablePasswordScalarHack() { 305 | return disablePasswordScalarHack; 306 | } 307 | 308 | public Spake2Role getMyRole() { 309 | return myRole; 310 | } 311 | 312 | public byte[] getMyMsg() { 313 | return myMsg; 314 | } 315 | 316 | public byte[] getMyName() { 317 | return myName; 318 | } 319 | 320 | public byte[] getTheirName() { 321 | return theirName; 322 | } 323 | 324 | @Override 325 | public boolean isDestroyed() { 326 | return isDestroyed; 327 | } 328 | 329 | @Override 330 | public void destroy() { 331 | isDestroyed = true; 332 | Arrays.fill(privateKey, (byte) 0); 333 | Arrays.fill(myMsg, (byte) 0); 334 | Arrays.fill(passwordScalar, (byte) 0); 335 | Arrays.fill(passwordHash, (byte) 0); 336 | } 337 | 338 | /** 339 | * @param password Shared password. 340 | * @return A message of size {@link #MAX_MSG_SIZE}. 341 | * @throws IllegalArgumentException If SHA-512 is unavailable for some reason. 342 | * @throws IllegalStateException If the message has already been generated. 343 | */ 344 | public byte[] generateMessage(final byte[] password) throws IllegalArgumentException, IllegalStateException { 345 | byte[] privateKey = new byte[64]; 346 | new SecureRandom().nextBytes(privateKey); 347 | System.out.printf("PVKEY(%s): %s%n", myRole, Utils.bytesToHex(privateKey)); 348 | return generateMessage(password, privateKey); 349 | } 350 | 351 | // Package private method for testing purposes 352 | byte[] generateMessage(final byte[] password, byte[] privateKey) throws IllegalArgumentException, IllegalStateException { 353 | if (isDestroyed) { 354 | throw new IllegalStateException("The context was destroyed."); 355 | } 356 | if (this.state != State.Init) { 357 | throw new IllegalStateException("Invalid state: " + this.state); 358 | } 359 | 360 | Ed25519ScalarOps scalarOps = curveSpec.getScalarOps(); 361 | 362 | System.arraycopy(scalarOps.reduce(privateKey), 0, privateKey, 0, 32); 363 | // Multiply by the cofactor (eight) so that we'll clear it when operating on 364 | // the peer's point later in the protocol. 365 | leftShift3(privateKey); 366 | System.arraycopy(privateKey, 0, this.privateKey, 0, this.privateKey.length); 367 | 368 | final GroupElement P = curveSpec.getB().scalarMultiply(this.privateKey); 369 | 370 | byte[] passwordTmp = getHash("SHA-512", password); // 64 byte 371 | System.arraycopy(passwordTmp, 0, this.passwordHash, 0, this.passwordHash.length); 372 | 373 | /** 374 | * Due to a copy-paste error, the call to {@link #leftShift3(byte[])} was omitted after reducing it, just above. 375 | * This meant that {@link #passwordScalar} was not a multiple of eight to clear the cofactor and thus three bits 376 | * of the password hash would leak. In order to fix this in a unilateral way, points of small order are added to 377 | * the mask point such as that it is in the prime-order subgroup. Since the ephemeral scalar is a multiple of 378 | * eight, these points will cancel out when calculating the shared secret. 379 | * 380 | * Adding points of small order is the same as adding multiples of the prime order to the password scalar. Since 381 | * that's faster, this what is done below. {@link #l} is a large prime, thus, odd, thus the LSB is one. So, 382 | * adding it will flip the LSB. Adding twice, it will flip the next bit, and so on for all the bottom three bits. 383 | */ 384 | Scalar passwordScalar = new Scalar(scalarOps.reduce(passwordTmp)); 385 | 386 | /** 387 | * passwordScalar is the result of scalar reducing and thus is, at most, $l-1$. In the following, we may add 388 | * $l+2×l+4×l$ for a max value of $8×l-1$. That is less than $2^256$ as required. 389 | */ 390 | 391 | if (!this.disablePasswordScalarHack) { 392 | Scalar order = new Scalar(l); 393 | Scalar tmp = new Scalar(); 394 | tmp.copy(order.cmov(tmp, isEqual(passwordScalar.getByte(0) & 1, 1))); 395 | passwordScalar.copy(passwordScalar.add(tmp)); 396 | order.copy(order.dbl()); 397 | 398 | tmp.reset(); 399 | tmp.copy(order.cmov(tmp, isEqual(passwordScalar.getByte(0) & 2, 2))); 400 | passwordScalar.copy(passwordScalar.add(tmp)); 401 | order.copy(order.dbl()); 402 | 403 | tmp.reset(); 404 | tmp.copy(order.cmov(tmp, isEqual(passwordScalar.getByte(0) & 4, 4))); 405 | passwordScalar.copy(passwordScalar.add(tmp)); 406 | 407 | assert ((passwordScalar.getByte(0) & 7) == 0); 408 | } 409 | 410 | System.arraycopy(passwordScalar.getBytes(), 0, this.passwordScalar, 0, this.passwordScalar.length); 411 | 412 | // mask = h(password) * . 413 | GroupElement mask = geScalarMultiplySmallPrecomp(curveSpec.getCurve(), this.passwordScalar, 414 | this.myRole == Spake2Role.Alice ? SPAKE_M_SMALL_PRECOMP : SPAKE_N_SMALL_PRECOMP); 415 | 416 | // P* = P + mask. 417 | GroupElement PStar = P.add(mask.toCached()).toP2(); 418 | 419 | System.arraycopy(PStar.toByteArray(), 0, this.myMsg, 0, this.myMsg.length); 420 | this.state = State.MsgGenerated; 421 | return this.myMsg.clone(); 422 | } 423 | 424 | /** 425 | * @param theirMsg Message generated/received from the other end. 426 | * @return Key of size {@link #MAX_KEY_SIZE}. 427 | * @throws IllegalArgumentException If the message is invalid or SHA-512 is unavailable for some reason. 428 | * @throws IllegalStateException If the key has already been generated. 429 | */ 430 | public byte[] processMessage(final byte[] theirMsg) throws IllegalArgumentException, IllegalStateException { 431 | if (isDestroyed) { 432 | throw new IllegalStateException("The context was destroyed."); 433 | } 434 | if (this.state != State.MsgGenerated) { 435 | throw new IllegalStateException("Invalid state: " + this.state); 436 | } 437 | if (theirMsg.length != 32) { 438 | throw new IllegalArgumentException("Peer's message is not 32 bytes"); 439 | } 440 | 441 | GroupElement QStar = curveSpec.getCurve().fromBytesNegateVarTime(theirMsg); 442 | if (QStar == null) { 443 | throw new IllegalArgumentException("Point received from peer was not on the curve."); 444 | } 445 | 446 | System.out.printf("Q*(%s): %s%n", myRole, Utils.bytesToHex(QStar.toByteArray())); 447 | 448 | // Unmask peer's value. 449 | GroupElement peersMask = geScalarMultiplySmallPrecomp(curveSpec.getCurve(), this.passwordScalar, 450 | this.myRole == Spake2Role.Alice ? SPAKE_N_SMALL_PRECOMP : SPAKE_M_SMALL_PRECOMP); 451 | 452 | System.out.printf("PEER'S MASK(%s): %s%n", myRole, Utils.bytesToHex(peersMask.toByteArray())); 453 | 454 | GroupElement QExt = QStar.sub(peersMask.toCached()).toP3(); 455 | // FIXME: Create a single precomp converter or fix generating single precompute 456 | GroupElement QPrecomp = new GroupElement(QExt.getCurve(), GroupElement.Representation.P3, QExt.getX(), 457 | QExt.getY(), QExt.getZ(), QExt.getT(), true, true); 458 | 459 | System.out.printf("QExt(%s): %s%n", myRole, Utils.bytesToHex(QExt.toByteArray())); 460 | 461 | byte[] dhShared = QPrecomp.scalarMultiply(this.privateKey).toByteArray(); 462 | 463 | System.out.printf("DH(%s): %s%n", myRole, Utils.bytesToHex(dhShared)); 464 | 465 | MessageDigest sha; 466 | try { 467 | sha = MessageDigest.getInstance("SHA-512"); 468 | } catch (NoSuchAlgorithmException e) { 469 | throw new IllegalArgumentException("SHA-512 algorithm is not supported."); 470 | } 471 | if (this.myRole == Spake2Role.Alice) { 472 | updateWithLengthPrefix(sha, this.myName, this.myName.length); 473 | updateWithLengthPrefix(sha, this.theirName, this.theirName.length); 474 | updateWithLengthPrefix(sha, this.myMsg, this.myMsg.length); 475 | updateWithLengthPrefix(sha, theirMsg, 32); 476 | } else { // Bob 477 | updateWithLengthPrefix(sha, this.theirName, this.theirName.length); 478 | updateWithLengthPrefix(sha, this.myName, this.myName.length); 479 | updateWithLengthPrefix(sha, theirMsg, 32); 480 | updateWithLengthPrefix(sha, this.myMsg, this.myMsg.length); 481 | } 482 | updateWithLengthPrefix(sha, dhShared, dhShared.length); 483 | updateWithLengthPrefix(sha, this.passwordHash, this.passwordHash.length); 484 | 485 | byte[] key = sha.digest(); 486 | this.state = State.KeyGenerated; 487 | 488 | return key.clone(); 489 | } 490 | 491 | /** 492 | * Multiplies n with 8 by shifting it 3 times to the left 493 | * 494 | * @param n 32 bytes value 495 | */ 496 | private static void leftShift3(byte[] n) { 497 | int carry = 0; 498 | for (int i = 0; i < 32; i++) { 499 | int next_carry = (byte) ((n[i] & 0xFF) >>> 5); 500 | n[i] = (byte) ((n[i] << 3) | carry); 501 | carry = next_carry; 502 | } 503 | } 504 | 505 | /** 506 | * l = 2^252 + 27742317777372353535851937790883648493 507 | */ 508 | private static final byte[] l = Utils.hexToBytes("edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010"); 509 | 510 | 511 | private static void updateWithLengthPrefix(MessageDigest sha, final byte[] data, int len) { 512 | byte[] len_le = new byte[8]; 513 | long l = len; 514 | int i; 515 | 516 | for (i = 0; i < 8; i++) { 517 | len_le[i] = (byte) (l & 0xFF); 518 | l = (l >>> 8) & 0xFFFF_FFFFL; 519 | } 520 | 521 | sha.update(len_le); 522 | sha.update(data); 523 | } 524 | 525 | private GroupElement geScalarMultiplySmallPrecomp(Curve curve, 526 | final byte[] a /* 32 bytes */, 527 | final GroupElement[] precompTable) { 528 | GroupElement h = curve.getZero(GroupElement.Representation.P3); 529 | // This loop does 64 additions and 64 doublings to calculate the result. 530 | for (long i = 63; i >= 0; i--) { 531 | int index = 0; 532 | 533 | for (long j = 0; j < 4; j++) { 534 | byte bit = (byte) (1 & (a[(int) ((8 * j) + (i >>> 3))] >>> (i & 7))); 535 | index |= (bit << j); 536 | } 537 | 538 | GroupElement e = curve.getZero(GroupElement.Representation.PRECOMP); 539 | for (int j = 1; j < 16; j++) { 540 | e = e.cmov(precompTable[j - 1], Utils.equal(index, j)); 541 | } 542 | 543 | h = h.add(h.toCached()).toP3().madd(e).toP3(); 544 | } 545 | return h; 546 | } 547 | 548 | // Package private for testing 549 | static byte[] getHash(String algo, byte[] bytes) throws IllegalArgumentException { 550 | MessageDigest md; 551 | try { 552 | md = MessageDigest.getInstance(algo); 553 | } catch (NoSuchAlgorithmException e) { 554 | throw new IllegalArgumentException("Invalid hashing algorithm " + algo); 555 | } 556 | md.reset(); 557 | return md.digest(bytes); 558 | } 559 | 560 | /** 561 | * @param a 32 bit value 562 | * @param b 32 bit value 563 | * @return 0xff...f if a == b and 0x0 otherwise. 564 | */ 565 | private static long isEqual(long a, long b) { 566 | return isZero(a ^ b); 567 | } 568 | 569 | /** 570 | * @param a 32 bit value 571 | * @return 0xff...f if a == 0 and 0x0 otherwise. 572 | */ 573 | private static long isZero(long a) { 574 | return copyMsbToEveryBit(~a & (a - 1)); 575 | } 576 | 577 | /** 578 | * @param a 32 bit value 579 | * @return The given value with the MSB copied to all the other bits. 580 | */ 581 | private static long copyMsbToEveryBit(long a) { 582 | // 2's complement of MSB 583 | return -(a >>> 63); 584 | } 585 | 586 | private enum State { 587 | Init, 588 | MsgGenerated, 589 | KeyGenerated, 590 | } 591 | 592 | static class Scalar { 593 | private final byte[] bytes; 594 | 595 | public Scalar(byte[] bytes) { 596 | this.bytes = new byte[32]; 597 | System.arraycopy(bytes, 0, this.bytes, 0, 32); 598 | } 599 | 600 | public Scalar() { 601 | this.bytes = new byte[32]; 602 | } 603 | 604 | public byte getByte(int idx) { 605 | return bytes[idx]; 606 | } 607 | 608 | public byte[] getBytes() { 609 | return bytes; 610 | } 611 | 612 | public void reset() { 613 | Arrays.fill(this.bytes, (byte) 0); 614 | } 615 | 616 | /** 617 | * Copy bytes from the given scalar 618 | */ 619 | public void copy(Scalar scalar) { 620 | System.arraycopy(scalar.bytes, 0, this.bytes, 0, 32); 621 | } 622 | 623 | /** 624 | * @return A new scalar with bits copied from this if the mask is all ones. 625 | */ 626 | public Scalar cmov(Scalar src, long mask) { 627 | byte[] m = new byte[4]; 628 | m[0] = (byte) mask; 629 | m[1] = (byte) (mask >>> 8); 630 | m[2] = (byte) (mask >>> 16); 631 | m[3] = (byte) (mask >>> 24); 632 | byte[] bytes = new byte[32]; 633 | for (int i = 0; i < 8; ++i) { 634 | int idx = i * 4; 635 | for (int j = 0; j < 4; ++j) { 636 | bytes[idx + j] = (byte) (m[j] & this.bytes[idx + j] | (~m[j] & src.bytes[idx + j])); 637 | } 638 | } 639 | return new Scalar(bytes); 640 | } 641 | 642 | /** 643 | * @return 2 * this 644 | */ 645 | Scalar dbl() { 646 | byte[] bytes = new byte[32]; 647 | int carry = 0; 648 | for (int i = 0; i < 32; ++i) { 649 | int carry_out = (this.bytes[i] & 0xFF) >>> 7; 650 | bytes[i] = (byte) ((this.bytes[i] << 1) | carry); 651 | carry = carry_out; 652 | } 653 | return new Scalar(bytes); 654 | } 655 | 656 | /** 657 | * @return src + this 658 | */ 659 | Scalar add(Scalar src) { 660 | byte[] bytes = new byte[32]; 661 | int carry = 0; 662 | for (int i = 0; i < 32; ++i) { 663 | int tmp = (src.bytes[i] & 0xFF) + (this.bytes[i] & 0xFF) + carry; 664 | bytes[i] = (byte) tmp; 665 | carry = tmp >>> 8; 666 | } 667 | return new Scalar(bytes); 668 | } 669 | } 670 | } 671 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/muntashirakon/crypto/spake2/Spake2Role.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.spake2; 8 | 9 | public enum Spake2Role { 10 | Alice, 11 | Bob, 12 | } 13 | -------------------------------------------------------------------------------- /java/src/test/java/io/github/muntashirakon/crypto/spake2/Spake25519Test.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Muntashir Al-Islam 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package io.github.muntashirakon.crypto.spake2; 8 | 9 | import org.junit.Test; 10 | 11 | import java.math.BigInteger; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.Arrays; 14 | 15 | import io.github.muntashirakon.crypto.ed25519.Curve; 16 | import io.github.muntashirakon.crypto.ed25519.Ed25519; 17 | import io.github.muntashirakon.crypto.ed25519.Ed25519CurveParameterSpec; 18 | import io.github.muntashirakon.crypto.ed25519.Ed25519Field; 19 | import io.github.muntashirakon.crypto.ed25519.FieldElement; 20 | import io.github.muntashirakon.crypto.ed25519.GroupElement; 21 | import io.github.muntashirakon.crypto.ed25519.Utils; 22 | 23 | import static org.junit.Assert.*; 24 | 25 | public class Spake25519Test { 26 | private static final byte[] B_EIGHT = Utils.hexToBytes("0800000000000000000000000000000000000000000000000000000000000000"); 27 | 28 | // Based on http://ed25519.cr.yp.to/python/ed25519.py 29 | private static GroupElement ed25519Edwards(GroupElement P, GroupElement Q) { 30 | Curve curve = P.getCurve(); 31 | Ed25519Field field = curve.getField(); 32 | FieldElement x1 = P.getX(); 33 | FieldElement y1 = P.getY(); 34 | FieldElement x2 = Q.getX(); 35 | FieldElement y2 = Q.getY(); 36 | FieldElement dx1x2y1y2 = curve.getD().multiply(x1).multiply(x2).multiply(y1).multiply(y2); 37 | FieldElement x3 = x1.multiply(y2).add(x2.multiply(y1)).multiply(dx1x2y1y2.addOne().invert()); // (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2) 38 | FieldElement y3 = y1.multiply(y2).add(x1.multiply(x2)).multiply(field.ONE.subtract(dx1x2y1y2).invert()); // (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2) 39 | return GroupElement.p3(curve, x3, y3, field.ZERO, field.ZERO); 40 | } 41 | 42 | private static GroupElement ed25519ScalarMultiply(GroupElement P, BigInteger e) { 43 | GroupElement Q = P.getCurve().getZero(GroupElement.Representation.P3); 44 | Q = ed25519Edwards(Q, Q); 45 | Q = ed25519Edwards(Q, P); 46 | 47 | int len = e.bitLength() - 2; 48 | for (int c = len; c >= 0; --c) { 49 | Q = ed25519Edwards(Q, Q); 50 | if (e.testBit(c)) Q = ed25519Edwards(Q, P); 51 | } 52 | return Q; 53 | } 54 | 55 | static GroupElement[] precomputeTable(String seed) { 56 | GroupElement[] t = new GroupElement[15]; 57 | byte[] seedBytes = seed.getBytes(StandardCharsets.UTF_8); 58 | byte[] v = Spake2Context.getHash("SHA-256", seedBytes); 59 | Ed25519CurveParameterSpec spec = Ed25519.getSpec(); 60 | GroupElement P = spec.getCurve().createPoint(v, true); 61 | Curve curve = P.getCurve(); 62 | for (int i = 1; i < 16; ++i) { 63 | // (i >>> 3 & 1) * (1 << 192) 64 | BigInteger t1 = BigInteger.valueOf((i >>> 3 & 1)).multiply(BigInteger.ONE.shiftLeft(192)); 65 | // (i >>> 2 & 1) * (1 << 128) 66 | BigInteger t2 = BigInteger.valueOf((i >>> 2 & 1)).multiply(BigInteger.ONE.shiftLeft(128)); 67 | // (i >>> 1 & 1) * (1 << 64) 68 | BigInteger t3 = BigInteger.valueOf((i >>> 1 & 1)).multiply(BigInteger.ONE.shiftLeft(64)); 69 | // (i & 1) 70 | BigInteger t4 = BigInteger.ZERO.add(BigInteger.valueOf(i & 1)); 71 | // k is the sum of all the above 72 | BigInteger k = BigInteger.ZERO.add(t1).add(t2).add(t3).add(t4); 73 | 74 | GroupElement ge = ed25519ScalarMultiply(P, k); 75 | FieldElement x = ge.getX(); 76 | FieldElement y = ge.getY(); 77 | 78 | FieldElement ypx = y.add(x); 79 | FieldElement ymx = y.subtract(x); 80 | FieldElement xy2d = x.multiply(y).multiply(curve.get2D()); 81 | 82 | t[i - 1] = GroupElement.precomp(curve, ypx, ymx, xy2d); 83 | } 84 | return t; 85 | } 86 | 87 | private static byte[] printPrecompTable(GroupElement[] groupElements, String name) { 88 | byte[] table = new byte[groupElements.length * 3 * 32]; 89 | for (int i = 0; i < groupElements.length; ++i) { 90 | System.arraycopy(groupElements[i].getX().toByteArray(), 0, table, i * 96, 32); 91 | System.arraycopy(groupElements[i].getY().toByteArray(), 0, table, i * 96 + 32, 32); 92 | System.arraycopy(groupElements[i].getZ().toByteArray(), 0, table, i * 96 + 64, 32); 93 | } 94 | System.out.printf(" private static final int[] %s = new int[] {", name); 95 | for (int i = 0; i < table.length; ++i) { 96 | if (i % 15 == 0) System.out.printf("%n "); 97 | System.out.printf(" 0x%02X,", table[i]); 98 | } 99 | System.out.println("\n };"); 100 | return table; 101 | } 102 | 103 | private static void printCTable(byte[] table, String name) { 104 | System.out.printf("static const uint8_t %s[%d] = {", name, table.length); 105 | for (int i = 0; i < table.length; ++i) { 106 | if (i % 12 == 0) System.out.printf("%n "); 107 | System.out.printf(" 0x%02X,", table[i]); 108 | } 109 | System.out.println("\n};"); 110 | } 111 | 112 | @Test 113 | public void testPrintTables() { 114 | printCTable(Utils.hexToBytes("47f6c458e5f062db8427d2d9bb20c954a76d6943959756a18d11d45e1ad190f980a86d185a93ca1d3025c5febe3aac4045b34a39b1f511385ca97fc4332137f3"), "kAlicePrivKey"); 115 | printCTable(Utils.hexToBytes("a6bf9f9bf7819e0ded8c2dd82a1aa38acb2f8a6403429cff33d64ea9c40439d5fd7029811a5f5a8f7c89c8b44ac0b421f6b24ca2ba18d2069995831730cd8c5a"), "kBobPrivKey"); 116 | } 117 | 118 | @Test 119 | public void scalarTestCmov() { 120 | Spake2Context.Scalar scalar = new Spake2Context.Scalar(Utils.hexToBytes( 121 | "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010")); 122 | Spake2Context.Scalar zero = new Spake2Context.Scalar(); 123 | assertEquals("0000000000000000000000000000000000000000000000000000000000000000", 124 | Utils.bytesToHex(scalar.cmov(zero, 0).getBytes())); 125 | assertEquals("0100000000000000000000000000000000000000000000000000000000000000", 126 | Utils.bytesToHex(scalar.cmov(zero, 1).getBytes())); 127 | assertEquals("0500000000000000040000000400000000000000000000000000000000000000", 128 | Utils.bytesToHex(scalar.cmov(zero, 5).getBytes())); 129 | assertEquals("0100000010000000100000001000000000000000000000000000000000000000", 130 | Utils.bytesToHex(scalar.cmov(zero, 0x11).getBytes())); 131 | assertEquals("2100000010000000100000001000000000000000000000000000000000000000", 132 | Utils.bytesToHex(scalar.cmov(zero, 0x31).getBytes())); 133 | assertEquals("6100000010000000500000005000000000000000000000000000000000000000", 134 | Utils.bytesToHex(scalar.cmov(zero, 0x71).getBytes())); 135 | assertEquals("e900000018000000d0000000d800000000000000000000000000000000000000", 136 | Utils.bytesToHex(scalar.cmov(zero, 0xF9).getBytes())); 137 | } 138 | 139 | @Test 140 | public void scalarTestCmov2() { 141 | Spake2Context.Scalar scalar = new Spake2Context.Scalar(Utils.hexToBytes( 142 | "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010")); 143 | Spake2Context.Scalar base = new Spake2Context.Scalar(); 144 | base.copy(scalar.cmov(base, 0)); 145 | assertEquals("0000000000000000000000000000000000000000000000000000000000000000", 146 | Utils.bytesToHex(base.getBytes())); 147 | base.copy(scalar.cmov(base, 1)); 148 | assertEquals("0100000000000000000000000000000000000000000000000000000000000000", 149 | Utils.bytesToHex(base.getBytes())); 150 | base.copy(scalar.cmov(base, 5)); 151 | assertEquals("0500000000000000040000000400000000000000000000000000000000000000", 152 | Utils.bytesToHex(base.getBytes())); 153 | base.copy(scalar.cmov(base, 0x11)); 154 | assertEquals("0500000010000000140000001400000000000000000000000000000000000000", 155 | Utils.bytesToHex(base.getBytes())); 156 | base.copy(scalar.cmov(base, 0x31)); 157 | assertEquals("2500000010000000140000001400000000000000000000000000000000000000", 158 | Utils.bytesToHex(base.getBytes())); 159 | base.copy(scalar.cmov(base, 0x71)); 160 | assertEquals("6500000010000000540000005400000000000000000000000000000000000000", 161 | Utils.bytesToHex(base.getBytes())); 162 | base.copy(scalar.cmov(base, 0xF9)); 163 | assertEquals("ed00000018000000d4000000dc00000000000000000000000000000000000000", 164 | Utils.bytesToHex(base.getBytes())); 165 | } 166 | 167 | @Test 168 | public void scalarTestDbl() { 169 | Spake2Context.Scalar scalar = new Spake2Context.Scalar(Utils.hexToBytes( 170 | "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010")); 171 | Spake2Context.Scalar eight = new Spake2Context.Scalar(B_EIGHT); 172 | assertEquals("daa7ebb934c624b0ac39ef45bdf3bd2900000000000000000000000000000020", 173 | Utils.bytesToHex(scalar.dbl().getBytes())); 174 | assertEquals("1000000000000000000000000000000000000000000000000000000000000000", 175 | Utils.bytesToHex(eight.dbl().getBytes())); 176 | scalar.copy(scalar.dbl()); 177 | assertEquals("daa7ebb934c624b0ac39ef45bdf3bd2900000000000000000000000000000020", 178 | Utils.bytesToHex(scalar.getBytes())); 179 | } 180 | 181 | @Test 182 | public void scalarTestAdd() { 183 | Spake2Context.Scalar scalar = new Spake2Context.Scalar(Utils.hexToBytes( 184 | "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010")); 185 | Spake2Context.Scalar eight = new Spake2Context.Scalar(B_EIGHT); 186 | assertEquals("f5d3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", 187 | Utils.bytesToHex(eight.add(scalar).getBytes())); 188 | assertEquals("daa7ebb934c624b0ac39ef45bdf3bd2900000000000000000000000000000020", 189 | Utils.bytesToHex(scalar.add(scalar).getBytes())); 190 | } 191 | 192 | @Test 193 | public void checkIfGeneratedValuesAreSameForN() { 194 | GroupElement[] ge = precomputeTable("edwards25519 point generation seed (N)"); 195 | assertArrayEquals(ge, Spake2Context.SPAKE_N_SMALL_PRECOMP); 196 | } 197 | 198 | @Test 199 | public void checkIfGeneratedValuesAreSameForM() { 200 | GroupElement[] ge = precomputeTable("edwards25519 point generation seed (M)"); 201 | assertArrayEquals(ge, Spake2Context.SPAKE_M_SMALL_PRECOMP); 202 | } 203 | 204 | @Test 205 | public void spake2() { 206 | for (int i = 0; i < 20; i++) { 207 | System.out.println("========"); 208 | SPAKE2Run spake2 = new SPAKE2Run(); 209 | assertTrue(spake2.run()); 210 | assertTrue(spake2.keyMatches()); 211 | } 212 | } 213 | 214 | @Test 215 | public void oldAlice() { 216 | for (int i = 0; i < 20; i++) { 217 | SPAKE2Run spake2 = new SPAKE2Run(); 218 | spake2.aliceDisablePasswordScalarHack = true; 219 | assertTrue(spake2.run()); 220 | if (!spake2.keyMatches()) { 221 | System.out.printf("Iteration %d: Keys didn't match.\n", i); 222 | } 223 | } 224 | } 225 | 226 | @Test 227 | public void oldBob() { 228 | for (int i = 0; i < 20; i++) { 229 | SPAKE2Run spake2 = new SPAKE2Run(); 230 | spake2.bobDisablePasswordScalarHack = true; 231 | assertTrue(spake2.run()); 232 | if (!spake2.keyMatches()) { 233 | System.out.printf("Iteration %d: Keys didn't match.\n", i); 234 | } 235 | } 236 | } 237 | 238 | @Test 239 | public void wrongPassword() { 240 | SPAKE2Run spake2 = new SPAKE2Run(); 241 | spake2.bobPassword = "wrong password".getBytes(StandardCharsets.UTF_8); 242 | assertTrue(spake2.run()); 243 | assertFalse(spake2.keyMatches()); 244 | } 245 | 246 | @Test 247 | public void wrongNames() { 248 | SPAKE2Run spake2 = new SPAKE2Run(); 249 | spake2.aliceNames.second = "charlie"; 250 | spake2.bobNames.second = "charlie"; 251 | assertTrue(spake2.run()); 252 | assertFalse(spake2.keyMatches()); 253 | } 254 | 255 | @Test 256 | public void corruptMessages() { 257 | for (int i = 0; i < 8 * Spake2Context.MAX_MSG_SIZE; i++) { 258 | SPAKE2Run spake2 = new SPAKE2Run(); 259 | spake2.aliceCorruptMsgBit = i; 260 | assertFalse(spake2.run() && spake2.keyMatches()); 261 | } 262 | } 263 | 264 | // Based on https://android.googlesource.com/platform/external/boringssl/+/f9e0b0e17fabac35627f18f94a8954c3857784ac/src/crypto/curve25519/spake25519_test.cc 265 | private static class SPAKE2Run { 266 | private final Pair aliceNames = new Pair<>("adb pair client\u0000", "adb pair server\u0000"); 267 | private final Pair bobNames = new Pair<>("adb pair server\u0000", "adb pair client\u0000"); 268 | private final byte[] alicePassword = Utils.hexToBytes("353932373831E63DD959651C211600F3B6561D0B9D90AF09D0A4A453EE2059A480CC7C5A94D4D48933F9FFF5FE43317D52FA7BFF8F8BC4F3488B8007330FEC7C7EDC91C20E5D"); 269 | private byte[] bobPassword = alicePassword; 270 | private boolean aliceDisablePasswordScalarHack = false; 271 | private boolean bobDisablePasswordScalarHack = false; 272 | private int aliceCorruptMsgBit = -1; 273 | private boolean keyMatches = false; 274 | 275 | private boolean run() { 276 | Spake2Context alice = new Spake2Context( 277 | Spake2Role.Alice, 278 | aliceNames.first.getBytes(StandardCharsets.UTF_8), 279 | aliceNames.second.getBytes(StandardCharsets.UTF_8)); 280 | Spake2Context bob = new Spake2Context( 281 | Spake2Role.Bob, 282 | bobNames.first.getBytes(StandardCharsets.UTF_8), 283 | bobNames.second.getBytes(StandardCharsets.UTF_8)); 284 | 285 | if (aliceDisablePasswordScalarHack) { 286 | alice.setDisablePasswordScalarHack(true); 287 | } 288 | if (bobDisablePasswordScalarHack) { 289 | bob.setDisablePasswordScalarHack(true); 290 | } 291 | 292 | byte[] aliceMsg; 293 | byte[] bobMsg; 294 | 295 | try { 296 | aliceMsg = alice.generateMessage(alicePassword, Utils.hexToBytes("47f6c458e5f062db8427d2d9bb20c954a76d6943959756a18d11d45e1ad190f980a86d185a93ca1d3025c5febe3aac4045b34a39b1f511385ca97fc4332137f3")); 297 | bobMsg = bob.generateMessage(bobPassword, Utils.hexToBytes("a6bf9f9bf7819e0ded8c2dd82a1aa38acb2f8a6403429cff33d64ea9c40439d5fd7029811a5f5a8f7c89c8b44ac0b421f6b24ca2ba18d2069995831730cd8c5a")); 298 | } catch (Exception e) { 299 | return false; 300 | } 301 | 302 | System.out.printf("ALICE_MSG: %s%n", Utils.bytesToHex(aliceMsg)); 303 | System.out.printf("BOB_MSG: %s%n", Utils.bytesToHex(bobMsg)); 304 | 305 | if (aliceCorruptMsgBit >= 0 && aliceCorruptMsgBit < (8 * aliceMsg.length)) { 306 | aliceMsg[aliceCorruptMsgBit / 8] ^= 1 << (aliceCorruptMsgBit & 7); 307 | } 308 | 309 | byte[] aliceKey; 310 | byte[] bobKey; 311 | try { 312 | aliceKey = alice.processMessage(bobMsg); 313 | bobKey = bob.processMessage(aliceMsg); 314 | } catch (Exception e) { 315 | return false; 316 | } 317 | 318 | System.out.printf("ALICE_KEY: %s%n", Utils.bytesToHex(aliceKey)); 319 | System.out.printf("BOB_KEY: %s%n", Utils.bytesToHex(bobKey)); 320 | 321 | keyMatches = Arrays.equals(aliceKey, bobKey); 322 | 323 | return true; 324 | } 325 | 326 | boolean keyMatches() { 327 | return keyMatches; 328 | } 329 | } 330 | 331 | private static class Pair { 332 | private S first; 333 | private T second; 334 | 335 | public Pair(S first, T second) { 336 | this.first = first; 337 | this.second = second; 338 | } 339 | } 340 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ":java" 2 | include ":android" 3 | rootProject.name = "Spake2" 4 | --------------------------------------------------------------------------------