├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main └── kotlin │ └── com │ └── carterharrison │ └── ecdsa │ ├── EcConstants.kt │ ├── EcCurve.kt │ ├── EcKeyGenerator.kt │ ├── EcKeyPair.kt │ ├── EcPoint.kt │ ├── EcSign.kt │ ├── EcSignature.kt │ ├── PointMath.kt │ ├── curves │ └── Secp256k1.kt │ └── hash │ ├── EcHasher.kt │ └── EcSha256.kt └── test └── kotlin └── com └── carterharrison └── ecdsa ├── EcPointTest.kt ├── PointMathTest.kt └── curves └── Secp256k1Test.kt /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/openjdk:8-jdk 6 | 7 | working_directory: ~/ecdsa-kotlin 8 | 9 | environment: 10 | JVM_OPTS: -Xmx3200m 11 | TERM: dumb 12 | 13 | steps: 14 | - checkout 15 | 16 | - restore_cache: 17 | keys: 18 | - v1-dependencies-{{ checksum "build.gradle" }} 19 | - v1-dependencies- 20 | 21 | - run: gradle dependencies 22 | - save_cache: 23 | paths: 24 | - ~/.gradle 25 | key: v1-dependencies-{{ checksum "build.gradle" }} 26 | 27 | test: 28 | docker: 29 | - image: circleci/openjdk:8-jdk 30 | 31 | steps: 32 | - checkout 33 | - run: 34 | name: Code Climate Set Up 35 | command: | 36 | # download test reporter as a static binary 37 | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 38 | chmod +x ./cc-test-reporter 39 | ./cc-test-reporter before-build 40 | - run: 41 | name: Test 42 | command: gradle test 43 | - run: 44 | name: Generate code coverage 45 | command: | 46 | mkdir -p ~/junit/ 47 | find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/junit/ \; 48 | gradle jacocoTestReport 49 | JACOCO_SOURCE_PATH=src/main/kotlin ./cc-test-reporter \ 50 | format-coverage ./build/reports/jacoco/test/jacocoTestReport.xml \ 51 | --input-type jacoco 52 | ./cc-test-reporter upload-coverage 53 | 54 | - store_artifacts: 55 | path: ~/junit 56 | - store_test_results: 57 | path: ~/junit 58 | 59 | workflows: 60 | version: 2 61 | build_and_test: 62 | jobs: 63 | - build 64 | - test: 65 | requires: 66 | - build 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | .gradle/ 3 | .DS_Store 4 | .idea 5 | /out/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Carter Harrison 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 5 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 6 | to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of 9 | the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 12 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 13 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | _ 3 | | | 4 | ___ ___ __| |___ __ _ 5 | / _ \/ __/ _` / __|/ _` | 6 | | __/ (_| (_| \__ \ (_| | 7 | \___|\___\__,_|___/\__,_| 8 | 9 | ``` 10 | 11 | # ecdsa-kotlin 12 | 13 | 14 | [![CircleCI](https://circleci.com/gh/carterharrison/ecdsa-kotlin.svg?style=shield)](https://circleci.com/gh/carterharrison/ecdsa-kotlin) [![](https://jitpack.io/v/carterharrison/ecdsa-kotlin.svg)](https://jitpack.io/#carterharrison/ecdsa-kotlin) 15 | 16 | 17 | A simple, yet lightweight, fast elliptical curve cryptography library in kotlin. **Please note that this library must undergo further testing before using in production.** 18 | 19 | 20 | ## Supported Curves 21 | This library comes with a plethora of curves, but do not worry! You can create your own curve to fit your cryptographic needs. Below are listed the curves that come out of the box. 22 | 23 | * `Secp256k1` 24 | 25 | ## Hashing 26 | This library comes with some hashing algorithms to create signatures, you can implement your own if your favorite hashing algorithm is not included. Below are listed the hashing algorithms that come out of the box. 27 | 28 | * `SHA256` 29 | 30 | ## Creating a Key Pair 31 | Creating a key pair is very simple, you may generate a random key pair, or generate one from a private key. The private key is simply a very large number. 32 | 33 | ```kotlin 34 | // generates a random key pair on the secp256k1 curve 35 | val randomKeys = EcKeyGenerator.newInstance(Secp256k1) 36 | 37 | // generates a random key pair on the secp256k1 curve with a private key 38 | val privateKey = BigInteger(...) 39 | val fromPrivateKey = EcKeyGenerator.newInstance(privateKey, Secp256k1) 40 | ``` 41 | 42 | ## Signing 43 | Signing is just as easy as creating a key pair. You may sign whatever data you please. 44 | 45 | ```kotlin 46 | // signs data [0x13, 0x37] 47 | val data = byteArrayOf(0x13, 0x37) 48 | val keyPair = EcKeyGenerator.newInstance(Secp256k1) 49 | val signature = EcSign.signData(keyPair, data, EcSha256) 50 | ``` 51 | 52 | ## Verifying 53 | After creating a signature, you can verify that the public key signed the data. 54 | 55 | ```kotlin 56 | // verifies that the public key signed the data 57 | val publicKey = EcPoint(...) 58 | val signature = EcSignature(...) 59 | val data = byteArrayOf(0x13, 0x37) 60 | val isValid = EcSign.verifySignature(publicKey, data, EcSha256, signature) 61 | ``` 62 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.jetbrains.kotlin.jvm' version '1.8.21' 3 | id 'jacoco' 4 | 5 | } 6 | 7 | group 'com.carterharrison' 8 | version 'v0.1.0-beta.0' 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | dependencies { 15 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 16 | testImplementation 'junit:junit:4.13.1' 17 | } 18 | 19 | jacocoTestReport { 20 | reports { 21 | xml.required = true 22 | html.required = false 23 | } 24 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carterharrison/ecdsa-kotlin/f820f593d3ca9dabf8f64c537017b748465e7de0/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 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 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 134 | 135 | Please set the JAVA_HOME variable in your environment to match the 136 | location of your Java installation." 137 | fi 138 | 139 | # Increase the maximum file descriptors if we can. 140 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 141 | case $MAX_FD in #( 142 | max*) 143 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 144 | # shellcheck disable=SC3045 145 | MAX_FD=$( ulimit -H -n ) || 146 | warn "Could not query maximum file descriptor limit" 147 | esac 148 | case $MAX_FD in #( 149 | '' | soft) :;; #( 150 | *) 151 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 152 | # shellcheck disable=SC3045 153 | ulimit -n "$MAX_FD" || 154 | warn "Could not set maximum file descriptor limit to $MAX_FD" 155 | esac 156 | fi 157 | 158 | # Collect all arguments for the java command, stacking in reverse order: 159 | # * args from the command line 160 | # * the main class name 161 | # * -classpath 162 | # * -D...appname settings 163 | # * --module-path (only if needed) 164 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 165 | 166 | # For Cygwin or MSYS, switch paths to Windows format before running java 167 | if "$cygwin" || "$msys" ; then 168 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 169 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 170 | 171 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 172 | 173 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 174 | for arg do 175 | if 176 | case $arg in #( 177 | -*) false ;; # don't mess with options #( 178 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 179 | [ -e "$t" ] ;; #( 180 | *) false ;; 181 | esac 182 | then 183 | arg=$( cygpath --path --ignore --mixed "$arg" ) 184 | fi 185 | # Roll the args list around exactly as many times as the number of 186 | # args, so each arg winds up back in the position where it started, but 187 | # possibly modified. 188 | # 189 | # NB: a `for` loop captures its iteration list before it begins, so 190 | # changing the positional parameters here affects neither the number of 191 | # iterations, nor the values presented in `arg`. 192 | shift # remove old arg 193 | set -- "$@" "$arg" # push replacement arg 194 | done 195 | fi 196 | 197 | 198 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 199 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 200 | 201 | # Collect all arguments for the java command; 202 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 203 | # shell script including quotes and variable substitutions, so put them in 204 | # double quotes to make sure that they get re-expanded; and 205 | # * put everything else in single quotes, so that it's not re-expanded. 206 | 207 | set -- \ 208 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 209 | -classpath "$CLASSPATH" \ 210 | org.gradle.wrapper.GradleWrapperMain \ 211 | "$@" 212 | 213 | # Stop when "xargs" is not available. 214 | if ! command -v xargs >/dev/null 2>&1 215 | then 216 | die "xargs is not available" 217 | fi 218 | 219 | # Use "xargs" to parse quoted args. 220 | # 221 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 222 | # 223 | # In Bash we could simply go: 224 | # 225 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 226 | # set -- "${ARGS[@]}" "$@" 227 | # 228 | # but POSIX shell has neither arrays nor command substitution, so instead we 229 | # post-process each arg (as a line of input to sed) to backslash-escape any 230 | # character that might be a shell metacharacter, then use eval to reverse 231 | # that process (while maintaining the separation between arguments), and wrap 232 | # the whole thing up as a single "set" statement. 233 | # 234 | # This will of course break if any of these variables contains a newline or 235 | # an unmatched quote. 236 | # 237 | 238 | eval "set -- $( 239 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 240 | xargs -n1 | 241 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 242 | tr '\n' ' ' 243 | )" '"$@"' 244 | 245 | exec "$JAVACMD" "$@" 246 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'ecdsa' 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/com/carterharrison/ecdsa/EcConstants.kt: -------------------------------------------------------------------------------- 1 | package com.carterharrison.ecdsa 2 | 3 | import java.math.BigInteger 4 | 5 | /** 6 | * An object to hold static values used when doing point math. 7 | */ 8 | object EcConstants { 9 | val ZERO : BigInteger = BigInteger.ZERO 10 | val ONE : BigInteger = BigInteger.valueOf(1) 11 | val TWO : BigInteger = BigInteger.valueOf(2) 12 | val THREE : BigInteger = BigInteger.valueOf(3) 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/carterharrison/ecdsa/EcCurve.kt: -------------------------------------------------------------------------------- 1 | package com.carterharrison.ecdsa 2 | 3 | import java.math.BigInteger 4 | 5 | 6 | /** 7 | * A cryptographic elliptical curve to preform cryptography on. 8 | */ 9 | abstract class EcCurve { 10 | /** 11 | * The prime modulus of the curve. 12 | */ 13 | abstract val p : BigInteger 14 | 15 | /** 16 | * The prime order of the curve. 17 | */ 18 | abstract val n : BigInteger 19 | 20 | /** 21 | * The a coefficient of the curve. 22 | */ 23 | abstract val a : BigInteger 24 | 25 | /** 26 | * The b coefficient of the curve. 27 | */ 28 | abstract val b : BigInteger 29 | 30 | /** 31 | * X cord of the generator point -> G. 32 | */ 33 | abstract val x : BigInteger 34 | 35 | /** 36 | * Y cord of the generator point -> G. 37 | */ 38 | abstract val y : BigInteger 39 | 40 | /** 41 | * The generator point of the curve. 42 | */ 43 | val g : EcPoint 44 | get() = EcPoint(x, y, this) 45 | 46 | /** 47 | * The identify of the curve. 48 | * 49 | * (PRIME MODULUS, 0) 50 | */ 51 | val identity : EcPoint 52 | get() = PointMath.identity(g) 53 | 54 | /** 55 | * Adds two points that belong to the curve. 56 | * 57 | * @param p1 The first point. 58 | * @param p2 The second point. 59 | * @return The sum of the two points. 60 | */ 61 | fun add (p1 : EcPoint, p2: EcPoint) : EcPoint { 62 | if (p1.x == p) { 63 | return p2 64 | } else if (p2.x == p) { 65 | return p1 66 | } 67 | 68 | if (p1.x == p2.x) { 69 | if (p1.y == p2.y) { 70 | return PointMath.double(p1) 71 | } 72 | return PointMath.identity(p1) 73 | } 74 | 75 | val m = PointMath.divide(p1.y + p - p2.y, p1.x + p - p2.x, p) 76 | return PointMath.dot(p1, p2, m, this) 77 | } 78 | 79 | /** 80 | * Finds the product of a point on the curve. (Scalar multiplication) 81 | * 82 | * @param g The generator point to start at. 83 | * @param n The number of times to dot the curve from g. 84 | * @return The point ended up on the curve. 85 | */ 86 | fun multiply (g : EcPoint, n : BigInteger) : EcPoint { 87 | var r = identity 88 | var q = g 89 | var m = n 90 | 91 | while (m != EcConstants.ZERO) { 92 | 93 | 94 | if (m and EcConstants.ONE != 0.toBigInteger()) { 95 | r = add(r, q) 96 | } 97 | 98 | m = m shr 1 99 | 100 | if (m != 0.toBigInteger()) { 101 | q = PointMath.double(q) 102 | } 103 | 104 | } 105 | 106 | return r 107 | } 108 | 109 | /** 110 | * Adds two points that belong to the curve. 111 | * 112 | * @param point The point to add to the g point. 113 | * @return The sum of the two points. 114 | */ 115 | operator fun plus (point : EcPoint) : EcPoint { 116 | return add(g, point) 117 | } 118 | 119 | /** 120 | * Finds the product of a point on the curve and its generator point. (Scalar multiplication) 121 | * 122 | * @param n The number of times to dot the curve from g. 123 | * @return The product of the point. 124 | */ 125 | operator fun times(n : BigInteger) : EcPoint { 126 | return multiply(g, n) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/kotlin/com/carterharrison/ecdsa/EcKeyGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.carterharrison.ecdsa 2 | 3 | import java.math.BigInteger 4 | import java.security.PrivateKey 5 | import java.security.SecureRandom 6 | 7 | /** 8 | * A set of functions to help create EcKeyPairs 9 | */ 10 | object EcKeyGenerator { 11 | 12 | /** 13 | * Creates a random KeyPair on a curve. 14 | * 15 | * @param curve The curve to create the keypair on. 16 | * @return A EcKeyPair with a public and private key. 17 | */ 18 | fun newInstance (curve: EcCurve) : EcKeyPair { 19 | val privateKey = random32BytePrivateKey % curve.p 20 | val publicKey = (curve.g * privateKey) 21 | return EcKeyPair(publicKey, privateKey) 22 | } 23 | 24 | /** 25 | * Creates a keypair from a private key. 26 | * 27 | * @param privateKey The private key to create a public key from. 28 | * @param curve The curve to create the public key on. 29 | */ 30 | fun newInstance (privateKey: BigInteger, curve: EcCurve) : EcKeyPair { 31 | val publicKey = (curve.g * privateKey) 32 | return EcKeyPair(publicKey, privateKey) 33 | } 34 | 35 | /** 36 | * Gets a random 32 byte (256 bit) BigInteger. 37 | */ 38 | private val random32BytePrivateKey : BigInteger 39 | get() { 40 | val privateKeyBytes = ByteArray(32) 41 | SecureRandom().nextBytes(privateKeyBytes) 42 | return (BigInteger(privateKeyBytes).abs()) 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/carterharrison/ecdsa/EcKeyPair.kt: -------------------------------------------------------------------------------- 1 | package com.carterharrison.ecdsa 2 | 3 | import java.math.BigInteger 4 | 5 | 6 | /** 7 | * A class to store public and private keys in a keypair. 8 | * 9 | * @property publicKey The public key corresponding to the private key 10 | * @property privateKey The private key of the keypair. 11 | */ 12 | class EcKeyPair (val publicKey : EcPoint, val privateKey: BigInteger) -------------------------------------------------------------------------------- /src/main/kotlin/com/carterharrison/ecdsa/EcPoint.kt: -------------------------------------------------------------------------------- 1 | package com.carterharrison.ecdsa 2 | 3 | import java.math.BigInteger 4 | 5 | 6 | /** 7 | * A point on an elliptical curve (x, y). 8 | * 9 | * @property x The x value of the point on the curve. 10 | * @property y The y value of the point on the curve. 11 | * @property curve The curve the point belongs to. 12 | */ 13 | class EcPoint (val x : BigInteger, val y : BigInteger, val curve: EcCurve) { 14 | 15 | /** 16 | * Adds a point to this point. 17 | * 18 | * @param other The point to add to this point. 19 | * @return The sum of the two points. 20 | */ 21 | operator fun plus (other: EcPoint) : EcPoint { 22 | return curve.add(this, other) 23 | } 24 | 25 | /** 26 | * Finds the product of this point and a number. (dotting the curve multiple times) 27 | * 28 | * @param n The number to multiply the point by. 29 | * @return The product of the point and the number. 30 | */ 31 | operator fun times(n: BigInteger): EcPoint { 32 | return curve.multiply(this, n) 33 | } 34 | 35 | override fun toString(): String { 36 | return "$x, $y" 37 | } 38 | 39 | override fun equals(other: Any?): Boolean { 40 | if (other is EcPoint) { 41 | return (x == other.x && y == other.y) 42 | } 43 | 44 | return false 45 | } 46 | 47 | override fun hashCode(): Int { 48 | return x.hashCode() + y.hashCode() 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/carterharrison/ecdsa/EcSign.kt: -------------------------------------------------------------------------------- 1 | package com.carterharrison.ecdsa 2 | 3 | import com.carterharrison.ecdsa.hash.EcHasher 4 | import java.math.BigInteger 5 | import java.security.SecureRandom 6 | 7 | object EcSign { 8 | 9 | /** 10 | * Gets a secure random k value, that is between 1 and n - 1 11 | * 12 | * @param n The upper ceiling of [1 to n - 1] to generate a random number 13 | * @return A secure random number between [1 and n - 1] 14 | */ 15 | private fun getRandomK (n : BigInteger) : BigInteger { 16 | val randomValue = BigInteger(256, SecureRandom()) 17 | 18 | if (randomValue >= n || randomValue <= BigInteger.ONE) { 19 | return getRandomK(n) 20 | } 21 | 22 | return randomValue 23 | } 24 | 25 | /** 26 | * Signs a message given a key pair, data, and a hashing algorithm to hash the message before signing it. 27 | * 28 | * @param keyPair The keypair to sign the message with 29 | * @param data tThe data to sign 30 | * @param hasher The hasher to hash the data with before signing 31 | * @return The signer of the data, keypair, and hasher 32 | */ 33 | fun signData (keyPair: EcKeyPair, data : ByteArray, hasher : EcHasher) : EcSignature { 34 | // todo range from 1 to n-1 35 | val hash = BigInteger(1, hasher.hash(data)) 36 | val g = keyPair.publicKey.curve.g 37 | val n = keyPair.publicKey.curve.n 38 | val k = getRandomK(n) % n 39 | val p1 = g * k 40 | val r = p1.x 41 | 42 | if (r == EcConstants.ZERO) { 43 | signData(keyPair, data, hasher) 44 | } 45 | val s = (k.modInverse(n)) * (hash + (keyPair.privateKey * r) % n) % n 46 | 47 | if (s == EcConstants.ZERO) { 48 | signData(keyPair, data, hasher) 49 | } 50 | 51 | return EcSignature(r, s) 52 | } 53 | 54 | /** 55 | * Verify that the public key signed that data 56 | * 57 | * @param publicKey The public key used in the signature 58 | * @param data The data signed 59 | * @param hasher The hasher used to sign the data 60 | * @param signature The signature signed by the public key 61 | * @return If the signature is valid 62 | */ 63 | fun verifySignature (publicKey : EcPoint, data: ByteArray, hasher: EcHasher, signature: EcSignature) : Boolean { 64 | val hash = BigInteger(1, hasher.hash(data)) 65 | val g = publicKey.curve.g 66 | val n = publicKey.curve.n 67 | val r = signature.r 68 | val s = signature.s 69 | 70 | if (r < BigInteger.ONE || r > n - BigInteger.ONE) { 71 | return false 72 | } 73 | 74 | if (s < BigInteger.ONE || s > n - BigInteger.ONE) { 75 | return false 76 | } 77 | 78 | val c = s.modInverse(n) 79 | val u1 = (hash * c) % n 80 | val u2 = (r * c) % n 81 | val xy = g * u1 + publicKey * u2 82 | val v = xy.x % n 83 | 84 | return v == r 85 | } 86 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/carterharrison/ecdsa/EcSignature.kt: -------------------------------------------------------------------------------- 1 | package com.carterharrison.ecdsa 2 | 3 | import java.math.BigInteger 4 | 5 | 6 | /** 7 | * A class to hold R and S values from a signature. 8 | * 9 | * @property r The r value of the signature 10 | * @property s The s value of the signature 11 | */ 12 | class EcSignature (val r : BigInteger, val s : BigInteger) -------------------------------------------------------------------------------- /src/main/kotlin/com/carterharrison/ecdsa/PointMath.kt: -------------------------------------------------------------------------------- 1 | package com.carterharrison.ecdsa 2 | 3 | import java.math.BigInteger 4 | 5 | 6 | /** 7 | * A helper object to do point arithmetic in a prime field. 8 | */ 9 | object PointMath { 10 | /** 11 | * Multiples two numbers in a prime field. 12 | * 13 | * @param a The first number to multiply. 14 | * @param b The second number to multiply. 15 | * @param prime The prime modulus of the field. 16 | * @return The product of a and b 17 | */ 18 | fun multiply (a : BigInteger, b : BigInteger, prime: BigInteger) : BigInteger { 19 | return (a * b) % prime 20 | } 21 | 22 | /** 23 | * Divides two numbers in a prime field. 24 | * 25 | * @param num The numerator to divide. 26 | * @param dom The denominator to divide. 27 | * @param prime The prime modulus of the field. 28 | * @return The quotient of num and dom 29 | */ 30 | fun divide (num : BigInteger, dom : BigInteger, prime: BigInteger) : BigInteger { 31 | val inverseDen = dom.modInverse(prime) 32 | return multiply(num % prime, inverseDen, prime) 33 | } 34 | 35 | /** 36 | * Finds the slop of a point on a curve. 37 | * 38 | * @param point The point on the curve to find the tangent slope. 39 | * @param curve The curve that the point is on. 40 | * @return The slope of the tangent line at the point 41 | */ 42 | fun tangent (point: EcPoint, curve: EcCurve) : BigInteger { 43 | return divide(point.x * point.x*EcConstants.THREE+curve.a, point.y * EcConstants.TWO, curve.p) 44 | } 45 | 46 | /** 47 | * Gets the identify of a point. (x, y) -> (CURVE PRIME, 0) 48 | * 49 | * @param point The point to get the identity of. 50 | */ 51 | fun identity (point: EcPoint) : EcPoint { 52 | return EcPoint(point.curve.p, EcConstants.ZERO, point.curve) 53 | } 54 | 55 | /** 56 | * Dots a point on a curve. Finds the line between A and B then finds the 3rd point on the elliptical curve. 57 | * 58 | * @param p1 The first point on the curve. 59 | * @param p2 The second point on the curve. 60 | * @param m The slope of the scent line. 61 | * @param curve The curve that the points are on. 62 | * @return The result of the dotting. 63 | */ 64 | fun dot (p1: EcPoint, p2: EcPoint, m : BigInteger, curve: EcCurve) : EcPoint { 65 | val v = (p1.y + curve.p - (m * p1.x) % curve.p) % curve.p 66 | val x = (m*m + curve.p - p1.x + curve.p - p2.x) % curve.p 67 | val y = (curve.p - (m*x) % curve.p + curve.p - v) % curve.p 68 | return EcPoint(x, y, curve) 69 | } 70 | 71 | /** 72 | * Doubles a point. Adds itself to itself. 73 | * 74 | * @param point The point to double. 75 | * @return point + point 76 | */ 77 | fun double (point: EcPoint) : EcPoint { 78 | if (point.x == point.curve.p) { 79 | return point 80 | } 81 | 82 | return dot(point, point, tangent(point, point.curve), point.curve) 83 | } 84 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/carterharrison/ecdsa/curves/Secp256k1.kt: -------------------------------------------------------------------------------- 1 | package com.carterharrison.ecdsa.curves 2 | 3 | import com.carterharrison.ecdsa.EcCurve 4 | import java.math.BigInteger 5 | 6 | 7 | object Secp256k1 : EcCurve() { 8 | override val a: BigInteger = BigInteger("00") 9 | override val b: BigInteger = BigInteger("07") 10 | override val n: BigInteger = BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16) 11 | override val p: BigInteger = BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16) 12 | override val x: BigInteger = BigInteger("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16) 13 | override val y: BigInteger = BigInteger("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16) 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/carterharrison/ecdsa/hash/EcHasher.kt: -------------------------------------------------------------------------------- 1 | package com.carterharrison.ecdsa.hash 2 | 3 | interface EcHasher { 4 | fun hash (data : ByteArray) : ByteArray 5 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/carterharrison/ecdsa/hash/EcSha256.kt: -------------------------------------------------------------------------------- 1 | package com.carterharrison.ecdsa.hash 2 | 3 | import java.security.MessageDigest 4 | 5 | 6 | 7 | object EcSha256 : EcHasher { 8 | override fun hash(data: ByteArray): ByteArray { 9 | return MessageDigest.getInstance("SHA-256").digest(data) 10 | } 11 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/carterharrison/ecdsa/EcPointTest.kt: -------------------------------------------------------------------------------- 1 | package com.carterharrison.ecdsa 2 | 3 | import com.carterharrison.ecdsa.curves.Secp256k1 4 | import org.junit.Assert 5 | import org.junit.Test 6 | import java.math.BigInteger 7 | import java.security.SecureRandom 8 | 9 | 10 | class EcPointTest { 11 | 12 | @Test 13 | fun tangent () { 14 | val g = EcPoint(Secp256k1.x, Secp256k1.y, Secp256k1) 15 | 16 | Assert.assertEquals( 17 | PointMath.tangent(g, Secp256k1), 18 | BigInteger("91914383230618135761690975197207778399550061809281766160147273830617914855857") 19 | ) 20 | } 21 | 22 | // @Test 23 | // fun mutpily () { 24 | // val g = EcPoint(Secp256k1.x, Secp256k1.y, Secp256k1) 25 | // val privateKeyBytes = ByteArray(32) 26 | // SecureRandom().nextBytes(privateKeyBytes) 27 | // val privateKey = (BigInteger("ad02e7aee082ac9d1f6a2e613f514879880fad8a971160fe811b5a4719fc1849", 16).abs()) % Secp256k1.p 28 | // println((g * privateKey).x.toString()) 29 | // } 30 | 31 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/carterharrison/ecdsa/PointMathTest.kt: -------------------------------------------------------------------------------- 1 | package com.carterharrison.ecdsa 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | 6 | class PointMathTest { 7 | 8 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/carterharrison/ecdsa/curves/Secp256k1Test.kt: -------------------------------------------------------------------------------- 1 | package com.carterharrison.ecdsa.curves 2 | 3 | import com.carterharrison.ecdsa.EcKeyGenerator 4 | import com.carterharrison.ecdsa.EcPoint 5 | import com.carterharrison.ecdsa.EcSign 6 | import com.carterharrison.ecdsa.hash.EcSha256 7 | import org.junit.Assert 8 | import org.junit.Test 9 | import java.math.BigInteger 10 | 11 | class Secp256k1Test { 12 | 13 | private fun testKeyPair (x : String, y : String, p : String) { 14 | val expectedPublic = EcPoint(BigInteger(x, 16), BigInteger(y, 16), Secp256k1) 15 | val privateKey = BigInteger(p, 16) 16 | val keypair = EcKeyGenerator.newInstance(privateKey, Secp256k1) 17 | 18 | Assert.assertEquals(expectedPublic, keypair.publicKey) 19 | } 20 | 21 | fun ByteArray.toHexString(): String { 22 | val builder = StringBuilder() 23 | val it = this.iterator() 24 | builder.append("0x") 25 | while (it.hasNext()) { 26 | builder.append(String.format("%02X", it.next())) 27 | } 28 | 29 | return builder.toString() 30 | } 31 | 32 | @Test 33 | fun testCase1 () { 34 | testKeyPair( 35 | "c1ed21b77a534b4a606de0d429c7bdd5112dd338c98676142598bf59e7f2f398", 36 | "650a8b8b7f03600e98afecb1be86349f775ac00872b93f00d4a49cec27237030", 37 | "a02235453ffd4ed5b29c3be7eae70a9701a47da2c47e2296a97132a65ea1a8d4" 38 | ) 39 | } 40 | 41 | @Test 42 | fun testCase2 () { 43 | testKeyPair( 44 | "32e0c671fef6f75bb00d4a010b69977a01f8bb605955e54de99d27b370f7d327", 45 | "5bf85eb2ea93097642a98388cbdc26d2b9d246535ff12fecbcf0fedb45ce003b", 46 | "5d335648023f8ec5fe5a3dfd1a6d1e1e463816690ee0d9392138fe04e54b7ea0" 47 | ) 48 | } 49 | 50 | @Test 51 | fun testCase3 () { 52 | testKeyPair( 53 | "20a53a720d34fcb2586b6f7a1b14cc0eb1e4b763f4805465815c0c1c3bd7a06d", 54 | "b721f75f16b912e2db07af1d155f0d633734a0d2cfc1ecb10fa76dc0f249ca4d", 55 | "06fdded00ddc6a3250973b1f994375f7208de58b1fc0585e55bc485a9e1c6a3b" 56 | ) 57 | } 58 | 59 | @Test 60 | fun testSign() { 61 | // val key = EcKeyGenerator.newInstance(Secp256k1) 62 | // val sign = EcSign.signData(key, byteArrayOf(0x00, 0x00, 0x00, 0x01, 0x01), EcSha256) 63 | // 64 | // println(EcSign.verifySignature(key.publicKey, byteArrayOf(0x00, 0x00, 0x00, 0x01, 0x01), EcSha256, sign)) 65 | // 66 | // 67 | // println(key.publicKey.x.toByteArray().toHexString()) 68 | // println(key.publicKey.y.toByteArray().toHexString()) 69 | // 70 | // println(sign.r.toByteArray().toHexString()) 71 | // println(sign.s.toByteArray().toHexString()) 72 | } 73 | } --------------------------------------------------------------------------------