├── android ├── .gitignore ├── src │ ├── main │ │ ├── cpp │ │ │ ├── .gitignore │ │ │ ├── CMakeLists_CurrentPlatform.cmake │ │ │ ├── CMakeLists.txt │ │ │ └── spake2_jni.cpp │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── muntashirakon │ │ │ └── crypto │ │ │ └── spake2 │ │ │ ├── Spake2Role.java │ │ │ └── Spake2Context.java │ └── test │ │ └── java │ │ └── io │ │ └── github │ │ └── muntashirakon │ │ └── crypto │ │ └── spake2 │ │ ├── Utils.java │ │ └── Spake2ContextTest.java └── build.gradle ├── .gitignore ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitmodules ├── gradle.properties ├── jitpack.yml ├── java ├── src │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── muntashirakon │ │ │ └── crypto │ │ │ ├── spake2 │ │ │ ├── Spake2Role.java │ │ │ └── Spake2Context.java │ │ │ ├── ed25519 │ │ │ ├── Ed25519.java │ │ │ ├── FieldElement.java │ │ │ ├── Ed25519CurveParameterSpec.java │ │ │ ├── Utils.java │ │ │ ├── Ed25519Field.java │ │ │ ├── Curve.java │ │ │ ├── Ed25519LittleEndianEncoding.java │ │ │ ├── Ed25519FieldElement.java │ │ │ └── GroupElement.java │ │ │ └── x25519 │ │ │ └── x25519Scalar.java │ └── test │ │ └── java │ │ └── io │ │ └── github │ │ └── muntashirakon │ │ └── crypto │ │ └── spake2 │ │ └── Spake25519Test.java └── build.gradle ├── README.md ├── gradlew.bat ├── LICENSE └── gradlew /android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /.cxx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .gradle/ 3 | .DS_Store 4 | build/ 5 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ":java" 2 | include ":android" 3 | rootProject.name = "Spake2" 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MuntashirAkon/spake2-java/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/src/main/cpp/.gitignore: -------------------------------------------------------------------------------- 1 | CMakeFiles 2 | cmake_install.cmake 3 | CMakeCache.txt 4 | Makefile 5 | *.dylib 6 | *.so 7 | *.dll 8 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.ndk.suppressMinSdkVersionError=21 -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk21 3 | before_install: 4 | - sdk install java 21.0.4-tem 5 | - sdk use java 21.0.4-tem 6 | - sdk install maven 7 | - mvn -v -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=8fad3d78296ca518113f3d29016617c7f9367dc005f932bd9d93bf45ba46072b 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | ); 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 | ed25519curve.createPoint( // B 24 | Utils.hexToBytes("5866666666666666666666666666666666666666666666666666666666666666"), 25 | true)); // Precompute tables for B 26 | 27 | public static Ed25519CurveParameterSpec getSpec() { 28 | return ED_25519_CURVE_SPEC; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 = '2.2.0' 14 | 15 | repositories { 16 | mavenCentral() 17 | } 18 | 19 | publishing { 20 | publications { 21 | maven(MavenPublication) { 22 | artifactId = 'spake2-java' 23 | from components.java 24 | 25 | pom { 26 | name = 'SPAKE2-Java' 27 | description = 'Implementation of SPAKE2 protocol in Java, fully compatible with BoringSSL.' 28 | url = 'https://github.com/MuntashirAkon/spake2-java' 29 | licenses { 30 | license { 31 | name = 'GNU General Public License, Version 3.0 or later' 32 | url = 'https://www.gnu.org/licenses/gpl-3.0.txt' 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | java { 41 | sourceCompatibility = JavaVersion.VERSION_1_8 42 | targetCompatibility = JavaVersion.VERSION_1_8 43 | 44 | withJavadocJar() 45 | withSourcesJar() 46 | } 47 | 48 | javadoc { 49 | exclude 'io/github/muntashirakon/crypto/ed25519/**' 50 | if(JavaVersion.current().isJava9Compatible()) { 51 | options.addBooleanOption('html5', true) 52 | } 53 | } 54 | 55 | dependencies { 56 | testImplementation 'junit:junit:4.13.2' 57 | } 58 | -------------------------------------------------------------------------------- /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 | group = 'io.github.muntashirakon' 13 | version = '2.2.0' 14 | 15 | android { 16 | namespace "io.github.muntashirakon.crypto.spake2" 17 | compileSdkVersion 35 18 | buildToolsVersion "35.0.0" 19 | 20 | defaultConfig { 21 | minSdkVersion 1 22 | targetSdkVersion 35 23 | } 24 | 25 | buildTypes { 26 | release { 27 | minifyEnabled false 28 | externalNativeBuild { 29 | cmake { 30 | cppFlags "-Wl,-z,max-page-size=16384" 31 | } 32 | } 33 | } 34 | } 35 | externalNativeBuild { 36 | cmake { 37 | path 'src/main/cpp/CMakeLists.txt' 38 | } 39 | } 40 | compileOptions { 41 | sourceCompatibility JavaVersion.VERSION_1_8 42 | targetCompatibility JavaVersion.VERSION_1_8 43 | } 44 | publishing { 45 | singleVariant("release") { 46 | withSourcesJar() 47 | withJavadocJar() 48 | } 49 | } 50 | packagingOptions { 51 | jniLibs { 52 | useLegacyPackaging true 53 | } 54 | } 55 | } 56 | 57 | 58 | publishing { 59 | publications { 60 | release(MavenPublication) { 61 | artifactId = 'spake2-android' 62 | afterEvaluate { 63 | from components.release 64 | } 65 | } 66 | } 67 | } 68 | 69 | dependencies { 70 | implementation "androidx.annotation:annotation:1.9.1" 71 | 72 | testImplementation 'junit:junit:4.13.2' 73 | // testImplementation 'org.robolectric:robolectric:4.6.1' 74 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 Ed25519LittleEndianEncoding.encode(this); 29 | } 30 | 31 | public abstract boolean isNonZero(); 32 | 33 | public boolean isNegative() { 34 | return Ed25519LittleEndianEncoding.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/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 GroupElement B; 25 | 26 | /** 27 | * @param curve the curve 28 | * @param hashAlgo the JCA string for the hash algorithm 29 | * @param B the parameter B 30 | * @throws IllegalArgumentException if hash algorithm is unsupported or length is wrong 31 | */ 32 | public Ed25519CurveParameterSpec(Curve curve, String hashAlgo, GroupElement B) { 33 | try { 34 | MessageDigest hash = MessageDigest.getInstance(hashAlgo); 35 | // EdDSA hash function must produce 2b-bit output 36 | if (curve.getField().getb() / 4 != hash.getDigestLength()) 37 | throw new IllegalArgumentException("Hash output is not 2b-bit"); 38 | } catch (NoSuchAlgorithmException e) { 39 | throw new IllegalArgumentException("Unsupported hash algorithm"); 40 | } 41 | 42 | this.curve = curve; 43 | this.hashAlgo = hashAlgo; 44 | this.B = B; 45 | } 46 | 47 | public String getName() { 48 | return ED_25519; 49 | } 50 | 51 | public Curve getCurve() { 52 | return curve; 53 | } 54 | 55 | public String getHashAlgorithm() { 56 | return hashAlgo; 57 | } 58 | 59 | /** 60 | * @return the base (generator) 61 | */ 62 | public GroupElement getB() { 63 | return B; 64 | } 65 | 66 | @Override 67 | public int hashCode() { 68 | return hashAlgo.hashCode() ^ curve.hashCode() ^ B.hashCode(); 69 | } 70 | 71 | @Override 72 | public boolean equals(Object o) { 73 | if (o == this) 74 | return true; 75 | if (!(o instanceof Ed25519CurveParameterSpec)) 76 | return false; 77 | Ed25519CurveParameterSpec s = (Ed25519CurveParameterSpec) o; 78 | return hashAlgo.equals(s.getHashAlgorithm()) && 79 | curve.equals(s.getCurve()) && 80 | B.equals(s.getB()); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | 42 | public Ed25519Field(int b, byte[] q) { 43 | this.b = b; 44 | this.q = fromByteArray(q); 45 | 46 | // Set up constants 47 | ZERO = fromByteArray(B_ZERO); 48 | ONE = fromByteArray(B_ONE); 49 | TWO = fromByteArray(B_TWO); 50 | FOUR = fromByteArray(B_FOUR); 51 | FIVE = fromByteArray(B_FIVE); 52 | EIGHT = fromByteArray(B_EIGHT); 53 | 54 | // Precompute values 55 | qm2 = this.q.subtract(TWO); 56 | qm5d8 = this.q.subtract(FIVE).divide(EIGHT); 57 | } 58 | 59 | public FieldElement fromByteArray(byte[] x) { 60 | return Ed25519LittleEndianEncoding.decode(this, x); 61 | } 62 | 63 | public int getb() { 64 | return b; 65 | } 66 | 67 | public FieldElement getQ() { 68 | return q; 69 | } 70 | 71 | public FieldElement getQm2() { 72 | return qm2; 73 | } 74 | 75 | public FieldElement getQm5d8() { 76 | return qm5d8; 77 | } 78 | 79 | @Override 80 | public int hashCode() { 81 | return q.hashCode(); 82 | } 83 | 84 | @Override 85 | public boolean equals(Object obj) { 86 | if (!(obj instanceof Ed25519Field)) 87 | return false; 88 | Ed25519Field f = (Ed25519Field) obj; 89 | return b == f.b && q.equals(f.q); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /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 | private final byte[] mMyMsg = new byte[MAX_MSG_SIZE]; 30 | 31 | private boolean mDisablePasswordScalarHack; 32 | private boolean mIsDestroyed; 33 | 34 | public Spake2Context(@NonNull Spake2Role myRole, 35 | final byte[] myName, 36 | final byte[] theirName) { 37 | mCtx = allocNewContext(myRole.ordinal(), myName, theirName); 38 | if (mCtx == 0L) { 39 | throw new UnsupportedOperationException("Could not allocate native context"); 40 | } 41 | } 42 | 43 | @NonNull 44 | public byte[] getMyMsg() { 45 | return mMyMsg; 46 | } 47 | 48 | public boolean isDisablePasswordScalarHack() { 49 | return mDisablePasswordScalarHack; 50 | } 51 | 52 | public void setDisablePasswordScalarHack(boolean disablePasswordScalarHack) { 53 | mDisablePasswordScalarHack = disablePasswordScalarHack; 54 | throw new UnsupportedOperationException("Not implemented yet."); 55 | } 56 | 57 | /** 58 | * @param password Shared password. 59 | * @return A message of size {@link #MAX_MSG_SIZE}. 60 | * @throws IllegalStateException If the context was destroyed. 61 | */ 62 | public byte[] generateMessage(byte[] password) throws IllegalStateException { 63 | if (mIsDestroyed) { 64 | throw new IllegalStateException("The context was destroyed."); 65 | } 66 | byte[] myMsg = generateMessage(mCtx, password); 67 | if (myMsg == null) { 68 | throw new IllegalStateException("Generated empty message"); 69 | } 70 | System.arraycopy(myMsg, 0, this.mMyMsg, 0, MAX_MSG_SIZE); 71 | return myMsg; 72 | } 73 | 74 | /** 75 | * @param theirMessage Message generated/received from the other end. 76 | * @return Key of size {@link #MAX_KEY_SIZE}. 77 | * @throws IllegalStateException If the context was destroyed. 78 | */ 79 | public byte[] processMessage(byte[] theirMessage) throws IllegalStateException { 80 | if (mIsDestroyed) { 81 | throw new IllegalStateException("The context was destroyed."); 82 | } 83 | byte[] key = processMessage(mCtx, theirMessage); 84 | if (key == null) { 85 | throw new IllegalStateException("No key was returned"); 86 | } 87 | return key; 88 | } 89 | 90 | @Override 91 | public boolean isDestroyed() { 92 | return mIsDestroyed; 93 | } 94 | 95 | @Override 96 | public void destroy() { 97 | mIsDestroyed = true; 98 | destroy(mCtx); 99 | } 100 | 101 | private static native long allocNewContext(int myRole, byte[] myName, byte[] theirName); 102 | 103 | @Nullable 104 | private static native byte[] generateMessage(long ctx, byte[] password); 105 | 106 | @Nullable 107 | private static native byte[] processMessage(long ctx, byte[] theirMessage); 108 | 109 | private static native void destroy(long ctx); 110 | } 111 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | System.out.println("========"); 31 | SPAKE2Run spake2 = new SPAKE2Run(); 32 | spake2.aliceDisablePasswordScalarHack = true; 33 | assertTrue(spake2.run()); 34 | assertTrue(spake2.keyMatches()); 35 | } 36 | } 37 | 38 | @Test 39 | public void oldBob() { 40 | for (int i = 0; i < 20; i++) { 41 | System.out.println("========"); 42 | SPAKE2Run spake2 = new SPAKE2Run(); 43 | spake2.bobDisablePasswordScalarHack = true; 44 | assertTrue(spake2.run()); 45 | assertTrue(spake2.keyMatches()); 46 | } 47 | } 48 | 49 | @Test 50 | public void wrongPassword() { 51 | SPAKE2Run spake2 = new SPAKE2Run(); 52 | spake2.bobPassword = "wrong password".getBytes(StandardCharsets.UTF_8); 53 | assertTrue(spake2.run()); 54 | assertFalse(spake2.keyMatches()); 55 | } 56 | 57 | @Test 58 | public void wrongNames() { 59 | SPAKE2Run spake2 = new SPAKE2Run(); 60 | spake2.aliceNames.second = "charlie"; 61 | spake2.bobNames.second = "charlie"; 62 | assertTrue(spake2.run()); 63 | assertFalse(spake2.keyMatches()); 64 | } 65 | 66 | @Test 67 | public void corruptMessages() { 68 | for (int i = 0; i < 8 * Spake2Context.MAX_MSG_SIZE; i++) { 69 | SPAKE2Run spake2 = new SPAKE2Run(); 70 | spake2.aliceCorruptMsgBit = i; 71 | assertFalse(spake2.run() && spake2.keyMatches()); 72 | } 73 | } 74 | 75 | // Based on https://android.googlesource.com/platform/external/boringssl/+/f9e0b0e17fabac35627f18f94a8954c3857784ac/src/crypto/curve25519/spake25519_test.cc 76 | private static class SPAKE2Run { 77 | private final Pair aliceNames = new Pair<>("adb pair client\u0000", "adb pair server\u0000"); 78 | private final Pair bobNames = new Pair<>("adb pair server\u0000", "adb pair client\u0000"); 79 | private final byte[] alicePassword = Utils.hexToBytes("353932373831E63DD959651C211600F3B6561D0B9D90AF09D0A4A453EE2059A480CC7C5A94D4D48933F9FFF5FE43317D52FA7BFF8F8BC4F3488B8007330FEC7C7EDC91C20E5D"); 80 | private byte[] bobPassword = alicePassword; 81 | private boolean aliceDisablePasswordScalarHack = false; 82 | private boolean bobDisablePasswordScalarHack = false; 83 | private int aliceCorruptMsgBit = -1; 84 | private boolean keyMatches = false; 85 | 86 | private boolean run() { 87 | Spake2Context alice = new Spake2Context( 88 | Spake2Role.Alice, 89 | aliceNames.first.getBytes(StandardCharsets.UTF_8), 90 | aliceNames.second.getBytes(StandardCharsets.UTF_8)); 91 | Spake2Context bob = new Spake2Context( 92 | Spake2Role.Bob, 93 | bobNames.first.getBytes(StandardCharsets.UTF_8), 94 | bobNames.second.getBytes(StandardCharsets.UTF_8)); 95 | 96 | if (aliceDisablePasswordScalarHack) { 97 | alice.setDisablePasswordScalarHack(true); 98 | } 99 | 100 | if (bobDisablePasswordScalarHack) { 101 | bob.setDisablePasswordScalarHack(true); 102 | } 103 | 104 | byte[] aliceMsg; 105 | byte[] bobMsg; 106 | 107 | try { 108 | aliceMsg = alice.generateMessage(alicePassword); 109 | bobMsg = bob.generateMessage(bobPassword); 110 | } catch (Exception e) { 111 | return false; 112 | } 113 | 114 | System.out.printf("ALICE_MSG: %s%n", Utils.bytesToHex(aliceMsg)); 115 | System.out.printf("BOB_MSG: %s%n", Utils.bytesToHex(bobMsg)); 116 | 117 | if (aliceCorruptMsgBit >= 0 && aliceCorruptMsgBit < (8 * aliceMsg.length)) { 118 | aliceMsg[aliceCorruptMsgBit / 8] ^= 1 << (aliceCorruptMsgBit & 7); 119 | } 120 | 121 | byte[] aliceKey; 122 | byte[] bobKey; 123 | try { 124 | aliceKey = alice.processMessage(bobMsg); 125 | bobKey = bob.processMessage(aliceMsg); 126 | } catch (Exception e) { 127 | return false; 128 | } 129 | 130 | System.out.printf("ALICE_KEY: %s%n", Utils.bytesToHex(aliceKey)); 131 | System.out.printf("BOB_KEY: %s%n", Utils.bytesToHex(bobKey)); 132 | 133 | keyMatches = Arrays.equals(aliceKey, bobKey); 134 | 135 | return true; 136 | } 137 | 138 | boolean keyMatches() { 139 | return keyMatches; 140 | } 141 | } 142 | 143 | private static class Pair { 144 | private S first; 145 | private T second; 146 | 147 | public Pair(S first, T second) { 148 | this.first = first; 149 | this.second = second; 150 | } 151 | } 152 | } -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015 the original 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 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /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 final class Ed25519LittleEndianEncoding { 15 | /** 16 | * Encodes a given field element in its 32 byte representation. This is done in two steps: 17 | *

    18 | *
  1. Reduce the value of the field element modulo $p$. 19 | *
  2. Convert the field element to the 32 byte representation. 20 | *

21 | * The idea for the modulo $p$ reduction algorithm is as follows: 22 | *

23 | *

Assumption:

24 | *
    25 | *
  • $p = 2^{255} - 19$ 26 | *
  • $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$. 27 | *
  • $h \cong r \mod p$, i.e. $h = r + q * p$ for some suitable $0 \le r \lt p$ and an integer $q$. 28 | *

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

31 | *

Proof:

32 | *

33 | * We begin with some very raw estimation for the bounds of some expressions: 34 | *

35 | * $$ 36 | * \begin{equation} 37 | * |h| \lt 2^{230} * 2^{30} = 2^{260} \Rightarrow |r + q * p| \lt 2^{260} \Rightarrow |q| \lt 2^{10}. \\ 38 | * \Rightarrow -1/4 \le a := 19^2 * 2^{-255} * q \lt 1/4. \\ 39 | * |h - 2^{230} * h_9| = |h_0 + \dots + 2^{204} * h_8| \lt 2^{204} * 2^{30} = 2^{234}. \\ 40 | * \Rightarrow -1/4 \le b := 19 * 2^{-255} * (h - 2^{230} * h_9) \lt 1/4 41 | * \end{equation} 42 | * $$ 43 | *

44 | * Therefore $0 \lt 1/2 - a - b \lt 1$. 45 | *

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

48 | * $$ 49 | * 0 \le x \lt 255 - 20 + 19 + 1 = 2^{255} \\ 50 | * \Rightarrow 0 \le 2^{-255} * x \lt 1. 51 | * $$ 52 | *

53 | * Since $q$ is an integer we have 54 | *

55 | * $$ 56 | * [q + 2^{-255} * x] = q \quad (1) 57 | * $$ 58 | *

59 | * Have a closer look at $x$: 60 | *

61 | * $$ 62 | * \begin{align} 63 | * 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) \\ 64 | * &= 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 \\ 65 | * &= h + 19 * 2^{-25} * h_9 + 1/2 - q^{255}. 66 | * \end{align} 67 | * $$ 68 | *

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

234 | * Return true if $x$ is in $\{1,3,5,\dots,q-2\}$
235 | * Return false if $x$ is in $\{0,2,4,\dots,q-1\}$ 236 | *

237 | * Preconditions: 238 | *

    239 | *
  • $|x|$ bounded by $1.1*2^{26},1.1*2^{25},1.1*2^{26},1.1*2^{25}$, etc. 240 | *
241 | * 242 | * @return true if $x$ is in $\{1,3,5,\dots,q-2\}$, false otherwise. 243 | */ 244 | public static boolean isNegative(FieldElement x) { 245 | byte[] s = encode(x); 246 | return (s[0] & 1) != 0; 247 | } 248 | 249 | } 250 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/muntashirakon/crypto/x25519/x25519Scalar.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.x25519; 8 | 9 | // x25519_sc 10 | @SuppressWarnings("PointlessBitwiseExpression") 11 | public final class x25519Scalar { 12 | private static long load_3(final byte[] in, int offset) { 13 | long result; 14 | result = in[offset] & 0xff; 15 | result |= (in[offset + 1] & 0xff) << 8; 16 | result |= (in[offset + 2] & 0xff) << 16; 17 | return result & 0xffffffffL; 18 | } 19 | 20 | private static long load_4(final byte[] in, int offset) { 21 | long result; 22 | result = in[offset] & 0xff; 23 | result |= (in[offset + 1] & 0xff) << 8; 24 | result |= (in[offset + 2] & 0xff) << 16; 25 | result |= in[offset + 3] << 24; 26 | return result & 0xffffffffL; 27 | } 28 | 29 | private static long int64_lshift21(long n) { 30 | return n << 21; 31 | } 32 | 33 | /** 34 | * 256^0*s[0] + 256^1*s[1] + ... + 256^31*s[31] = s mod l 35 | * where l = 2^252 + 27742317777372353535851937790883648493. 36 | *

37 | * This is in-place operation. 38 | * 39 | * @param s 256^0*s[0] + 256^1*s[1] + ... + 256^63*s[63] 40 | */ 41 | public static void reduce(final byte[] s) { 42 | if (s.length != 64) { 43 | throw new IllegalArgumentException("The input must be 64 bytes."); 44 | } 45 | 46 | long s0 = 2097151 & load_3(s, 0); 47 | long s1 = 2097151 & (load_4(s, 2) >> 5); 48 | long s2 = 2097151 & (load_3(s, 5) >> 2); 49 | long s3 = 2097151 & (load_4(s, 7) >> 7); 50 | long s4 = 2097151 & (load_4(s, 10) >> 4); 51 | long s5 = 2097151 & (load_3(s, 13) >> 1); 52 | long s6 = 2097151 & (load_4(s, 15) >> 6); 53 | long s7 = 2097151 & (load_3(s, 18) >> 3); 54 | long s8 = 2097151 & load_3(s, 21); 55 | long s9 = 2097151 & (load_4(s, 23) >> 5); 56 | long s10 = 2097151 & (load_3(s, 26) >> 2); 57 | long s11 = 2097151 & (load_4(s, 28) >> 7); 58 | long s12 = 2097151 & (load_4(s, 31) >> 4); 59 | long s13 = 2097151 & (load_3(s, 34) >> 1); 60 | long s14 = 2097151 & (load_4(s, 36) >> 6); 61 | long s15 = 2097151 & (load_3(s, 39) >> 3); 62 | long s16 = 2097151 & load_3(s, 42); 63 | long s17 = 2097151 & (load_4(s, 44) >> 5); 64 | long s18 = 2097151 & (load_3(s, 47) >> 2); 65 | long s19 = 2097151 & (load_4(s, 49) >> 7); 66 | long s20 = 2097151 & (load_4(s, 52) >> 4); 67 | long s21 = 2097151 & (load_3(s, 55) >> 1); 68 | long s22 = 2097151 & (load_4(s, 57) >> 6); 69 | long s23 = (load_4(s, 60) >> 3); 70 | long carry0; 71 | long carry1; 72 | long carry2; 73 | long carry3; 74 | long carry4; 75 | long carry5; 76 | long carry6; 77 | long carry7; 78 | long carry8; 79 | long carry9; 80 | long carry10; 81 | long carry11; 82 | long carry12; 83 | long carry13; 84 | long carry14; 85 | long carry15; 86 | long carry16; 87 | 88 | s11 += s23 * 666643; 89 | s12 += s23 * 470296; 90 | s13 += s23 * 654183; 91 | s14 -= s23 * 997805; 92 | s15 += s23 * 136657; 93 | s16 -= s23 * 683901; 94 | s23 = 0; 95 | 96 | s10 += s22 * 666643; 97 | s11 += s22 * 470296; 98 | s12 += s22 * 654183; 99 | s13 -= s22 * 997805; 100 | s14 += s22 * 136657; 101 | s15 -= s22 * 683901; 102 | s22 = 0; 103 | 104 | s9 += s21 * 666643; 105 | s10 += s21 * 470296; 106 | s11 += s21 * 654183; 107 | s12 -= s21 * 997805; 108 | s13 += s21 * 136657; 109 | s14 -= s21 * 683901; 110 | s21 = 0; 111 | 112 | s8 += s20 * 666643; 113 | s9 += s20 * 470296; 114 | s10 += s20 * 654183; 115 | s11 -= s20 * 997805; 116 | s12 += s20 * 136657; 117 | s13 -= s20 * 683901; 118 | s20 = 0; 119 | 120 | s7 += s19 * 666643; 121 | s8 += s19 * 470296; 122 | s9 += s19 * 654183; 123 | s10 -= s19 * 997805; 124 | s11 += s19 * 136657; 125 | s12 -= s19 * 683901; 126 | s19 = 0; 127 | 128 | s6 += s18 * 666643; 129 | s7 += s18 * 470296; 130 | s8 += s18 * 654183; 131 | s9 -= s18 * 997805; 132 | s10 += s18 * 136657; 133 | s11 -= s18 * 683901; 134 | s18 = 0; 135 | 136 | carry6 = (s6 + (1 << 20)) >> 21; 137 | s7 += carry6; 138 | s6 -= int64_lshift21(carry6); 139 | carry8 = (s8 + (1 << 20)) >> 21; 140 | s9 += carry8; 141 | s8 -= int64_lshift21(carry8); 142 | carry10 = (s10 + (1 << 20)) >> 21; 143 | s11 += carry10; 144 | s10 -= int64_lshift21(carry10); 145 | carry12 = (s12 + (1 << 20)) >> 21; 146 | s13 += carry12; 147 | s12 -= int64_lshift21(carry12); 148 | carry14 = (s14 + (1 << 20)) >> 21; 149 | s15 += carry14; 150 | s14 -= int64_lshift21(carry14); 151 | carry16 = (s16 + (1 << 20)) >> 21; 152 | s17 += carry16; 153 | s16 -= int64_lshift21(carry16); 154 | 155 | carry7 = (s7 + (1 << 20)) >> 21; 156 | s8 += carry7; 157 | s7 -= int64_lshift21(carry7); 158 | carry9 = (s9 + (1 << 20)) >> 21; 159 | s10 += carry9; 160 | s9 -= int64_lshift21(carry9); 161 | carry11 = (s11 + (1 << 20)) >> 21; 162 | s12 += carry11; 163 | s11 -= int64_lshift21(carry11); 164 | carry13 = (s13 + (1 << 20)) >> 21; 165 | s14 += carry13; 166 | s13 -= int64_lshift21(carry13); 167 | carry15 = (s15 + (1 << 20)) >> 21; 168 | s16 += carry15; 169 | s15 -= int64_lshift21(carry15); 170 | 171 | s5 += s17 * 666643; 172 | s6 += s17 * 470296; 173 | s7 += s17 * 654183; 174 | s8 -= s17 * 997805; 175 | s9 += s17 * 136657; 176 | s10 -= s17 * 683901; 177 | s17 = 0; 178 | 179 | s4 += s16 * 666643; 180 | s5 += s16 * 470296; 181 | s6 += s16 * 654183; 182 | s7 -= s16 * 997805; 183 | s8 += s16 * 136657; 184 | s9 -= s16 * 683901; 185 | s16 = 0; 186 | 187 | s3 += s15 * 666643; 188 | s4 += s15 * 470296; 189 | s5 += s15 * 654183; 190 | s6 -= s15 * 997805; 191 | s7 += s15 * 136657; 192 | s8 -= s15 * 683901; 193 | s15 = 0; 194 | 195 | s2 += s14 * 666643; 196 | s3 += s14 * 470296; 197 | s4 += s14 * 654183; 198 | s5 -= s14 * 997805; 199 | s6 += s14 * 136657; 200 | s7 -= s14 * 683901; 201 | s14 = 0; 202 | 203 | s1 += s13 * 666643; 204 | s2 += s13 * 470296; 205 | s3 += s13 * 654183; 206 | s4 -= s13 * 997805; 207 | s5 += s13 * 136657; 208 | s6 -= s13 * 683901; 209 | s13 = 0; 210 | 211 | s0 += s12 * 666643; 212 | s1 += s12 * 470296; 213 | s2 += s12 * 654183; 214 | s3 -= s12 * 997805; 215 | s4 += s12 * 136657; 216 | s5 -= s12 * 683901; 217 | s12 = 0; 218 | 219 | carry0 = (s0 + (1 << 20)) >> 21; 220 | s1 += carry0; 221 | s0 -= int64_lshift21(carry0); 222 | carry2 = (s2 + (1 << 20)) >> 21; 223 | s3 += carry2; 224 | s2 -= int64_lshift21(carry2); 225 | carry4 = (s4 + (1 << 20)) >> 21; 226 | s5 += carry4; 227 | s4 -= int64_lshift21(carry4); 228 | carry6 = (s6 + (1 << 20)) >> 21; 229 | s7 += carry6; 230 | s6 -= int64_lshift21(carry6); 231 | carry8 = (s8 + (1 << 20)) >> 21; 232 | s9 += carry8; 233 | s8 -= int64_lshift21(carry8); 234 | carry10 = (s10 + (1 << 20)) >> 21; 235 | s11 += carry10; 236 | s10 -= int64_lshift21(carry10); 237 | 238 | carry1 = (s1 + (1 << 20)) >> 21; 239 | s2 += carry1; 240 | s1 -= int64_lshift21(carry1); 241 | carry3 = (s3 + (1 << 20)) >> 21; 242 | s4 += carry3; 243 | s3 -= int64_lshift21(carry3); 244 | carry5 = (s5 + (1 << 20)) >> 21; 245 | s6 += carry5; 246 | s5 -= int64_lshift21(carry5); 247 | carry7 = (s7 + (1 << 20)) >> 21; 248 | s8 += carry7; 249 | s7 -= int64_lshift21(carry7); 250 | carry9 = (s9 + (1 << 20)) >> 21; 251 | s10 += carry9; 252 | s9 -= int64_lshift21(carry9); 253 | carry11 = (s11 + (1 << 20)) >> 21; 254 | s12 += carry11; 255 | s11 -= int64_lshift21(carry11); 256 | 257 | s0 += s12 * 666643; 258 | s1 += s12 * 470296; 259 | s2 += s12 * 654183; 260 | s3 -= s12 * 997805; 261 | s4 += s12 * 136657; 262 | s5 -= s12 * 683901; 263 | s12 = 0; 264 | 265 | carry0 = s0 >> 21; 266 | s1 += carry0; 267 | s0 -= int64_lshift21(carry0); 268 | carry1 = s1 >> 21; 269 | s2 += carry1; 270 | s1 -= int64_lshift21(carry1); 271 | carry2 = s2 >> 21; 272 | s3 += carry2; 273 | s2 -= int64_lshift21(carry2); 274 | carry3 = s3 >> 21; 275 | s4 += carry3; 276 | s3 -= int64_lshift21(carry3); 277 | carry4 = s4 >> 21; 278 | s5 += carry4; 279 | s4 -= int64_lshift21(carry4); 280 | carry5 = s5 >> 21; 281 | s6 += carry5; 282 | s5 -= int64_lshift21(carry5); 283 | carry6 = s6 >> 21; 284 | s7 += carry6; 285 | s6 -= int64_lshift21(carry6); 286 | carry7 = s7 >> 21; 287 | s8 += carry7; 288 | s7 -= int64_lshift21(carry7); 289 | carry8 = s8 >> 21; 290 | s9 += carry8; 291 | s8 -= int64_lshift21(carry8); 292 | carry9 = s9 >> 21; 293 | s10 += carry9; 294 | s9 -= int64_lshift21(carry9); 295 | carry10 = s10 >> 21; 296 | s11 += carry10; 297 | s10 -= int64_lshift21(carry10); 298 | carry11 = s11 >> 21; 299 | s12 += carry11; 300 | s11 -= int64_lshift21(carry11); 301 | 302 | s0 += s12 * 666643; 303 | s1 += s12 * 470296; 304 | s2 += s12 * 654183; 305 | s3 -= s12 * 997805; 306 | s4 += s12 * 136657; 307 | s5 -= s12 * 683901; 308 | s12 = 0; 309 | 310 | carry0 = s0 >> 21; 311 | s1 += carry0; 312 | s0 -= int64_lshift21(carry0); 313 | carry1 = s1 >> 21; 314 | s2 += carry1; 315 | s1 -= int64_lshift21(carry1); 316 | carry2 = s2 >> 21; 317 | s3 += carry2; 318 | s2 -= int64_lshift21(carry2); 319 | carry3 = s3 >> 21; 320 | s4 += carry3; 321 | s3 -= int64_lshift21(carry3); 322 | carry4 = s4 >> 21; 323 | s5 += carry4; 324 | s4 -= int64_lshift21(carry4); 325 | carry5 = s5 >> 21; 326 | s6 += carry5; 327 | s5 -= int64_lshift21(carry5); 328 | carry6 = s6 >> 21; 329 | s7 += carry6; 330 | s6 -= int64_lshift21(carry6); 331 | carry7 = s7 >> 21; 332 | s8 += carry7; 333 | s7 -= int64_lshift21(carry7); 334 | carry8 = s8 >> 21; 335 | s9 += carry8; 336 | s8 -= int64_lshift21(carry8); 337 | carry9 = s9 >> 21; 338 | s10 += carry9; 339 | s9 -= int64_lshift21(carry9); 340 | carry10 = s10 >> 21; 341 | s11 += carry10; 342 | s10 -= int64_lshift21(carry10); 343 | 344 | s[0] = (byte) (s0 >> 0); 345 | s[1] = (byte) (s0 >> 8); 346 | s[2] = (byte) ((s0 >> 16) | (s1 << 5)); 347 | s[3] = (byte) (s1 >> 3); 348 | s[4] = (byte) (s1 >> 11); 349 | s[5] = (byte) ((s1 >> 19) | (s2 << 2)); 350 | s[6] = (byte) (s2 >> 6); 351 | s[7] = (byte) ((s2 >> 14) | (s3 << 7)); 352 | s[8] = (byte) (s3 >> 1); 353 | s[9] = (byte) (s3 >> 9); 354 | s[10] = (byte) ((s3 >> 17) | (s4 << 4)); 355 | s[11] = (byte) (s4 >> 4); 356 | s[12] = (byte) (s4 >> 12); 357 | s[13] = (byte) ((s4 >> 20) | (s5 << 1)); 358 | s[14] = (byte) (s5 >> 7); 359 | s[15] = (byte) ((s5 >> 15) | (s6 << 6)); 360 | s[16] = (byte) (s6 >> 2); 361 | s[17] = (byte) (s6 >> 10); 362 | s[18] = (byte) ((s6 >> 18) | (s7 << 3)); 363 | s[19] = (byte) (s7 >> 5); 364 | s[20] = (byte) (s7 >> 13); 365 | s[21] = (byte) (s8 >> 0); 366 | s[22] = (byte) (s8 >> 8); 367 | s[23] = (byte) ((s8 >> 16) | (s9 << 5)); 368 | s[24] = (byte) (s9 >> 3); 369 | s[25] = (byte) (s9 >> 11); 370 | s[26] = (byte) ((s9 >> 19) | (s10 << 2)); 371 | s[27] = (byte) (s10 >> 6); 372 | s[28] = (byte) ((s10 >> 14) | (s11 << 7)); 373 | s[29] = (byte) (s11 >> 1); 374 | s[30] = (byte) (s11 >> 9); 375 | s[31] = (byte) (s11 >> 17); 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /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 static org.junit.Assert.assertArrayEquals; 10 | import static org.junit.Assert.assertEquals; 11 | import static org.junit.Assert.assertFalse; 12 | import static org.junit.Assert.assertTrue; 13 | 14 | import org.junit.Test; 15 | 16 | import java.math.BigInteger; 17 | import java.nio.charset.StandardCharsets; 18 | import java.util.Arrays; 19 | 20 | import io.github.muntashirakon.crypto.ed25519.Curve; 21 | import io.github.muntashirakon.crypto.ed25519.Ed25519; 22 | import io.github.muntashirakon.crypto.ed25519.Ed25519CurveParameterSpec; 23 | import io.github.muntashirakon.crypto.ed25519.Ed25519Field; 24 | import io.github.muntashirakon.crypto.ed25519.FieldElement; 25 | import io.github.muntashirakon.crypto.ed25519.GroupElement; 26 | import io.github.muntashirakon.crypto.ed25519.Utils; 27 | import io.github.muntashirakon.crypto.x25519.x25519Scalar; 28 | 29 | public class Spake25519Test { 30 | private static final byte[] B_EIGHT = Utils.hexToBytes("0800000000000000000000000000000000000000000000000000000000000000"); 31 | 32 | // Based on http://ed25519.cr.yp.to/python/ed25519.py 33 | private static GroupElement ed25519Edwards(GroupElement P, GroupElement Q) { 34 | Curve curve = P.getCurve(); 35 | Ed25519Field field = curve.getField(); 36 | FieldElement x1 = P.getX(); 37 | FieldElement y1 = P.getY(); 38 | FieldElement x2 = Q.getX(); 39 | FieldElement y2 = Q.getY(); 40 | FieldElement dx1x2y1y2 = curve.getD().multiply(x1).multiply(x2).multiply(y1).multiply(y2); 41 | FieldElement x3 = x1.multiply(y2).add(x2.multiply(y1)).multiply(dx1x2y1y2.addOne().invert()); // (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2) 42 | 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) 43 | return GroupElement.p3(curve, x3, y3, field.ZERO, field.ZERO); 44 | } 45 | 46 | private static GroupElement ed25519ScalarMultiply(GroupElement P, BigInteger e) { 47 | GroupElement Q = P.getCurve().getZero(GroupElement.Representation.P3); 48 | Q = ed25519Edwards(Q, Q); 49 | Q = ed25519Edwards(Q, P); 50 | 51 | int len = e.bitLength() - 2; 52 | for (int c = len; c >= 0; --c) { 53 | Q = ed25519Edwards(Q, Q); 54 | if (e.testBit(c)) Q = ed25519Edwards(Q, P); 55 | } 56 | return Q; 57 | } 58 | 59 | static GroupElement[] precomputeTable(String seed) { 60 | GroupElement[] t = new GroupElement[15]; 61 | byte[] seedBytes = seed.getBytes(StandardCharsets.UTF_8); 62 | byte[] v = Spake2Context.getHash("SHA-256", seedBytes); 63 | Ed25519CurveParameterSpec spec = Ed25519.getSpec(); 64 | GroupElement P = spec.getCurve().createPoint(v, true); 65 | Curve curve = P.getCurve(); 66 | for (int i = 1; i < 16; ++i) { 67 | // (i >>> 3 & 1) * (1 << 192) 68 | BigInteger t1 = BigInteger.valueOf((i >>> 3 & 1)).multiply(BigInteger.ONE.shiftLeft(192)); 69 | // (i >>> 2 & 1) * (1 << 128) 70 | BigInteger t2 = BigInteger.valueOf((i >>> 2 & 1)).multiply(BigInteger.ONE.shiftLeft(128)); 71 | // (i >>> 1 & 1) * (1 << 64) 72 | BigInteger t3 = BigInteger.valueOf((i >>> 1 & 1)).multiply(BigInteger.ONE.shiftLeft(64)); 73 | // (i & 1) 74 | BigInteger t4 = BigInteger.ZERO.add(BigInteger.valueOf(i & 1)); 75 | // k is the sum of all the above 76 | BigInteger k = BigInteger.ZERO.add(t1).add(t2).add(t3).add(t4); 77 | 78 | GroupElement ge = ed25519ScalarMultiply(P, k); 79 | FieldElement x = ge.getX(); 80 | FieldElement y = ge.getY(); 81 | 82 | FieldElement ypx = y.add(x); 83 | FieldElement ymx = y.subtract(x); 84 | FieldElement xy2d = x.multiply(y).multiply(curve.get2D()); 85 | 86 | t[i - 1] = GroupElement.precomp(curve, ypx, ymx, xy2d); 87 | } 88 | return t; 89 | } 90 | 91 | private static byte[] printPrecompTable(GroupElement[] groupElements, String name) { 92 | byte[] table = new byte[groupElements.length * 3 * 32]; 93 | for (int i = 0; i < groupElements.length; ++i) { 94 | System.arraycopy(groupElements[i].getX().toByteArray(), 0, table, i * 96, 32); 95 | System.arraycopy(groupElements[i].getY().toByteArray(), 0, table, i * 96 + 32, 32); 96 | System.arraycopy(groupElements[i].getZ().toByteArray(), 0, table, i * 96 + 64, 32); 97 | } 98 | System.out.printf(" private static final int[] %s = new int[] {", name); 99 | for (int i = 0; i < table.length; ++i) { 100 | if (i % 15 == 0) System.out.printf("%n "); 101 | System.out.printf(" 0x%02X,", table[i]); 102 | } 103 | System.out.println("\n };"); 104 | return table; 105 | } 106 | 107 | private static void printCTable(byte[] table, String name) { 108 | System.out.printf("static const uint8_t %s[%d] = {", name, table.length); 109 | for (int i = 0; i < table.length; ++i) { 110 | if (i % 12 == 0) System.out.printf("%n "); 111 | System.out.printf(" 0x%02X,", table[i]); 112 | } 113 | System.out.println("\n};"); 114 | } 115 | 116 | public void printPasswords() { 117 | printCTable(Utils.hexToBytes("47f6c458e5f062db8427d2d9bb20c954a76d6943959756a18d11d45e1ad190f980a86d185a93ca1d3025c5febe3aac4045b34a39b1f511385ca97fc4332137f3"), "kAlicePrivKey"); 118 | printCTable(Utils.hexToBytes("a6bf9f9bf7819e0ded8c2dd82a1aa38acb2f8a6403429cff33d64ea9c40439d5fd7029811a5f5a8f7c89c8b44ac0b421f6b24ca2ba18d2069995831730cd8c5a"), "kBobPrivKey"); 119 | } 120 | 121 | @Test 122 | public void testX25519ScReduce() { 123 | byte[] expected = Utils.hexToBytes("00f4f4563dba61b24551c122bbbb630855b1b5ed3f0a619792a0d9fd2a2cfb3880a86d185a93ca1d3025c5febe3aac4045b34a39b1f511385ca97fc4332137f3"); 124 | byte[] privateKey = Utils.hexToBytes("47f6c458e5f062db8427d2d9bb20c954a76d6943959756a18d11d45e1ad190f980a86d185a93ca1d3025c5febe3aac4045b34a39b1f511385ca97fc4332137f3"); 125 | x25519Scalar.reduce(privateKey); 126 | Spake2Context.leftShift3(privateKey); 127 | assertEquals(Utils.bytesToHex(expected), Utils.bytesToHex(privateKey)); 128 | } 129 | 130 | @Test 131 | public void testAllOnesIfEquals() { 132 | assertEquals(Spake2Context.isEqual(1, 1), 0xffff_ffff); 133 | assertEquals(Spake2Context.isEqual(1, 0), 0x0); 134 | } 135 | 136 | @Test 137 | public void scalarTestCmov() { 138 | Spake2Context.Scalar scalar = new Spake2Context.Scalar(Utils.hexToBytes( 139 | "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010")); 140 | Spake2Context.Scalar zero = new Spake2Context.Scalar(); 141 | Spake2Context.Scalar tmpScalar = new Spake2Context.Scalar(); 142 | tmpScalar.copy(scalar); 143 | tmpScalar.cmov(zero, 0); 144 | assertEquals("edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", 145 | Utils.bytesToHex(tmpScalar.getBytes())); 146 | tmpScalar.copy(scalar); 147 | tmpScalar.cmov(zero, 1); 148 | assertEquals("ecd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", 149 | Utils.bytesToHex(tmpScalar.getBytes())); 150 | tmpScalar.copy(scalar); 151 | tmpScalar.cmov(zero, 5); 152 | assertEquals("e8d3f55c1a631258d29cf7a2daf9de1400000000000000000000000000000010", 153 | Utils.bytesToHex(tmpScalar.getBytes())); 154 | tmpScalar.copy(scalar); 155 | tmpScalar.cmov(zero, 0x11); 156 | assertEquals("ecd3f55c0a631258c69cf7a2cef9de1400000000000000000000000000000010", 157 | Utils.bytesToHex(tmpScalar.getBytes())); 158 | tmpScalar.copy(scalar); 159 | tmpScalar.cmov(zero, 0x31); 160 | assertEquals("ccd3f55c0a631258c69cf7a2cef9de1400000000000000000000000000000010", 161 | Utils.bytesToHex(tmpScalar.getBytes())); 162 | tmpScalar.copy(scalar); 163 | tmpScalar.cmov(zero, 0x71); 164 | assertEquals("8cd3f55c0a631258869cf7a28ef9de1400000000000000000000000000000010", 165 | Utils.bytesToHex(tmpScalar.getBytes())); 166 | tmpScalar.copy(scalar); 167 | tmpScalar.cmov(zero, 0xf9); 168 | assertEquals("04d3f55c02631258069cf7a206f9de1400000000000000000000000000000010", 169 | Utils.bytesToHex(tmpScalar.getBytes())); 170 | } 171 | 172 | @Test 173 | public void scalarTestCmov2() { 174 | Spake2Context.Scalar scalar = new Spake2Context.Scalar(Utils.hexToBytes( 175 | "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010")); 176 | Spake2Context.Scalar base = new Spake2Context.Scalar(); 177 | base.cmov(scalar, 0); 178 | assertEquals("0000000000000000000000000000000000000000000000000000000000000000", 179 | Utils.bytesToHex(base.getBytes())); 180 | base.cmov(scalar, 1); 181 | assertEquals("0100000000000000000000000000000000000000000000000000000000000000", 182 | Utils.bytesToHex(base.getBytes())); 183 | base.cmov(scalar, 5); 184 | assertEquals("0500000000000000040000000400000000000000000000000000000000000000", 185 | Utils.bytesToHex(base.getBytes())); 186 | base.cmov(scalar, 0x11); 187 | assertEquals("0500000010000000140000001400000000000000000000000000000000000000", 188 | Utils.bytesToHex(base.getBytes())); 189 | base.cmov(scalar, 0x31); 190 | assertEquals("2500000010000000140000001400000000000000000000000000000000000000", 191 | Utils.bytesToHex(base.getBytes())); 192 | base.cmov(scalar, 0x71); 193 | assertEquals("6500000010000000540000005400000000000000000000000000000000000000", 194 | Utils.bytesToHex(base.getBytes())); 195 | base.cmov(scalar, 0xf9); 196 | assertEquals("ed00000018000000d4000000dc00000000000000000000000000000000000000", 197 | Utils.bytesToHex(base.getBytes())); 198 | } 199 | 200 | @Test 201 | public void scalarTestDbl() { 202 | Spake2Context.Scalar scalar = new Spake2Context.Scalar(Utils.hexToBytes( 203 | "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010")); 204 | Spake2Context.Scalar eight = new Spake2Context.Scalar(B_EIGHT); 205 | scalar.dbl(); 206 | assertEquals("daa7ebb934c624b0ac39ef45bdf3bd2900000000000000000000000000000020", 207 | Utils.bytesToHex(scalar.getBytes())); 208 | eight.dbl(); 209 | assertEquals("1000000000000000000000000000000000000000000000000000000000000000", 210 | Utils.bytesToHex(eight.getBytes())); 211 | } 212 | 213 | @Test 214 | public void scalarTestAdd() { 215 | Spake2Context.Scalar scalar = new Spake2Context.Scalar(Utils.hexToBytes( 216 | "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010")); 217 | Spake2Context.Scalar eight = new Spake2Context.Scalar(B_EIGHT); 218 | eight.add(scalar); 219 | assertEquals("f5d3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", 220 | Utils.bytesToHex(eight.getBytes())); 221 | scalar.add(scalar); 222 | assertEquals("daa7ebb934c624b0ac39ef45bdf3bd2900000000000000000000000000000020", 223 | Utils.bytesToHex(scalar.getBytes())); 224 | } 225 | 226 | @Test 227 | public void checkIfGeneratedValuesAreSameForN() { 228 | GroupElement[] ge = precomputeTable("edwards25519 point generation seed (N)"); 229 | assertArrayEquals(ge, Spake2Context.SPAKE_N_SMALL_PRECOMP); 230 | } 231 | 232 | @Test 233 | public void checkIfGeneratedValuesAreSameForM() { 234 | GroupElement[] ge = precomputeTable("edwards25519 point generation seed (M)"); 235 | assertArrayEquals(ge, Spake2Context.SPAKE_M_SMALL_PRECOMP); 236 | } 237 | 238 | @Test 239 | public void spake2() { 240 | for (int i = 0; i < 20; i++) { 241 | System.out.println("========"); 242 | SPAKE2Run spake2 = new SPAKE2Run(); 243 | assertTrue(spake2.run()); 244 | assertTrue(spake2.keyMatches()); 245 | } 246 | } 247 | 248 | @Test 249 | public void oldAlice() { 250 | for (int i = 0; i < 20; i++) { 251 | System.out.println("========"); 252 | SPAKE2Run spake2 = new SPAKE2Run(); 253 | spake2.aliceDisablePasswordScalarHack = true; 254 | assertTrue(spake2.run()); 255 | assertTrue(spake2.keyMatches()); 256 | } 257 | } 258 | 259 | @Test 260 | public void oldBob() { 261 | for (int i = 0; i < 20; i++) { 262 | System.out.println("========"); 263 | SPAKE2Run spake2 = new SPAKE2Run(); 264 | spake2.bobDisablePasswordScalarHack = true; 265 | assertTrue(spake2.run()); 266 | assertTrue(spake2.keyMatches()); 267 | } 268 | } 269 | 270 | @Test 271 | public void wrongPassword() { 272 | SPAKE2Run spake2 = new SPAKE2Run(); 273 | spake2.bobPassword = "wrong password".getBytes(StandardCharsets.UTF_8); 274 | assertTrue(spake2.run()); 275 | assertFalse(spake2.keyMatches()); 276 | } 277 | 278 | @Test 279 | public void wrongNames() { 280 | SPAKE2Run spake2 = new SPAKE2Run(); 281 | spake2.aliceNames.second = "charlie"; 282 | spake2.bobNames.second = "charlie"; 283 | assertTrue(spake2.run()); 284 | assertFalse(spake2.keyMatches()); 285 | } 286 | 287 | @Test 288 | public void corruptMessages() { 289 | for (int i = 0; i < 8 * Spake2Context.MAX_MSG_SIZE; i++) { 290 | SPAKE2Run spake2 = new SPAKE2Run(); 291 | spake2.aliceCorruptMsgBit = i; 292 | assertFalse(spake2.run() && spake2.keyMatches()); 293 | } 294 | } 295 | 296 | // Based on https://android.googlesource.com/platform/external/boringssl/+/f9e0b0e17fabac35627f18f94a8954c3857784ac/src/crypto/curve25519/spake25519_test.cc 297 | private static class SPAKE2Run { 298 | private final Pair aliceNames = new Pair<>("adb pair client\u0000", "adb pair server\u0000"); 299 | private final Pair bobNames = new Pair<>("adb pair server\u0000", "adb pair client\u0000"); 300 | private final byte[] alicePassword = Utils.hexToBytes("353932373831E63DD959651C211600F3B6561D0B9D90AF09D0A4A453EE2059A480CC7C5A94D4D48933F9FFF5FE43317D52FA7BFF8F8BC4F3488B8007330FEC7C7EDC91C20E5D"); 301 | private byte[] bobPassword = alicePassword; 302 | private boolean aliceDisablePasswordScalarHack = false; 303 | private boolean bobDisablePasswordScalarHack = false; 304 | private int aliceCorruptMsgBit = -1; 305 | private boolean keyMatches = false; 306 | 307 | private boolean run() { 308 | Spake2Context alice = new Spake2Context( 309 | Spake2Role.Alice, 310 | aliceNames.first.getBytes(StandardCharsets.UTF_8), 311 | aliceNames.second.getBytes(StandardCharsets.UTF_8)); 312 | Spake2Context bob = new Spake2Context( 313 | Spake2Role.Bob, 314 | bobNames.first.getBytes(StandardCharsets.UTF_8), 315 | bobNames.second.getBytes(StandardCharsets.UTF_8)); 316 | 317 | if (aliceDisablePasswordScalarHack) { 318 | alice.setDisablePasswordScalarHack(true); 319 | } 320 | if (bobDisablePasswordScalarHack) { 321 | bob.setDisablePasswordScalarHack(true); 322 | } 323 | 324 | byte[] aliceMsg; 325 | byte[] bobMsg; 326 | 327 | try { 328 | aliceMsg = alice.generateMessage(alicePassword, Utils.hexToBytes("47f6c458e5f062db8427d2d9bb20c954a76d6943959756a18d11d45e1ad190f980a86d185a93ca1d3025c5febe3aac4045b34a39b1f511385ca97fc4332137f3")); 329 | bobMsg = bob.generateMessage(bobPassword, Utils.hexToBytes("a6bf9f9bf7819e0ded8c2dd82a1aa38acb2f8a6403429cff33d64ea9c40439d5fd7029811a5f5a8f7c89c8b44ac0b421f6b24ca2ba18d2069995831730cd8c5a")); 330 | } catch (Exception e) { 331 | return false; 332 | } 333 | 334 | System.out.printf("ALICE_MSG: %s%n", Utils.bytesToHex(aliceMsg)); 335 | System.out.printf("BOB_MSG: %s%n", Utils.bytesToHex(bobMsg)); 336 | 337 | if (aliceCorruptMsgBit >= 0 && aliceCorruptMsgBit < (8 * aliceMsg.length)) { 338 | aliceMsg[aliceCorruptMsgBit / 8] ^= 1 << (aliceCorruptMsgBit & 7); 339 | } 340 | 341 | byte[] aliceKey; 342 | byte[] bobKey; 343 | try { 344 | aliceKey = alice.processMessage(bobMsg); 345 | bobKey = bob.processMessage(aliceMsg); 346 | } catch (Exception e) { 347 | return false; 348 | } 349 | 350 | System.out.printf("ALICE_KEY: %s%n", Utils.bytesToHex(aliceKey)); 351 | System.out.printf("BOB_KEY: %s%n", Utils.bytesToHex(bobKey)); 352 | 353 | keyMatches = Arrays.equals(aliceKey, bobKey); 354 | 355 | return true; 356 | } 357 | 358 | boolean keyMatches() { 359 | return keyMatches; 360 | } 361 | } 362 | 363 | private static class Pair { 364 | private S first; 365 | private T second; 366 | 367 | public Pair(S first, T second) { 368 | this.first = first; 369 | this.second = second; 370 | } 371 | } 372 | } -------------------------------------------------------------------------------- /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.FieldElement; 21 | import io.github.muntashirakon.crypto.ed25519.GroupElement; 22 | import io.github.muntashirakon.crypto.ed25519.Utils; 23 | import io.github.muntashirakon.crypto.x25519.x25519Scalar; 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[] mMyName; 274 | private final byte[] mTheirName; 275 | private final Spake2Role mMyRole; 276 | private final byte[] mPrivateKey = new byte[32]; 277 | private final byte[] mMyMsg = new byte[32]; 278 | private final byte[] mPasswordScalar = new byte[32]; 279 | private final byte[] mPasswordHash = new byte[64]; 280 | private final Ed25519CurveParameterSpec mCurveSpec; 281 | 282 | private State mState; 283 | private boolean mDisablePasswordScalarHack; 284 | private boolean mIsDestroyed = false; 285 | 286 | public Spake2Context(Spake2Role myRole, 287 | final byte[] myName, 288 | final byte[] theirName) { 289 | mMyRole = myRole; 290 | mMyName = new byte[myName.length]; 291 | mTheirName = new byte[theirName.length]; 292 | mState = State.Init; 293 | 294 | System.arraycopy(myName, 0, mMyName, 0, myName.length); 295 | System.arraycopy(theirName, 0, mTheirName, 0, theirName.length); 296 | 297 | mCurveSpec = Ed25519.getSpec(); 298 | } 299 | 300 | public void setDisablePasswordScalarHack(boolean disablePasswordScalarHack) { 301 | mDisablePasswordScalarHack = disablePasswordScalarHack; 302 | } 303 | 304 | public boolean isDisablePasswordScalarHack() { 305 | return mDisablePasswordScalarHack; 306 | } 307 | 308 | public Spake2Role getMyRole() { 309 | return mMyRole; 310 | } 311 | 312 | public byte[] getMyMsg() { 313 | return mMyMsg; 314 | } 315 | 316 | public byte[] getMyName() { 317 | return mMyName; 318 | } 319 | 320 | public byte[] getTheirName() { 321 | return mTheirName; 322 | } 323 | 324 | @Override 325 | public boolean isDestroyed() { 326 | return mIsDestroyed; 327 | } 328 | 329 | @Override 330 | public void destroy() { 331 | mIsDestroyed = true; 332 | Arrays.fill(mMyName, (byte) 0); 333 | Arrays.fill(mTheirName, (byte) 0); 334 | Arrays.fill(mPrivateKey, (byte) 0); 335 | Arrays.fill(mMyMsg, (byte) 0); 336 | Arrays.fill(mPasswordScalar, (byte) 0); 337 | Arrays.fill(mPasswordHash, (byte) 0); 338 | } 339 | 340 | /** 341 | * @param password Shared password. 342 | * @return A message of size {@link #MAX_MSG_SIZE}. 343 | * @throws IllegalArgumentException If SHA-512 is unavailable for some reason. 344 | * @throws IllegalStateException If the message has already been generated. 345 | */ 346 | public byte[] generateMessage(final byte[] password) throws IllegalArgumentException, IllegalStateException { 347 | byte[] privateKey = new byte[64]; 348 | new SecureRandom().nextBytes(privateKey); 349 | System.out.printf("PVKEY(%s): %s%n", mMyRole, Utils.bytesToHex(privateKey)); 350 | return generateMessage(password, privateKey); 351 | } 352 | 353 | // Package private method for testing purposes 354 | byte[] generateMessage(final byte[] password, byte[] privateKey) throws IllegalArgumentException, IllegalStateException { 355 | if (mIsDestroyed) { 356 | throw new IllegalStateException("The context was destroyed."); 357 | } 358 | if (mState != State.Init) { 359 | throw new IllegalStateException("Invalid state: " + mState); 360 | } 361 | 362 | x25519Scalar.reduce(privateKey); 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, mPrivateKey, 0, mPrivateKey.length); 367 | 368 | final GroupElement P = mCurveSpec.getB().scalarMultiply(mPrivateKey); 369 | 370 | byte[] passwordTmp = getHash("SHA-512", password); // 64 byte 371 | System.arraycopy(passwordTmp, 0, mPasswordHash, 0, mPasswordHash.length); 372 | x25519Scalar.reduce(passwordTmp); 373 | 374 | /** 375 | * Due to a copy-paste error, the call to {@link #leftShift3(byte[])} was omitted after reducing it, just above. 376 | * This meant that {@link #mPasswordScalar} was not a multiple of eight to clear the cofactor and thus three bits 377 | * of the password hash would leak. In order to fix this in a unilateral way, points of small order are added to 378 | * the mask point such as that it is in the prime-order subgroup. Since the ephemeral scalar is a multiple of 379 | * eight, these points will cancel out when calculating the shared secret. 380 | * 381 | * Adding points of small order is the same as adding multiples of the prime order to the password scalar. Since 382 | * that's faster, this what is done below. {@link #l} is a large prime, thus, odd, thus the LSB is one. So, 383 | * adding it will flip the LSB. Adding twice, it will flip the next bit, and so on for all the bottom three bits. 384 | */ 385 | Scalar passwordScalar = new Scalar(passwordTmp); 386 | 387 | /** 388 | * passwordScalar is the result of scalar reducing and thus is, at most, (l-1). In the following, we may add 389 | * (l + 2×l + 4×l) for a max value of (8×l - 1). That is, < 2^256, as required. 390 | */ 391 | 392 | if (!mDisablePasswordScalarHack) { 393 | Scalar order = new Scalar(l); 394 | Scalar tmp = new Scalar(); 395 | tmp.cmov(order, isEqual(passwordScalar.getByte(0) & 1, 1)); 396 | passwordScalar.add(tmp); 397 | order.dbl(); 398 | 399 | tmp.reset(); 400 | tmp.cmov(order, isEqual(passwordScalar.getByte(0) & 2, 2)); 401 | passwordScalar.add(tmp); 402 | order.dbl(); 403 | 404 | tmp.reset(); 405 | tmp.cmov(order, isEqual(passwordScalar.getByte(0) & 4, 4)); 406 | passwordScalar.add(tmp); 407 | 408 | assert ((passwordScalar.getByte(0) & 7) == 0); 409 | } 410 | 411 | System.arraycopy(passwordScalar.getBytes(), 0, mPasswordScalar, 0, mPasswordScalar.length); 412 | 413 | // mask = h(password) * . 414 | GroupElement mask = geScalarMultiplySmallPrecomp(mCurveSpec.getCurve(), mPasswordScalar, 415 | mMyRole == Spake2Role.Alice ? SPAKE_M_SMALL_PRECOMP : SPAKE_N_SMALL_PRECOMP); 416 | 417 | // P* = P + mask. 418 | GroupElement PStar = P.add(mask.toCached()).toP2(); 419 | 420 | System.arraycopy(PStar.toByteArray(), 0, mMyMsg, 0, mMyMsg.length); 421 | mState = State.MsgGenerated; 422 | return mMyMsg.clone(); 423 | } 424 | 425 | /** 426 | * @param theirMsg Message generated/received from the other end. 427 | * @return Key of size {@link #MAX_KEY_SIZE}. 428 | * @throws IllegalArgumentException If the message is invalid or SHA-512 is unavailable for some reason. 429 | * @throws IllegalStateException If the key has already been generated. 430 | */ 431 | public byte[] processMessage(final byte[] theirMsg) throws IllegalArgumentException, IllegalStateException { 432 | if (mIsDestroyed) { 433 | throw new IllegalStateException("The context was destroyed."); 434 | } 435 | if (mState != State.MsgGenerated) { 436 | throw new IllegalStateException("Invalid state: " + mState); 437 | } 438 | if (theirMsg.length != 32) { 439 | throw new IllegalArgumentException("Peer's message is not 32 bytes"); 440 | } 441 | 442 | GroupElement QStar = mCurveSpec.getCurve().fromBytesNegateVarTime(theirMsg); 443 | if (QStar == null) { 444 | throw new IllegalArgumentException("Point received from peer was not on the curve."); 445 | } 446 | 447 | System.out.printf("Q*(%s): %s%n", mMyRole, Utils.bytesToHex(QStar.toByteArray())); 448 | 449 | // Unmask peer's value. 450 | GroupElement peersMask = geScalarMultiplySmallPrecomp(mCurveSpec.getCurve(), mPasswordScalar, 451 | mMyRole == Spake2Role.Alice ? SPAKE_N_SMALL_PRECOMP : SPAKE_M_SMALL_PRECOMP); 452 | 453 | System.out.printf("PEER'S MASK(%s): %s%n", mMyRole, Utils.bytesToHex(peersMask.toByteArray())); 454 | 455 | GroupElement QExt = QStar.sub(peersMask.toCached()).toP3(); 456 | // FIXME: Create a single precomp converter or fix generating single precompute 457 | GroupElement QPrecomp = new GroupElement(QExt.getCurve(), GroupElement.Representation.P3, QExt.getX(), 458 | QExt.getY(), QExt.getZ(), QExt.getT(), true, true); 459 | 460 | System.out.printf("QExt(%s): %s%n", mMyRole, Utils.bytesToHex(QExt.toByteArray())); 461 | 462 | byte[] dhShared = QPrecomp.scalarMultiply(mPrivateKey).toByteArray(); 463 | 464 | System.out.printf("DH(%s): %s%n", mMyRole, Utils.bytesToHex(dhShared)); 465 | 466 | MessageDigest sha; 467 | try { 468 | sha = MessageDigest.getInstance("SHA-512"); 469 | } catch (NoSuchAlgorithmException e) { 470 | throw new IllegalArgumentException("SHA-512 algorithm is not supported."); 471 | } 472 | if (mMyRole == Spake2Role.Alice) { 473 | updateWithLengthPrefix(sha, mMyName, mMyName.length); 474 | updateWithLengthPrefix(sha, mTheirName, mTheirName.length); 475 | updateWithLengthPrefix(sha, mMyMsg, mMyMsg.length); 476 | updateWithLengthPrefix(sha, theirMsg, 32); 477 | } else { // Bob 478 | updateWithLengthPrefix(sha, mTheirName, mTheirName.length); 479 | updateWithLengthPrefix(sha, mMyName, mMyName.length); 480 | updateWithLengthPrefix(sha, theirMsg, 32); 481 | updateWithLengthPrefix(sha, mMyMsg, mMyMsg.length); 482 | } 483 | updateWithLengthPrefix(sha, dhShared, dhShared.length); 484 | updateWithLengthPrefix(sha, mPasswordHash, mPasswordHash.length); 485 | 486 | byte[] key = sha.digest(); 487 | this.mState = State.KeyGenerated; 488 | 489 | return key.clone(); 490 | } 491 | 492 | /** 493 | * Multiplies n with 8 by shifting it 3 times to the left 494 | * 495 | * @param n 32 bytes value 496 | */ 497 | static void leftShift3(byte[] n) { 498 | int carry = 0; 499 | for (int i = 0; i < 32; i++) { 500 | int next_carry = (byte) ((n[i] & 0xFF) >>> 5); 501 | n[i] = (byte) ((n[i] << 3) | carry); 502 | carry = next_carry; 503 | } 504 | } 505 | 506 | /** 507 | * l = 2^252 + 27742317777372353535851937790883648493 508 | */ 509 | private static final byte[] l = Utils.hexToBytes("edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010"); 510 | 511 | 512 | private static void updateWithLengthPrefix(MessageDigest sha, final byte[] data, int len) { 513 | byte[] len_le = new byte[8]; 514 | long l = len; 515 | int i; 516 | 517 | for (i = 0; i < 8; i++) { 518 | len_le[i] = (byte) (l & 0xFF); 519 | l = (l >>> 8) & 0xFFFF_FFFFL; 520 | } 521 | 522 | sha.update(len_le); 523 | sha.update(data); 524 | } 525 | 526 | private static GroupElement geScalarMultiplySmallPrecomp(Curve curve, 527 | final byte[] a /* 32 bytes */, 528 | final GroupElement[] precompTable) { 529 | GroupElement h = curve.getZero(GroupElement.Representation.P3); 530 | // This loop does 64 additions and 64 doublings to calculate the result. 531 | for (long i = 63; i >= 0; i--) { 532 | int index = 0; 533 | 534 | for (long j = 0; j < 4; j++) { 535 | byte bit = (byte) (1 & (a[(int) ((8 * j) + (i >>> 3))] >>> (i & 7))); 536 | index |= (bit << j); 537 | } 538 | 539 | GroupElement e = curve.getZero(GroupElement.Representation.PRECOMP); 540 | for (int j = 1; j < 16; j++) { 541 | e = e.cmov(precompTable[j - 1], Utils.equal(index, j)); 542 | } 543 | 544 | h = h.add(h.toCached()).toP3().madd(e).toP3(); 545 | } 546 | return h; 547 | } 548 | 549 | // Package private for testing 550 | static byte[] getHash(String algo, byte[] bytes) throws IllegalArgumentException { 551 | MessageDigest md; 552 | try { 553 | md = MessageDigest.getInstance(algo); 554 | } catch (NoSuchAlgorithmException e) { 555 | throw new IllegalArgumentException("Invalid hashing algorithm " + algo); 556 | } 557 | md.reset(); 558 | return md.digest(bytes); 559 | } 560 | 561 | /** 562 | * @param a 32 bit value 563 | * @param b 32 bit value 564 | * @return 0xff...f if a == b and 0x0 otherwise. 565 | */ 566 | static long isEqual(int a, int b) { 567 | return isZero(a ^ b); 568 | } 569 | 570 | /** 571 | * @param a 32 bit value 572 | * @return 0xff...f if a == 0 and 0x0 otherwise. 573 | */ 574 | private static long isZero(long a) { 575 | return copyMsbToEveryBit(~a & (a - 1)); 576 | } 577 | 578 | /** 579 | * @param a 32 bit value 580 | * @return The given value with the MSB copied to all the other bits. 581 | */ 582 | private static long copyMsbToEveryBit(long a) { 583 | // 2's complement of MSB 584 | return -(a >>> 63); 585 | } 586 | 587 | private enum State { 588 | Init, 589 | MsgGenerated, 590 | KeyGenerated, 591 | } 592 | 593 | static class Scalar { 594 | private final byte[] mBytes; 595 | 596 | public Scalar(byte[] bytes) { 597 | mBytes = new byte[32]; 598 | System.arraycopy(bytes, 0, mBytes, 0, mBytes.length); 599 | } 600 | 601 | public Scalar() { 602 | mBytes = new byte[32]; 603 | } 604 | 605 | public byte getByte(int idx) { 606 | return mBytes[idx]; 607 | } 608 | 609 | public byte[] getBytes() { 610 | return mBytes; 611 | } 612 | 613 | public void reset() { 614 | Arrays.fill(mBytes, (byte) 0); 615 | } 616 | 617 | /** 618 | * Copy bytes from the given scalar 619 | */ 620 | public void copy(Scalar scalar) { 621 | System.arraycopy(scalar.mBytes, 0, mBytes, 0, mBytes.length); 622 | } 623 | 624 | /** 625 | * Scalar with bits copied from src if the mask is all ones. 626 | */ 627 | public void cmov(Scalar src, long mask) { 628 | byte[] m = new byte[4]; 629 | m[0] = (byte) mask; 630 | m[1] = (byte) (mask >>> 8); 631 | m[2] = (byte) (mask >>> 16); 632 | m[3] = (byte) (mask >>> 24); 633 | byte[] bytes = new byte[32]; 634 | for (int i = 0; i < 8; ++i) { 635 | int idx = i * 4; 636 | for (int j = 0; j < 4; ++j) { 637 | mBytes[idx + j] = (byte) (m[j] & src.mBytes[idx + j] | (~m[j] & mBytes[idx + j])); 638 | } 639 | } 640 | } 641 | 642 | /** 643 | * 2 * this 644 | */ 645 | void dbl() { 646 | int carry = 0; 647 | for (int i = 0; i < 32; ++i) { 648 | int carry_out = (mBytes[i] & 0xFF) >>> 7; 649 | mBytes[i] = (byte) ((mBytes[i] << 1) | carry); 650 | carry = carry_out; 651 | } 652 | } 653 | 654 | /** 655 | * this + src 656 | */ 657 | void add(Scalar src) { 658 | byte[] bytes = new byte[32]; 659 | int carry = 0; 660 | for (int i = 0; i < 32; ++i) { 661 | int tmp = (mBytes[i] & 0xFF) + (src.mBytes[i] & 0xFF) + carry; 662 | mBytes[i] = (byte) tmp; 663 | carry = tmp >>> 8; 664 | } 665 | } 666 | } 667 | } 668 | -------------------------------------------------------------------------------- /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/GroupElement.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.util.Arrays; 11 | 12 | /** 13 | * A point $(x,y)$ on an EdDSA curve. 14 | *

15 | * Reviewed/commented by Bloody Rookie (nemproject@gmx.de) 16 | *

17 | * Literature:
18 | * [1] Daniel J. Bernstein, Niels Duif, Tanja Lange, Peter Schwabe and Bo-Yin Yang : High-speed high-security signatures
19 | * [2] Huseyin Hisil, Kenneth Koon-Ho Wong, Gary Carter, Ed Dawson: Twisted Edwards Curves Revisited
20 | * [3] Daniel J. Bernsteina, Tanja Lange: A complete set of addition laws for incomplete Edwards curves
21 | * [4] Daniel J. Bernstein, Peter Birkner, Marc Joye, Tanja Lange and Christiane Peters: Twisted Edwards Curves
22 | * [5] Christiane Pascale Peters: Curves, Codes, and Cryptography (PhD thesis)
23 | * [6] Daniel J. Bernstein, Peter Birkner, Tanja Lange and Christiane Peters: Optimizing double-base elliptic-curve single-scalar multiplication
24 | */ 25 | public class GroupElement implements Serializable { 26 | private static final long serialVersionUID = 2395879087349587L; 27 | 28 | /** 29 | * Available representations for a group element. 30 | *

    31 | *
  • P2: Projective representation $(X:Y:Z)$ satisfying $x=X/Z, y=Y/Z$. 32 | *
  • P3: Extended projective representation $(X:Y:Z:T)$ satisfying $x=X/Z, y=Y/Z, XY=ZT$. 33 | *
  • P3PrecomputedDouble: P3 but with dblPrecmp populated. 34 | *
  • P1P1: Completed representation $((X:Z), (Y:T))$ satisfying $x=X/Z, y=Y/T$. 35 | *
  • PRECOMP: Precomputed representation $(y+x, y-x, 2dxy)$. 36 | *
  • CACHED: Cached representation $(Y+X, Y-X, Z, 2dT)$ 37 | *
38 | */ 39 | public enum Representation { 40 | /** Projective ($P^2$): $(X:Y:Z)$ satisfying $x=X/Z, y=Y/Z$ */ 41 | P2, 42 | /** Extended ($P^3$): $(X:Y:Z:T)$ satisfying $x=X/Z, y=Y/Z, XY=ZT$ */ 43 | P3, 44 | /** Can only be requested. Results in P3 representation but also populates dblPrecmp. */ 45 | P3PrecomputedDouble, 46 | /** Completed ($P \times P$): $((X:Z),(Y:T))$ satisfying $x=X/Z, y=Y/T$ */ 47 | P1P1, 48 | /** Precomputed (Duif): $(y+x,y-x,2dxy)$ */ 49 | PRECOMP, 50 | /** Cached: $(Y+X,Y-X,Z,2dT)$ */ 51 | CACHED 52 | } 53 | 54 | /** 55 | * Creates a new group element in P2 representation. 56 | * 57 | * @param curve The curve. 58 | * @param X The $X$ coordinate. 59 | * @param Y The $Y$ coordinate. 60 | * @param Z The $Z$ coordinate. 61 | * @return The group element in P2 representation. 62 | */ 63 | public static GroupElement p2( 64 | final Curve curve, 65 | final FieldElement X, 66 | final FieldElement Y, 67 | final FieldElement Z) { 68 | return new GroupElement(curve, Representation.P2, X, Y, Z, null); 69 | } 70 | 71 | /** 72 | * Creates a new group element in P3 representation, without pre-computation. 73 | * 74 | * @param curve The curve. 75 | * @param X The $X$ coordinate. 76 | * @param Y The $Y$ coordinate. 77 | * @param Z The $Z$ coordinate. 78 | * @param T The $T$ coordinate. 79 | * @return The group element in P3 representation. 80 | */ 81 | public static GroupElement p3( 82 | final Curve curve, 83 | final FieldElement X, 84 | final FieldElement Y, 85 | final FieldElement Z, 86 | final FieldElement T) { 87 | return p3(curve, X, Y, Z, T, false); 88 | } 89 | 90 | /** 91 | * Creates a new group element in P3 representation, potentially with pre-computation. 92 | * 93 | * @param curve The curve. 94 | * @param X The $X$ coordinate. 95 | * @param Y The $Y$ coordinate. 96 | * @param Z The $Z$ coordinate. 97 | * @param T The $T$ coordinate. 98 | * @param precomputeDoubleOnly If true, populate dblPrecmp, else set to null. 99 | * @return The group element in P3 representation. 100 | */ 101 | public static GroupElement p3( 102 | final Curve curve, 103 | final FieldElement X, 104 | final FieldElement Y, 105 | final FieldElement Z, 106 | final FieldElement T, 107 | final boolean precomputeDoubleOnly) { 108 | return new GroupElement(curve, Representation.P3, X, Y, Z, T, precomputeDoubleOnly); 109 | } 110 | 111 | /** 112 | * Creates a new group element in P1P1 representation. 113 | * 114 | * @param curve The curve. 115 | * @param X The $X$ coordinate. 116 | * @param Y The $Y$ coordinate. 117 | * @param Z The $Z$ coordinate. 118 | * @param T The $T$ coordinate. 119 | * @return The group element in P1P1 representation. 120 | */ 121 | public static GroupElement p1p1( 122 | final Curve curve, 123 | final FieldElement X, 124 | final FieldElement Y, 125 | final FieldElement Z, 126 | final FieldElement T) { 127 | return new GroupElement(curve, Representation.P1P1, X, Y, Z, T); 128 | } 129 | 130 | /** 131 | * Creates a new group element in PRECOMP representation. 132 | * 133 | * @param curve The curve. 134 | * @param ypx The $y + x$ value. 135 | * @param ymx The $y - x$ value. 136 | * @param xy2d The $2 * d * x * y$ value. 137 | * @return The group element in PRECOMP representation. 138 | */ 139 | public static GroupElement precomp( 140 | final Curve curve, 141 | final FieldElement ypx, 142 | final FieldElement ymx, 143 | final FieldElement xy2d) { 144 | return new GroupElement(curve, Representation.PRECOMP, ypx, ymx, xy2d, null); 145 | } 146 | 147 | /** 148 | * Creates a new group element in CACHED representation. 149 | * 150 | * @param curve The curve. 151 | * @param YpX The $Y + X$ value. 152 | * @param YmX The $Y - X$ value. 153 | * @param Z The $Z$ coordinate. 154 | * @param T2d The $2 * d * T$ value. 155 | * @return The group element in CACHED representation. 156 | */ 157 | public static GroupElement cached( 158 | final Curve curve, 159 | final FieldElement YpX, 160 | final FieldElement YmX, 161 | final FieldElement Z, 162 | final FieldElement T2d) { 163 | return new GroupElement(curve, Representation.CACHED, YpX, YmX, Z, T2d); 164 | } 165 | 166 | /** 167 | * Variable is package private only so that tests run. 168 | */ 169 | final Curve curve; 170 | 171 | /** 172 | * Variable is package private only so that tests run. 173 | */ 174 | final Representation repr; 175 | 176 | /** 177 | * Variable is package private only so that tests run. 178 | */ 179 | final FieldElement X; 180 | 181 | /** 182 | * Variable is package private only so that tests run. 183 | */ 184 | final FieldElement Y; 185 | 186 | /** 187 | * Variable is package private only so that tests run. 188 | */ 189 | final FieldElement Z; 190 | 191 | /** 192 | * Variable is package private only so that tests run. 193 | */ 194 | final FieldElement T; 195 | 196 | /** 197 | * Precomputed table for {@link #scalarMultiply(byte[])}, 198 | * filled if necessary. 199 | *

200 | * Variable is package private only so that tests run. 201 | */ 202 | final GroupElement[][] precmp; 203 | 204 | /** 205 | * Precomputed table for {@link #doubleScalarMultiplyVariableTime(GroupElement, byte[], byte[])}, 206 | * filled if necessary. 207 | *

208 | * Variable is package private only so that tests run. 209 | */ 210 | final GroupElement[] dblPrecmp; 211 | 212 | /** 213 | * Creates a group element for a curve, without any pre-computation. 214 | * 215 | * @param curve The curve. 216 | * @param repr The representation used to represent the group element. 217 | * @param X The $X$ coordinate. 218 | * @param Y The $Y$ coordinate. 219 | * @param Z The $Z$ coordinate. 220 | * @param T The $T$ coordinate. 221 | */ 222 | public GroupElement( 223 | final Curve curve, 224 | final Representation repr, 225 | final FieldElement X, 226 | final FieldElement Y, 227 | final FieldElement Z, 228 | final FieldElement T) { 229 | this(curve, repr, X, Y, Z, T, false); 230 | } 231 | 232 | /** 233 | * Creates a group element for a curve, with optional pre-computation. 234 | * 235 | * @param curve The curve. 236 | * @param repr The representation used to represent the group element. 237 | * @param X The $X$ coordinate. 238 | * @param Y The $Y$ coordinate. 239 | * @param Z The $Z$ coordinate. 240 | * @param T The $T$ coordinate. 241 | * @param precomputeDouble If true, populate dblPrecmp, else set to null. 242 | */ 243 | public GroupElement( 244 | final Curve curve, 245 | final Representation repr, 246 | final FieldElement X, 247 | final FieldElement Y, 248 | final FieldElement Z, 249 | final FieldElement T, 250 | final boolean precomputeDouble) { 251 | this(curve, repr, X, Y, Z, T, false, precomputeDouble); 252 | } 253 | 254 | public GroupElement( 255 | final Curve curve, 256 | final Representation repr, 257 | final FieldElement X, 258 | final FieldElement Y, 259 | final FieldElement Z, 260 | final FieldElement T, 261 | final boolean precomputeSingle, final boolean precomputeDouble) { 262 | this.curve = curve; 263 | this.repr = repr; 264 | this.X = X; 265 | this.Y = Y; 266 | this.Z = Z; 267 | this.T = T; 268 | this.precmp = precomputeSingle ? precomputeSingle() : null; 269 | this.dblPrecmp = precomputeDouble ? precomputeDouble() : null; 270 | } 271 | 272 | /** 273 | * Creates a group element for a curve from a given encoded point. No pre-computation. 274 | *

275 | * A point $(x,y)$ is encoded by storing $y$ in bit 0 to bit 254 and the sign of $x$ in bit 255. 276 | * $x$ is recovered in the following way: 277 | *

    278 | *
  • $x = sign(x) * \sqrt{(y^2 - 1) / (d * y^2 + 1)} = sign(x) * \sqrt{u / v}$ with $u = y^2 - 1$ and $v = d * y^2 + 1$. 279 | *
  • Setting $β = (u * v^3) * (u * v^7)^{((q - 5) / 8)}$ one has $β^2 = \pm(u / v)$. 280 | *
  • If $v * β = -u$ multiply $β$ with $i=\sqrt{-1}$. 281 | *
  • Set $x := β$. 282 | *
  • If $sign(x) \ne$ bit 255 of $s$ then negate $x$. 283 | *
284 | * 285 | * @param curve The curve. 286 | * @param s The encoded point. 287 | */ 288 | public GroupElement(final Curve curve, final byte[] s) { 289 | this(curve, s, false); 290 | } 291 | 292 | /** 293 | * Creates a group element for a curve from a given encoded point. With optional pre-computation. 294 | *

295 | * A point $(x,y)$ is encoded by storing $y$ in bit 0 to bit 254 and the sign of $x$ in bit 255. 296 | * $x$ is recovered in the following way: 297 | *

    298 | *
  • $x = sign(x) * \sqrt{(y^2 - 1) / (d * y^2 + 1)} = sign(x) * \sqrt{u / v}$ with $u = y^2 - 1$ and $v = d * y^2 + 1$. 299 | *
  • Setting $β = (u * v^3) * (u * v^7)^{((q - 5) / 8)}$ one has $β^2 = \pm(u / v)$. 300 | *
  • If $v * β = -u$ multiply $β$ with $i=\sqrt{-1}$. 301 | *
  • Set $x := β$. 302 | *
  • If $sign(x) \ne$ bit 255 of $s$ then negate $x$. 303 | *
304 | * 305 | * @param curve The curve. 306 | * @param s The encoded point. 307 | * @param precomputeSingleAndDouble If true, populate both precmp and dblPrecmp, else set both to null. 308 | */ 309 | public GroupElement(final Curve curve, final byte[] s, boolean precomputeSingleAndDouble) { 310 | FieldElement x, y, yy, u, v, v3, vxx, check; 311 | y = curve.getField().fromByteArray(s); 312 | yy = y.square(); 313 | 314 | // u = y^2-1 315 | u = yy.subtractOne(); 316 | 317 | // v = dy^2+1 318 | v = yy.multiply(curve.getD()).addOne(); 319 | 320 | // v3 = v^3 321 | v3 = v.square().multiply(v); 322 | 323 | // x = (v3^2)vu, aka x = uv^7 324 | x = v3.square().multiply(v).multiply(u); 325 | 326 | // x = (uv^7)^((q-5)/8) 327 | x = x.pow22523(); 328 | 329 | // x = uv^3(uv^7)^((q-5)/8) 330 | x = v3.multiply(u).multiply(x); 331 | 332 | vxx = x.square().multiply(v); 333 | check = vxx.subtract(u); // vx^2-u 334 | if (check.isNonZero()) { 335 | check = vxx.add(u); // vx^2+u 336 | 337 | if (check.isNonZero()) 338 | throw new IllegalArgumentException("not a valid GroupElement"); 339 | x = x.multiply(curve.getI()); 340 | } 341 | 342 | if ((x.isNegative() ? 1 : 0) != Utils.bit(s, curve.getField().getb()-1)) { 343 | x = x.negate(); 344 | } 345 | 346 | this.curve = curve; 347 | this.repr = Representation.P3; 348 | this.X = x; 349 | this.Y = y; 350 | this.Z = curve.getField().ONE; 351 | this.T = this.X.multiply(this.Y); 352 | if(precomputeSingleAndDouble) { 353 | precmp = precomputeSingle(); 354 | dblPrecmp = precomputeDouble(); 355 | } else { 356 | precmp = null; 357 | dblPrecmp = null; 358 | } 359 | } 360 | 361 | /** 362 | * Gets the curve of the group element. 363 | * 364 | * @return The curve. 365 | */ 366 | public Curve getCurve() { 367 | return this.curve; 368 | } 369 | 370 | /** 371 | * Gets the representation of the group element. 372 | * 373 | * @return The representation. 374 | */ 375 | public Representation getRepresentation() { 376 | return this.repr; 377 | } 378 | 379 | /** 380 | * Gets the $X$ value of the group element. 381 | * This is for most representation the projective $X$ coordinate. 382 | * 383 | * @return The $X$ value. 384 | */ 385 | public FieldElement getX() { 386 | return this.X; 387 | } 388 | 389 | /** 390 | * Gets the $Y$ value of the group element. 391 | * This is for most representation the projective $Y$ coordinate. 392 | * 393 | * @return The $Y$ value. 394 | */ 395 | public FieldElement getY() { 396 | return this.Y; 397 | } 398 | 399 | /** 400 | * Gets the $Z$ value of the group element. 401 | * This is for most representation the projective $Z$ coordinate. 402 | * 403 | * @return The $Z$ value. 404 | */ 405 | public FieldElement getZ() { 406 | return this.Z; 407 | } 408 | 409 | /** 410 | * Gets the $T$ value of the group element. 411 | * This is for most representation the projective $T$ coordinate. 412 | * 413 | * @return The $T$ value. 414 | */ 415 | public FieldElement getT() { 416 | return this.T; 417 | } 418 | 419 | /** 420 | * Converts the group element to an encoded point on the curve. 421 | * 422 | * @return The encoded point as byte array. 423 | */ 424 | public byte[] toByteArray() { 425 | switch (this.repr) { 426 | case P2: 427 | case P3: 428 | FieldElement recip = Z.invert(); 429 | FieldElement x = X.multiply(recip); 430 | FieldElement y = Y.multiply(recip); 431 | byte[] s = y.toByteArray(); 432 | s[s.length-1] |= (x.isNegative() ? (byte) 0x80 : 0); 433 | return s; 434 | default: 435 | return toP2().toByteArray(); 436 | } 437 | } 438 | 439 | /** 440 | * Converts the group element to the P2 representation. 441 | * 442 | * @return The group element in the P2 representation. 443 | */ 444 | public GroupElement toP2() { 445 | return toRep(Representation.P2); 446 | } 447 | 448 | /** 449 | * Converts the group element to the P3 representation. 450 | * 451 | * @return The group element in the P3 representation. 452 | */ 453 | public GroupElement toP3() { 454 | return toRep(Representation.P3); 455 | } 456 | 457 | /** 458 | * Converts the group element to the P3 representation, with dblPrecmp populated. 459 | * 460 | * @return The group element in the P3 representation. 461 | */ 462 | public GroupElement toP3PrecomputeDouble() { 463 | return toRep(Representation.P3PrecomputedDouble); 464 | } 465 | 466 | /** 467 | * Converts the group element to the CACHED representation. 468 | * 469 | * @return The group element in the CACHED representation. 470 | */ 471 | public GroupElement toCached() { 472 | return toRep(Representation.CACHED); 473 | } 474 | 475 | /** 476 | * Convert a GroupElement from one Representation to another. 477 | * TODO-CR: Add additional conversion? 478 | * $r = p$ 479 | *

480 | * Supported conversions: 481 | *

    482 | *
  • P3 $\rightarrow$ P2 483 | *
  • P3 $\rightarrow$ CACHED (1 multiply, 1 add, 1 subtract) 484 | *
  • P1P1 $\rightarrow$ P2 (3 multiply) 485 | *
  • P1P1 $\rightarrow$ P3 (4 multiply) 486 | * 487 | * @param repr The representation to convert to. 488 | * @return A new group element in the given representation. 489 | */ 490 | private GroupElement toRep(final Representation repr) { 491 | switch (this.repr) { 492 | case P2: 493 | switch (repr) { 494 | case P2: 495 | return p2(this.curve, this.X, this.Y, this.Z); 496 | default: 497 | throw new IllegalArgumentException(); 498 | } 499 | case P3: 500 | switch (repr) { 501 | case P2: 502 | return p2(this.curve, this.X, this.Y, this.Z); 503 | case P3: 504 | return p3(this.curve, this.X, this.Y, this.Z, this.T); 505 | case CACHED: 506 | return cached(this.curve, this.Y.add(this.X), this.Y.subtract(this.X), this.Z, this.T.multiply(this.curve.get2D())); 507 | default: 508 | throw new IllegalArgumentException(); 509 | } 510 | case P1P1: 511 | switch (repr) { 512 | case P2: 513 | return p2(this.curve, this.X.multiply(this.T), Y.multiply(this.Z), this.Z.multiply(this.T)); 514 | case P3: 515 | return p3(this.curve, this.X.multiply(this.T), Y.multiply(this.Z), this.Z.multiply(this.T), this.X.multiply(this.Y), false); 516 | case P3PrecomputedDouble: 517 | return p3(this.curve, this.X.multiply(this.T), Y.multiply(this.Z), this.Z.multiply(this.T), this.X.multiply(this.Y), true); 518 | case P1P1: 519 | return p1p1(this.curve, this.X, this.Y, this.Z, this.T); 520 | default: 521 | throw new IllegalArgumentException(); 522 | } 523 | case PRECOMP: 524 | switch (repr) { 525 | case PRECOMP: 526 | return precomp(this.curve, this.X, this.Y, this.Z); 527 | default: 528 | throw new IllegalArgumentException(); 529 | } 530 | case CACHED: 531 | switch (repr) { 532 | case CACHED: 533 | return cached(this.curve, this.X, this.Y, this.Z, this.T); 534 | default: 535 | throw new IllegalArgumentException(); 536 | } 537 | default: 538 | throw new UnsupportedOperationException(); 539 | } 540 | } 541 | 542 | /** 543 | * Precomputes table for {@link #scalarMultiply(byte[])}. 544 | */ 545 | private GroupElement[][] precomputeSingle() { 546 | // Precomputation for single scalar multiplication. 547 | GroupElement[][] precmp = new GroupElement[32][8]; 548 | // TODO-CR BR: check that this == base point when the method is called. 549 | GroupElement Bi = this; 550 | for (int i = 0; i < 32; i++) { 551 | GroupElement Bij = Bi; 552 | for (int j = 0; j < 8; j++) { 553 | final FieldElement recip = Bij.Z.invert(); 554 | final FieldElement x = Bij.X.multiply(recip); 555 | final FieldElement y = Bij.Y.multiply(recip); 556 | precmp[i][j] = precomp(this.curve, y.add(x), y.subtract(x), x.multiply(y).multiply(this.curve.get2D())); 557 | Bij = Bij.add(Bi.toCached()).toP3(); 558 | } 559 | // Only every second summand is precomputed (16^2 = 256) 560 | for (int k = 0; k < 8; k++) { 561 | Bi = Bi.add(Bi.toCached()).toP3(); 562 | } 563 | } 564 | return precmp; 565 | } 566 | 567 | /** 568 | * Precomputes table for {@link #doubleScalarMultiplyVariableTime(GroupElement, byte[], byte[])}. 569 | */ 570 | private GroupElement[] precomputeDouble() { 571 | // Precomputation for double scalar multiplication. 572 | // P,3P,5P,7P,9P,11P,13P,15P 573 | GroupElement[] dblPrecmp = new GroupElement[8]; 574 | GroupElement Bi = this; 575 | for (int i = 0; i < 8; i++) { 576 | final FieldElement recip = Bi.Z.invert(); 577 | final FieldElement x = Bi.X.multiply(recip); 578 | final FieldElement y = Bi.Y.multiply(recip); 579 | dblPrecmp[i] = precomp(this.curve, y.add(x), y.subtract(x), x.multiply(y).multiply(this.curve.get2D())); 580 | // Bi = edwards(B,edwards(B,Bi)) 581 | Bi = this.add(this.add(Bi.toCached()).toP3().toCached()).toP3(); 582 | } 583 | return dblPrecmp; 584 | } 585 | 586 | /** 587 | * Doubles a given group element $p$ in $P^2$ or $P^3$ representation and returns the result in $P \times P$ representation. 588 | * $r = 2 * p$ where $p = (X : Y : Z)$ or $p = (X : Y : Z : T)$ 589 | *

    590 | * $r$ in $P \times P$ representation: 591 | *

    592 | * $r = ((X' : Z'), (Y' : T'))$ where 593 | *

      594 | *
    • $X' = (X + Y)^2 - (Y^2 + X^2)$ 595 | *
    • $Y' = Y^2 + X^2$ 596 | *
    • $Z' = y^2 - X^2$ 597 | *
    • $T' = 2 * Z^2 - (y^2 - X^2)$ 598 | *

    599 | * $r$ converted from $P \times P$ to $P^2$ representation: 600 | *

    601 | * $r = (X'' : Y'' : Z'')$ where 602 | *

      603 | *
    • $X'' = X' * Z' = ((X + Y)^2 - Y^2 - X^2) * (2 * Z^2 - (y^2 - X^2))$ 604 | *
    • $Y'' = Y' * T' = (Y^2 + X^2) * (2 * Z^2 - (y^2 - X^2))$ 605 | *
    • $Z'' = Z' * T' = (y^2 - X^2) * (2 * Z^2 - (y^2 - X^2))$ 606 | *

    607 | * Formula for the $P^2$ representation is in agreement with the formula given in [4] page 12 (with $a = -1$) 608 | * up to a common factor -1 which does not matter: 609 | *

    610 | * $$ 611 | * B = (X + Y)^2; C = X^2; D = Y^2; E = -C = -X^2; F := E + D = Y^2 - X^2; H = Z^2; J = F − 2 * H; \\ 612 | * X3 = (B − C − D) · J = X' * (-T'); \\ 613 | * Y3 = F · (E − D) = Z' * (-Y'); \\ 614 | * Z3 = F · J = Z' * (-T'). 615 | * $$ 616 | * 617 | * @return The P1P1 representation 618 | */ 619 | public GroupElement dbl() { 620 | switch (this.repr) { 621 | case P2: 622 | case P3: // Ignore T for P3 representation 623 | FieldElement XX, YY, B, A, AA, Yn, Zn; 624 | XX = this.X.square(); 625 | YY = this.Y.square(); 626 | B = this.Z.squareAndDouble(); 627 | A = this.X.add(this.Y); 628 | AA = A.square(); 629 | Yn = YY.add(XX); 630 | Zn = YY.subtract(XX); 631 | return p1p1(this.curve, AA.subtract(Yn), Yn, Zn, B.subtract(Zn)); 632 | default: 633 | throw new UnsupportedOperationException(); 634 | } 635 | } 636 | 637 | /** 638 | * GroupElement addition using the twisted Edwards addition law with 639 | * extended coordinates (Hisil2008). 640 | *

    641 | * this must be in $P^3$ representation and $q$ in PRECOMP representation. 642 | * $r = p + q$ where $p = this = (X1 : Y1 : Z1 : T1), q = (q.X, q.Y, q.Z) = (Y2/Z2 + X2/Z2, Y2/Z2 - X2/Z2, 2 * d * X2/Z2 * Y2/Z2)$ 643 | *

    644 | * $r$ in $P \times P$ representation: 645 | *

    646 | * $r = ((X' : Z'), (Y' : T'))$ where 647 | *

      648 | *
    • $X' = (Y1 + X1) * q.X - (Y1 - X1) * q.Y = ((Y1 + X1) * (Y2 + X2) - (Y1 - X1) * (Y2 - X2)) * 1/Z2$ 649 | *
    • $Y' = (Y1 + X1) * q.X + (Y1 - X1) * q.Y = ((Y1 + X1) * (Y2 + X2) + (Y1 - X1) * (Y2 - X2)) * 1/Z2$ 650 | *
    • $Z' = 2 * Z1 + T1 * q.Z = 2 * Z1 + T1 * 2 * d * X2 * Y2 * 1/Z2^2 = (2 * Z1 * Z2 + 2 * d * T1 * T2) * 1/Z2$ 651 | *
    • $T' = 2 * Z1 - T1 * q.Z = 2 * Z1 - T1 * 2 * d * X2 * Y2 * 1/Z2^2 = (2 * Z1 * Z2 - 2 * d * T1 * T2) * 1/Z2$ 652 | *

    653 | * Setting $A = (Y1 - X1) * (Y2 - X2), B = (Y1 + X1) * (Y2 + X2), C = 2 * d * T1 * T2, D = 2 * Z1 * Z2$ we get 654 | *

      655 | *
    • $X' = (B - A) * 1/Z2$ 656 | *
    • $Y' = (B + A) * 1/Z2$ 657 | *
    • $Z' = (D + C) * 1/Z2$ 658 | *
    • $T' = (D - C) * 1/Z2$ 659 | *

    660 | * $r$ converted from $P \times P$ to $P^2$ representation: 661 | *

    662 | * $r = (X'' : Y'' : Z'' : T'')$ where 663 | *

      664 | *
    • $X'' = X' * Z' = (B - A) * (D + C) * 1/Z2^2$ 665 | *
    • $Y'' = Y' * T' = (B + A) * (D - C) * 1/Z2^2$ 666 | *
    • $Z'' = Z' * T' = (D + C) * (D - C) * 1/Z2^2$ 667 | *
    • $T'' = X' * Y' = (B - A) * (B + A) * 1/Z2^2$ 668 | *

    669 | * TODO-CR BR: Formula for the $P^2$ representation is not in agreement with the formula given in [2] page 6
    670 | * TODO-CR BR: (the common factor $1/Z2^2$ does not matter):
    671 | * $$ 672 | * E = B - A, F = D - C, G = D + C, H = B + A \\ 673 | * X3 = E * F = (B - A) * (D - C); \\ 674 | * Y3 = G * H = (D + C) * (B + A); \\ 675 | * Z3 = F * G = (D - C) * (D + C); \\ 676 | * T3 = E * H = (B - A) * (B + A); 677 | * $$ 678 | * 679 | * @param q the PRECOMP representation of the GroupElement to add. 680 | * @return the P1P1 representation of the result. 681 | */ 682 | public GroupElement madd(GroupElement q) { 683 | if (this.repr != Representation.P3) 684 | throw new UnsupportedOperationException(); 685 | if (q.repr != Representation.PRECOMP) 686 | throw new IllegalArgumentException(); 687 | 688 | FieldElement YpX, YmX, A, B, C, D; 689 | YpX = this.Y.add(this.X); 690 | YmX = this.Y.subtract(this.X); 691 | A = YpX.multiply(q.X); // q->y+x 692 | B = YmX.multiply(q.Y); // q->y-x 693 | C = q.Z.multiply(this.T); // q->2dxy 694 | D = this.Z.add(this.Z); 695 | return p1p1(this.curve, A.subtract(B), A.add(B), D.add(C), D.subtract(C)); 696 | } 697 | 698 | /** 699 | * GroupElement subtraction using the twisted Edwards addition law with 700 | * extended coordinates (Hisil2008). 701 | *

    702 | * this must be in $P^3$ representation and $q$ in PRECOMP representation. 703 | * $r = p - q$ where $p = this = (X1 : Y1 : Z1 : T1), q = (q.X, q.Y, q.Z) = (Y2/Z2 + X2/Z2, Y2/Z2 - X2/Z2, 2 * d * X2/Z2 * Y2/Z2)$ 704 | *

    705 | * Negating $q$ means negating the value of $X2$ and $T2$ (the latter is irrelevant here). 706 | * The formula is in accordance to {@link #madd the above addition}. 707 | * 708 | * @param q the PRECOMP representation of the GroupElement to subtract. 709 | * @return the P1P1 representation of the result. 710 | */ 711 | public GroupElement msub(GroupElement q) { 712 | if (this.repr != Representation.P3) 713 | throw new UnsupportedOperationException(); 714 | if (q.repr != Representation.PRECOMP) 715 | throw new IllegalArgumentException(); 716 | 717 | FieldElement YpX, YmX, A, B, C, D; 718 | YpX = this.Y.add(this.X); 719 | YmX = this.Y.subtract(this.X); 720 | A = YpX.multiply(q.Y); // q->y-x 721 | B = YmX.multiply(q.X); // q->y+x 722 | C = q.Z.multiply(this.T); // q->2dxy 723 | D = this.Z.add(this.Z); 724 | return p1p1(this.curve, A.subtract(B), A.add(B), D.subtract(C), D.add(C)); 725 | } 726 | 727 | /** 728 | * GroupElement addition using the twisted Edwards addition law with 729 | * extended coordinates (Hisil2008). 730 | *

    731 | * this must be in $P^3$ representation and $q$ in CACHED representation. 732 | * $r = p + q$ where $p = this = (X1 : Y1 : Z1 : T1), q = (q.X, q.Y, q.Z, q.T) = (Y2 + X2, Y2 - X2, Z2, 2 * d * T2)$ 733 | *

    734 | * $r$ in $P \times P$ representation: 735 | *

      736 | *
    • $X' = (Y1 + X1) * (Y2 + X2) - (Y1 - X1) * (Y2 - X2)$ 737 | *
    • $Y' = (Y1 + X1) * (Y2 + X2) + (Y1 - X1) * (Y2 - X2)$ 738 | *
    • $Z' = 2 * Z1 * Z2 + 2 * d * T1 * T2$ 739 | *
    • $T' = 2 * Z1 * T2 - 2 * d * T1 * T2$ 740 | *

    741 | * Setting $A = (Y1 - X1) * (Y2 - X2), B = (Y1 + X1) * (Y2 + X2), C = 2 * d * T1 * T2, D = 2 * Z1 * Z2$ we get 742 | *

      743 | *
    • $X' = (B - A)$ 744 | *
    • $Y' = (B + A)$ 745 | *
    • $Z' = (D + C)$ 746 | *
    • $T' = (D - C)$ 747 | *

    748 | * Same result as in {@link #madd} (up to a common factor which does not matter). 749 | * 750 | * @param q the CACHED representation of the GroupElement to add. 751 | * @return the P1P1 representation of the result. 752 | */ 753 | public GroupElement add(GroupElement q) { 754 | if (this.repr != Representation.P3) 755 | throw new UnsupportedOperationException(); 756 | if (q.repr != Representation.CACHED) 757 | throw new IllegalArgumentException(); 758 | 759 | FieldElement YpX, YmX, A, B, C, ZZ, D; 760 | YpX = this.Y.add(this.X); 761 | YmX = this.Y.subtract(this.X); 762 | A = YpX.multiply(q.X); // q->Y+X 763 | B = YmX.multiply(q.Y); // q->Y-X 764 | C = q.T.multiply(this.T); // q->2dT 765 | ZZ = this.Z.multiply(q.Z); 766 | D = ZZ.add(ZZ); 767 | return p1p1(this.curve, A.subtract(B), A.add(B), D.add(C), D.subtract(C)); 768 | } 769 | 770 | /** 771 | * GroupElement subtraction using the twisted Edwards addition law with 772 | * extended coordinates (Hisil2008). 773 | *

    774 | * $r = p - q$ 775 | *

    776 | * Negating $q$ means negating the value of the coordinate $X2$ and $T2$. 777 | * The formula is in accordance to {@link #add the above addition}. 778 | * 779 | * @param q the PRECOMP representation of the GroupElement to subtract. 780 | * @return the P1P1 representation of the result. 781 | */ 782 | public GroupElement sub(GroupElement q) { 783 | if (this.repr != Representation.P3) 784 | throw new UnsupportedOperationException(); 785 | if (q.repr != Representation.CACHED) 786 | throw new IllegalArgumentException(); 787 | 788 | FieldElement YpX, YmX, trZ, trY, trT, trX, D; 789 | YpX = Y.add(X); 790 | YmX = Y.subtract(X); 791 | trZ = YpX.multiply(q.Y); // q->Y-X 792 | trY = YmX.multiply(q.X); // q->Y+X 793 | trT = q.T.multiply(T); // q->2dT 794 | trX = Z.multiply(q.Z); 795 | D = trX.add(trX); 796 | System.out.println("===> " + D.toString()); 797 | return p1p1(curve, trZ.subtract(trY), trZ.add(trY), D.subtract(trT), D.add(trT)); 798 | } 799 | 800 | /** 801 | * Negates this group element by subtracting it from the neutral group element. 802 | *

    803 | * TODO-CR BR: why not simply negate the coordinates $X$ and $T$? 804 | * 805 | * @return The negative of this group element. 806 | */ 807 | public GroupElement negate() { 808 | if (this.repr != Representation.P3) 809 | throw new UnsupportedOperationException(); 810 | return this.curve.getZero(Representation.P3).sub(toCached()).toP3PrecomputeDouble(); 811 | } 812 | 813 | @Override 814 | public int hashCode() { 815 | return Arrays.hashCode(this.toByteArray()); 816 | } 817 | 818 | @Override 819 | public boolean equals(Object obj) { 820 | if (obj == this) 821 | return true; 822 | if (!(obj instanceof GroupElement)) 823 | return false; 824 | GroupElement ge = (GroupElement) obj; 825 | if (!this.repr.equals(ge.repr)) { 826 | try { 827 | ge = ge.toRep(this.repr); 828 | } catch (RuntimeException e) { 829 | return false; 830 | } 831 | } 832 | switch (this.repr) { 833 | case P2: 834 | case P3: 835 | // Try easy way first 836 | if (this.Z.equals(ge.Z)) 837 | return this.X.equals(ge.X) && this.Y.equals(ge.Y); 838 | // X1/Z1 = X2/Z2 --> X1*Z2 = X2*Z1 839 | final FieldElement x1 = this.X.multiply(ge.Z); 840 | final FieldElement y1 = this.Y.multiply(ge.Z); 841 | final FieldElement x2 = ge.X.multiply(this.Z); 842 | final FieldElement y2 = ge.Y.multiply(this.Z); 843 | return x1.equals(x2) && y1.equals(y2); 844 | case P1P1: 845 | return toP2().equals(ge); 846 | case PRECOMP: 847 | // Compare directly, PRECOMP is derived directly from x and y 848 | return this.X.equals(ge.X) && this.Y.equals(ge.Y) && this.Z.equals(ge.Z); 849 | case CACHED: 850 | // Try easy way first 851 | if (this.Z.equals(ge.Z)) 852 | return this.X.equals(ge.X) && this.Y.equals(ge.Y) && this.T.equals(ge.T); 853 | // (Y+X)/Z = y+x etc. 854 | final FieldElement x3 = this.X.multiply(ge.Z); 855 | final FieldElement y3 = this.Y.multiply(ge.Z); 856 | final FieldElement t3 = this.T.multiply(ge.Z); 857 | final FieldElement x4 = ge.X.multiply(this.Z); 858 | final FieldElement y4 = ge.Y.multiply(this.Z); 859 | final FieldElement t4 = ge.T.multiply(this.Z); 860 | return x3.equals(x4) && y3.equals(y4) && t3.equals(t4); 861 | default: 862 | return false; 863 | } 864 | } 865 | 866 | /** 867 | * Convert a to radix 16. 868 | *

    869 | * Method is package private only so that tests run. 870 | * 871 | * @param a $= a[0]+256*a[1]+...+256^{31} a[31]$ 872 | * @return 64 bytes, each between -8 and 7 873 | */ 874 | static byte[] toRadix16(final byte[] a) { 875 | final byte[] e = new byte[64]; 876 | int i; 877 | // Radix 16 notation 878 | for (i = 0; i < 32; i++) { 879 | e[2*i+0] = (byte) (a[i] & 15); 880 | e[2*i+1] = (byte) ((a[i] >> 4) & 15); 881 | } 882 | /* each e[i] is between 0 and 15 */ 883 | /* e[63] is between 0 and 7 */ 884 | int carry = 0; 885 | for (i = 0; i < 63; i++) { 886 | e[i] += carry; 887 | carry = e[i] + 8; 888 | carry >>= 4; 889 | e[i] -= carry << 4; 890 | } 891 | e[63] += carry; 892 | /* each e[i] is between -8 and 7 */ 893 | return e; 894 | } 895 | 896 | /** 897 | * Constant-time conditional move. 898 | *

    899 | * Replaces this with $u$ if $b == 1$.
    900 | * Replaces this with this if $b == 0$. 901 | *

    902 | * Method is package private only so that tests run. 903 | * 904 | * @param u The group element to return if $b == 1$. 905 | * @param b in $\{0, 1\}$ 906 | * @return $u$ if $b == 1$; this if $b == 0$. Results undefined if $b$ is not in $\{0, 1\}$. 907 | */ 908 | public GroupElement cmov(final GroupElement u, final int b) { 909 | return precomp(curve, X.cmov(u.X, b), Y.cmov(u.Y, b), Z.cmov(u.Z, b)); 910 | } 911 | 912 | /** 913 | * Look up $16^i r_i B$ in the precomputed table. 914 | *

    915 | * No secret array indices, no secret branching. 916 | * Constant time. 917 | *

    918 | * Must have previously precomputed. 919 | *

    920 | * Method is package private only so that tests run. 921 | * 922 | * @param pos $= i/2$ for $i$ in $\{0, 2, 4,..., 62\}$ 923 | * @param b $= r_i$ 924 | * @return the GroupElement 925 | */ 926 | GroupElement select(final int pos, final int b) { 927 | // Is r_i negative? 928 | final int bnegative = Utils.negative(b); 929 | // |r_i| 930 | final int babs = b - (((-bnegative) & b) << 1); 931 | 932 | // 16^i |r_i| B 933 | final GroupElement t = this.curve.getZero(Representation.PRECOMP) 934 | .cmov(this.precmp[pos][0], Utils.equal(babs, 1)) 935 | .cmov(this.precmp[pos][1], Utils.equal(babs, 2)) 936 | .cmov(this.precmp[pos][2], Utils.equal(babs, 3)) 937 | .cmov(this.precmp[pos][3], Utils.equal(babs, 4)) 938 | .cmov(this.precmp[pos][4], Utils.equal(babs, 5)) 939 | .cmov(this.precmp[pos][5], Utils.equal(babs, 6)) 940 | .cmov(this.precmp[pos][6], Utils.equal(babs, 7)) 941 | .cmov(this.precmp[pos][7], Utils.equal(babs, 8)); 942 | // -16^i |r_i| B 943 | final GroupElement tminus = precomp(curve, t.Y, t.X, t.Z.negate()); 944 | // 16^i r_i B 945 | return t.cmov(tminus, bnegative); 946 | } 947 | 948 | /** 949 | * $h = a * B$ where $a = a[0]+256*a[1]+\dots+256^{31} a[31]$ and 950 | * $B$ is this point. If its lookup table has not been precomputed, it 951 | * will be at the start of the method (and cached for later calls). 952 | * Constant time. 953 | *

    954 | * Preconditions: (TODO: Check this applies here) 955 | * $a[31] \le 127$ 956 | * @param a $= a[0]+256*a[1]+\dots+256^{31} a[31]$ 957 | * @return the GroupElement 958 | */ 959 | public GroupElement scalarMultiply(final byte[] a) { 960 | GroupElement t; 961 | int i; 962 | 963 | final byte[] e = toRadix16(a); 964 | 965 | GroupElement h = this.curve.getZero(Representation.P3); 966 | for (i = 1; i < 64; i += 2) { 967 | t = select(i/2, e[i]); 968 | h = h.madd(t).toP3(); 969 | } 970 | 971 | h = h.dbl().toP2().dbl().toP2().dbl().toP2().dbl().toP3(); 972 | 973 | for (i = 0; i < 64; i += 2) { 974 | t = select(i/2, e[i]); 975 | h = h.madd(t).toP3(); 976 | } 977 | 978 | return h; 979 | } 980 | 981 | /** 982 | * Calculates a sliding-windows base 2 representation for a given value $a$. 983 | * To learn more about it see [6] page 8. 984 | *

    985 | * Output: $r$ which satisfies 986 | * $a = r0 * 2^0 + r1 * 2^1 + \dots + r255 * 2^{255}$ with $ri$ in $\{-15, -13, -11, -9, -7, -5, -3, -1, 0, 1, 3, 5, 7, 9, 11, 13, 15\}$ 987 | *

    988 | * Method is package private only so that tests run. 989 | * 990 | * @param a $= a[0]+256*a[1]+\dots+256^{31} a[31]$. 991 | * @return The byte array $r$ in the above described form. 992 | */ 993 | static byte[] slide(final byte[] a) { 994 | byte[] r = new byte[256]; 995 | 996 | // Put each bit of 'a' into a separate byte, 0 or 1 997 | for (int i = 0; i < 256; ++i) { 998 | r[i] = (byte) (1 & (a[i >> 3] >> (i & 7))); 999 | } 1000 | 1001 | // Note: r[i] will always be odd. 1002 | for (int i = 0; i < 256; ++i) { 1003 | if (r[i] != 0) { 1004 | for (int b = 1; b <= 6 && i + b < 256; ++b) { 1005 | // Accumulate bits if possible 1006 | if (r[i + b] != 0) { 1007 | if (r[i] + (r[i + b] << b) <= 15) { 1008 | r[i] += r[i + b] << b; 1009 | r[i + b] = 0; 1010 | } else if (r[i] - (r[i + b] << b) >= -15) { 1011 | r[i] -= r[i + b] << b; 1012 | for (int k = i + b; k < 256; ++k) { 1013 | if (r[k] == 0) { 1014 | r[k] = 1; 1015 | break; 1016 | } 1017 | r[k] = 0; 1018 | } 1019 | } else 1020 | break; 1021 | } 1022 | } 1023 | } 1024 | } 1025 | 1026 | return r; 1027 | } 1028 | 1029 | /** 1030 | * $r = a * A + b * B$ where $a = a[0]+256*a[1]+\dots+256^{31} a[31]$, 1031 | * $b = b[0]+256*b[1]+\dots+256^{31} b[31]$ and $B$ is this point. 1032 | *

    1033 | * $A$ must have been previously precomputed. 1034 | * 1035 | * @param A in P3 representation. 1036 | * @param a $= a[0]+256*a[1]+\dots+256^{31} a[31]$ 1037 | * @param b $= b[0]+256*b[1]+\dots+256^{31} b[31]$ 1038 | * @return the GroupElement 1039 | */ 1040 | public GroupElement doubleScalarMultiplyVariableTime(final GroupElement A, final byte[] a, final byte[] b) { 1041 | // TODO-CR BR: A check that this is the base point is needed. 1042 | final byte[] aslide = slide(a); 1043 | final byte[] bslide = slide(b); 1044 | 1045 | GroupElement r = this.curve.getZero(Representation.P2); 1046 | 1047 | int i; 1048 | for (i = 255; i >= 0; --i) { 1049 | if (aslide[i] != 0 || bslide[i] != 0) break; 1050 | } 1051 | 1052 | for (; i >= 0; --i) { 1053 | GroupElement t = r.dbl(); 1054 | 1055 | if (aslide[i] > 0) { 1056 | t = t.toP3().madd(A.dblPrecmp[aslide[i]/2]); 1057 | } else if(aslide[i] < 0) { 1058 | t = t.toP3().msub(A.dblPrecmp[(-aslide[i])/2]); 1059 | } 1060 | 1061 | if (bslide[i] > 0) { 1062 | t = t.toP3().madd(this.dblPrecmp[bslide[i]/2]); 1063 | } else if(bslide[i] < 0) { 1064 | t = t.toP3().msub(this.dblPrecmp[(-bslide[i])/2]); 1065 | } 1066 | 1067 | r = t.toP2(); 1068 | } 1069 | 1070 | return r; 1071 | } 1072 | 1073 | /** 1074 | * Verify that a point is on its curve. 1075 | * @return true if the point lies on its curve. 1076 | */ 1077 | public boolean isOnCurve() { 1078 | return isOnCurve(curve); 1079 | } 1080 | 1081 | /** 1082 | * Verify that a point is on the curve. 1083 | * @param curve The curve to check. 1084 | * @return true if the point lies on the curve. 1085 | */ 1086 | public boolean isOnCurve(Curve curve) { 1087 | switch (repr) { 1088 | case P2: 1089 | case P3: 1090 | FieldElement recip = Z.invert(); 1091 | FieldElement x = X.multiply(recip); 1092 | FieldElement y = Y.multiply(recip); 1093 | FieldElement xx = x.square(); 1094 | FieldElement yy = y.square(); 1095 | FieldElement dxxyy = curve.getD().multiply(xx).multiply(yy); 1096 | return curve.getField().ONE.add(dxxyy).add(xx).equals(yy); 1097 | 1098 | default: 1099 | return toP2().isOnCurve(curve); 1100 | } 1101 | } 1102 | 1103 | @Override 1104 | public String toString() { 1105 | return "[GroupElement\nX="+X+"\nY="+Y+"\nZ="+Z+"\nT="+T+"\n]"; 1106 | } 1107 | } 1108 | --------------------------------------------------------------------------------