├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pom.xml ├── settings.gradle └── src ├── main └── java │ └── com │ └── starkbank │ └── ellipticcurve │ ├── Curve.java │ ├── Ecdsa.java │ ├── Math.java │ ├── Point.java │ ├── PrivateKey.java │ ├── PublicKey.java │ ├── Signature.java │ └── utils │ ├── Base64.java │ ├── BinaryAscii.java │ ├── ByteString.java │ ├── Der.java │ ├── File.java │ └── RandomInteger.java └── test ├── java └── com │ └── starkbank │ └── ellipticcurve │ ├── EcdsaTest.java │ ├── OpenSSLTest.java │ ├── PrivateKeyTest.java │ ├── PublicKeyTest.java │ ├── SignatureTest.java │ └── Utils.java └── resources ├── message.txt ├── privateKey.pem ├── publicKey.pem ├── signature.binary └── signatureBinary.txt /.gitignore: -------------------------------------------------------------------------------- 1 | ########################## 2 | ## Java 3 | ########################## 4 | *.class 5 | .mtj.tmp/ 6 | *.war 7 | *.ear 8 | hs_err_pid* 9 | 10 | ########################## 11 | ## Maven 12 | ########################## 13 | target/ 14 | pom.xml.tag 15 | pom.xml.releaseBackup 16 | pom.xml.versionsBackup 17 | pom.xml.next 18 | release.properties 19 | 20 | # Ignore Gradle project-specific cache directory 21 | .gradle 22 | 23 | # Ignore Gradle build output directory 24 | build 25 | 26 | ########################## 27 | ## IntelliJ 28 | ########################## 29 | *.iml 30 | .idea/ 31 | *.ipr 32 | *.iws 33 | out/ 34 | .idea_modules/ 35 | 36 | ########################## 37 | ## Eclipse 38 | ########################## 39 | .metadata 40 | .classpath 41 | .project 42 | .settings/ 43 | bin/ 44 | tmp/ 45 | *.tmp 46 | *.bak 47 | *.swp 48 | *~.nib 49 | local.properties 50 | .loadpath 51 | 52 | ########################## 53 | ## NetBeans 54 | ########################## 55 | nbproject/private/ 56 | build/ 57 | nbbuild/ 58 | dist/ 59 | nbdist/ 60 | nbactions.xml 61 | nb-configuration.xml 62 | 63 | ########################## 64 | ## OS X 65 | ########################## 66 | .DS_Store 67 | 68 | ########################## 69 | ## Example Files 70 | ########################## 71 | src/main/java/Generate* 72 | message.txt 73 | publicKey.pem 74 | privateKey.pem 75 | signatureBinary.txt 76 | gradle.properties 77 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | install: true 3 | 4 | matrix: 5 | include: 6 | - jdk: openjdk8 7 | - jdk: openjdk9 8 | - jdk: openjdk10 9 | - jdk: openjdk11 10 | - jdk: openjdk12 11 | - jdk: openjdk13 12 | - jdk: openjdk14 13 | - jdk: openjdk-ea 14 | allow_failures: 15 | - jdk: openjdk-ea -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to the following versioning pattern: 7 | 8 | Given a version number MAJOR.MINOR.PATCH, increment: 9 | 10 | - MAJOR version when **breaking changes** are introduced; 11 | - MINOR version when **backwards compatible changes** are introduced; 12 | - PATCH version when backwards compatible bug **fixes** are implemented. 13 | 14 | 15 | ## [Unreleased] 16 | 17 | ### Fixed 18 | - groupId in pom.xml 19 | 20 | ## [1.0.2] - 2021-11-09 21 | ### Fixed 22 | - point at infinity verification in signature and public key 23 | 24 | ## [1.0.1] - 2021-11-04 25 | ### Fixed 26 | - Signature r and s range check 27 | 28 | ## [1.0.0] - 2020-04-22 29 | ### Added 30 | - first official version 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Stark Bank S.A. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## A lightweight and fast ECDSA 2 | 3 | ### Overview 4 | 5 | This is a pure Java implementation of the Elliptic Curve Digital Signature Algorithm (ECDSA). It is compatible with Java 8+ and OpenSSL. It uses some elegant math such as Jacobian Coordinates to speed up the ECDSA. 6 | 7 | ### Installation 8 | 9 | #### Maven Central 10 | In pom.xml: 11 | 12 | ```xml 13 | 14 | com.starkbank 15 | starkbank-ecdsa 16 | 1.0.2 17 | 18 | ``` 19 | 20 | Then run: 21 | ```sh 22 | mvn clean install 23 | ``` 24 | 25 | ### Curves 26 | 27 | We currently support `secp256k1`, but it's super easy to add more curves to the project. Just add them on `Curve.java` 28 | 29 | ### Speed 30 | 31 | We ran a test on JDK 13.0.1 on a MAC Pro i5 2019. The libraries ran 100 times and showed the average times displayed bellow: 32 | 33 | | Library | sign | verify | 34 | | ------------------ |:-------------:| -------:| 35 | | [java.security] | 0.9ms | 2.4ms | 36 | | starkbank-ecdsa | 4.3ms | 9.9ms | 37 | 38 | ### Sample Code 39 | 40 | How to use it: 41 | 42 | ```java 43 | import com.starkbank.ellipticcurve.PrivateKey; 44 | import com.starkbank.ellipticcurve.PublicKey; 45 | import com.starkbank.ellipticcurve.Signature; 46 | import com.starkbank.ellipticcurve.Ecdsa; 47 | 48 | 49 | public class GenerateKeys{ 50 | 51 | public static void main(String[] args){ 52 | // Generate Keys 53 | PrivateKey privateKey = new PrivateKey(); 54 | PublicKey publicKey = privateKey.publicKey(); 55 | 56 | String message = "Testing message"; 57 | // Generate Signature 58 | Signature signature = Ecdsa.sign(message, privateKey); 59 | 60 | // Verify if signature is valid 61 | boolean verified = Ecdsa.verify(message, signature, publicKey) ; 62 | 63 | // Return the signature verification status 64 | System.out.println("Verified: " + verified); 65 | 66 | } 67 | } 68 | ``` 69 | ### OpenSSL 70 | 71 | This library is compatible with OpenSSL, so you can use it to generate keys: 72 | 73 | ``` 74 | openssl ecparam -name secp256k1 -genkey -out privateKey.pem 75 | openssl ec -in privateKey.pem -pubout -out publicKey.pem 76 | ``` 77 | 78 | Create a message.txt file and sign it: 79 | 80 | ``` 81 | openssl dgst -sha256 -sign privateKey.pem -out signatureBinary.txt message.txt 82 | ``` 83 | 84 | It's time to verify: 85 | 86 | ```java 87 | import com.starkbank.ellipticcurve.Ecdsa; 88 | import com.starkbank.ellipticcurve.PublicKey; 89 | import com.starkbank.ellipticcurve.Signature; 90 | import com.starkbank.ellipticcurve.utils.ByteString; 91 | import com.starkbank.ellipticcurve.utils.File; 92 | 93 | 94 | public class VerifyKeys { 95 | 96 | public static void main(String[] args){ 97 | // Read files 98 | String publicKeyPem = File.read("publicKey.pem"); 99 | byte[] signatureBin = File.readBytes("signatureBinary.txt"); 100 | String message = File.read("message.txt"); 101 | 102 | ByteString byteString = new ByteString(signatureBin); 103 | 104 | PublicKey publicKey = PublicKey.fromPem(publicKeyPem); 105 | Signature signature = Signature.fromDer(byteString); 106 | 107 | // Get verification status: 108 | boolean verified = Ecdsa.verify(message, signature, publicKey); 109 | System.out.println("Verification status: " + verified); 110 | } 111 | } 112 | ``` 113 | 114 | You can also verify it on terminal: 115 | 116 | ``` 117 | openssl dgst -sha256 -verify publicKey.pem -signature signatureBinary.txt message.txt 118 | ``` 119 | 120 | NOTE: If you want to create a Digital Signature to use in the [Stark Bank], you need to convert the binary signature to base64. 121 | 122 | ``` 123 | openssl base64 -in signatureBinary.txt -out signatureBase64.txt 124 | ``` 125 | 126 | You can also verify it with this library: 127 | 128 | ```java 129 | import com.starkbank.ellipticcurve.utils.ByteString; 130 | import com.starkbank.ellipticcurve.Signature; 131 | import com.starkbank.ellipticcurve.utils.File; 132 | 133 | 134 | public class GenerateSignature { 135 | 136 | public static void main(String[] args) { 137 | // Load signature file 138 | byte[] signatureBin = File.readBytes("signatureBinary.txt"); 139 | Signature signature = Signature.fromDer(new ByteString(signatureBin)); 140 | // Print signature 141 | System.out.println(signature.toBase64()); 142 | } 143 | } 144 | ``` 145 | 146 | [Stark Bank]: https://starkbank.com 147 | 148 | ### Run all unit tests 149 | ```shell 150 | gradle test 151 | ``` 152 | 153 | [ecdsa-python]: https://github.com/starkbank/ecdsa-python 154 | [java.security]: https://docs.oracle.com/javase/7/docs/api/index.html 155 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group 'com.starkbank.ellipticcurve' 6 | version '1.0.2' 7 | 8 | sourceCompatibility = 1.7 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | dependencies { 14 | testCompile group: 'junit', name: 'junit', version: '4.12' 15 | testCompile group: 'org.mockito', name: 'mockito-core', version: '2.22.0' 16 | } 17 | 18 | apply plugin: 'maven' 19 | apply plugin: 'signing' 20 | 21 | archivesBaseName = "starkbank-ecdsa" 22 | 23 | 24 | task javadocJar(type: Jar) { 25 | classifier = 'javadoc' 26 | from javadoc 27 | } 28 | 29 | task sourcesJar(type: Jar) { 30 | classifier = 'sources' 31 | from sourceSets.main.allSource 32 | } 33 | 34 | artifacts { 35 | archives javadocJar, sourcesJar 36 | } 37 | 38 | signing { 39 | sign configurations.archives 40 | } 41 | 42 | uploadArchives { 43 | repositories { 44 | mavenDeployer { 45 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 46 | 47 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { 48 | authentication( 49 | userName: project.hasProperty('ossrhUsername') ? project.property('ossrhUsername') : 'username', 50 | password: project.hasProperty('ossrhPassword') ? project.property('ossrhPassword') : 'password' 51 | ) 52 | } 53 | 54 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { 55 | authentication( 56 | userName: project.hasProperty('ossrhUsername') ? project.property('ossrhUsername') : 'username', 57 | password: project.hasProperty('ossrhPassword') ? project.property('ossrhPassword') : 'password' 58 | ) 59 | } 60 | 61 | pom.project { 62 | name 'StarkBank ECDSA library' 63 | packaging 'jar' 64 | // optionally artifactId can be defined here 65 | description 'Pure Java ECDSA library by Stark Bank' 66 | url 'https://github.com/starkbank/ecdsa-java' 67 | 68 | scm { 69 | connection 'scm:git:git://github.com/starkbank/ecdsa-java.git' 70 | developerConnection 'scm:git:ssh://github.com/starkbank/ecdsa-java.git' 71 | url 'https://github.com/starkbank/ecdsa-java/' 72 | } 73 | 74 | licenses { 75 | license { 76 | name 'MIT License' 77 | url 'https://github.com/starkbank/sdk-java/blob/master/LICENSE' 78 | } 79 | } 80 | 81 | developers { 82 | developer { 83 | id 'rcmstark' 84 | name 'Rafael Stark' 85 | email 'rafael@starkbank.com' 86 | } 87 | developer { 88 | id 'daltonmenezes' 89 | name 'Dalton Menezes' 90 | email 'dalton.menezes@starkbank.com' 91 | } 92 | developer { 93 | id 'cdottori' 94 | name 'Caio Dottori' 95 | email 'caio.dottori@starkbank.com' 96 | } 97 | developer { 98 | id 'thalesmello' 99 | name 'Thales Mello' 100 | email 'thalesmello@gmail.com' 101 | } 102 | } 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starkbank/ecdsa-java/1a133705dfa47090914658a8f90b2101c7e62092/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-6.6-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.starkbank.ellipticcurve 5 | ecdsa-java 6 | 1.0.2 7 | 8 | 1.7 9 | 1.7 10 | 11 | 12 | 13 | junit 14 | junit 15 | 4.13.1 16 | test 17 | 18 | 19 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user guide at https://docs.gradle.org/5.1.1/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = 'starkbank-ecdsa' 11 | -------------------------------------------------------------------------------- /src/main/java/com/starkbank/ellipticcurve/Curve.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve; 2 | import java.math.BigInteger; 3 | import java.util.*; 4 | 5 | /** 6 | * Elliptic Curve Equation. 7 | * y^2 = x^3 + A*x + B (mod P) 8 | * 9 | */ 10 | 11 | public class Curve { 12 | 13 | public BigInteger A; 14 | public BigInteger B; 15 | public BigInteger P; 16 | public BigInteger N; 17 | public Point G; 18 | public String name; 19 | public long[] oid; 20 | 21 | /** 22 | * 23 | * @param A A 24 | * @param B B 25 | * @param P P 26 | * @param N N 27 | * @param Gx Gx 28 | * @param Gy Gy 29 | * @param name name 30 | * @param oid oid 31 | */ 32 | public Curve(BigInteger A, BigInteger B, BigInteger P, BigInteger N, BigInteger Gx, BigInteger Gy, String name, long[] oid) { 33 | this.A = A; 34 | this.B = B; 35 | this.P = P; 36 | this.N = N; 37 | this.G = new Point(Gx, Gy); 38 | this.name = name; 39 | this.oid = oid; 40 | } 41 | 42 | /** 43 | * Verify if the point `p` is on the curve 44 | * 45 | * @param p Point p = Point(x, y) 46 | * @return true if point is in the curve otherwise false 47 | */ 48 | public boolean contains(Point p) { 49 | if (p.x.compareTo(BigInteger.ZERO) < 0) { 50 | return false; 51 | } 52 | if (p.x.compareTo(this.P) >= 0) { 53 | return false; 54 | } 55 | if (p.y.compareTo(BigInteger.ZERO) < 0) { 56 | return false; 57 | } 58 | if (p.y.compareTo(this.P) >= 0) { 59 | return false; 60 | } 61 | return p.y.pow(2).subtract(p.x.pow(3).add(A.multiply(p.x)).add(B)).mod(P).intValue() == 0; 62 | } 63 | 64 | /** 65 | * 66 | * @return int 67 | */ 68 | public int length() { 69 | return (1 + N.toString(16).length()) / 2; 70 | } 71 | 72 | /** 73 | * 74 | */ 75 | public static final Curve secp256k1 = new Curve( 76 | BigInteger.ZERO, 77 | BigInteger.valueOf(7), 78 | new BigInteger("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", 16), 79 | new BigInteger("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16), 80 | new BigInteger("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", 16), 81 | new BigInteger("483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", 16), 82 | "secp256k1", 83 | new long[]{1, 3, 132, 0, 10} 84 | ); 85 | 86 | /** 87 | * 88 | */ 89 | public static final List supportedCurves = new ArrayList(); 90 | 91 | /** 92 | * 93 | */ 94 | public static final Map curvesByOid = new HashMap(); 95 | 96 | static { 97 | supportedCurves.add(secp256k1); 98 | 99 | for (Object c : supportedCurves) { 100 | Curve curve = (Curve) c; 101 | curvesByOid.put(Arrays.hashCode(curve.oid), curve); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/starkbank/ellipticcurve/Ecdsa.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve; 2 | import com.starkbank.ellipticcurve.utils.BinaryAscii; 3 | import com.starkbank.ellipticcurve.utils.RandomInteger; 4 | import java.math.BigInteger; 5 | import java.security.MessageDigest; 6 | import java.security.NoSuchAlgorithmException; 7 | 8 | 9 | public class Ecdsa { 10 | 11 | /** 12 | * 13 | * @param message message 14 | * @param privateKey privateKey 15 | * @param hashfunc hashfunc 16 | * @return Signature 17 | */ 18 | 19 | public static Signature sign(String message, PrivateKey privateKey, MessageDigest hashfunc) { 20 | byte[] hashMessage = hashfunc.digest(message.getBytes()); 21 | BigInteger numberMessage = BinaryAscii.numberFromString(hashMessage); 22 | Curve curve = privateKey.curve; 23 | BigInteger randNum = RandomInteger.between(BigInteger.ONE, curve.N); 24 | Point randomSignPoint = Math.multiply(curve.G, randNum, curve.N, curve.A, curve.P); 25 | BigInteger r = randomSignPoint.x.mod(curve.N); 26 | BigInteger s = ((numberMessage.add(r.multiply(privateKey.secret))).multiply(Math.inv(randNum, curve.N))).mod(curve.N); 27 | return new Signature(r, s); 28 | } 29 | 30 | /** 31 | * 32 | * @param message message 33 | * @param privateKey privateKey 34 | * @return Signature 35 | */ 36 | public static Signature sign(String message, PrivateKey privateKey) { 37 | try { 38 | return sign(message, privateKey, MessageDigest.getInstance("SHA-256")); 39 | } catch (NoSuchAlgorithmException e) { 40 | throw new IllegalStateException("Could not find SHA-256 message digest in provided java environment"); 41 | } 42 | } 43 | 44 | /** 45 | * 46 | * @param message message 47 | * @param signature signature 48 | * @param publicKey publicKey 49 | * @param hashfunc hashfunc 50 | * @return boolean 51 | */ 52 | public static boolean verify(String message, Signature signature, PublicKey publicKey, MessageDigest hashfunc) { 53 | byte[] hashMessage = hashfunc.digest(message.getBytes()); 54 | BigInteger numberMessage = BinaryAscii.numberFromString(hashMessage); 55 | Curve curve = publicKey.curve; 56 | BigInteger r = signature.r; 57 | BigInteger s = signature.s; 58 | 59 | if (r.compareTo(new BigInteger(String.valueOf(1))) < 0) { 60 | return false; 61 | } 62 | if (r.compareTo(curve.N) >= 0) { 63 | return false; 64 | } 65 | if (s.compareTo(new BigInteger(String.valueOf(1))) < 0) { 66 | return false; 67 | } 68 | if (s.compareTo(curve.N) >= 0) { 69 | return false; 70 | } 71 | 72 | BigInteger w = Math.inv(s, curve.N); 73 | Point u1 =Math.multiply(curve.G, numberMessage.multiply(w).mod(curve.N), curve.N, curve.A, curve.P); 74 | Point u2 = Math.multiply(publicKey.point, r.multiply(w).mod(curve.N), curve.N, curve.A, curve.P); 75 | Point v = Math.add(u1, u2, curve.A, curve.P); 76 | if (v.isAtInfinity()) { 77 | return false; 78 | } 79 | return v.x.mod(curve.N).equals(r); 80 | } 81 | 82 | /** 83 | * 84 | * @param message message 85 | * @param signature signature 86 | * @param publicKey publicKey 87 | * @return boolean 88 | */ 89 | public static boolean verify(String message, Signature signature, PublicKey publicKey) { 90 | try { 91 | return verify(message, signature, publicKey, MessageDigest.getInstance("SHA-256")); 92 | } catch (NoSuchAlgorithmException e) { 93 | throw new IllegalStateException("Could not find SHA-256 message digest in provided java environment"); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/starkbank/ellipticcurve/Math.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve; 2 | import java.math.BigInteger; 3 | 4 | 5 | public final class Math { 6 | 7 | /** 8 | * Fast way to multiply point and scalar in elliptic curves 9 | * 10 | * @param p First Point to multiply 11 | * @param n Scalar to multiply 12 | * @param N Order of the elliptic curve 13 | * @param P Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod P) 14 | * @param A Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod P) 15 | * @return Point that represents the sum of First and Second Point 16 | */ 17 | public static Point multiply(Point p, BigInteger n, BigInteger N, BigInteger A, BigInteger P) { 18 | return fromJacobian(jacobianMultiply(toJacobian(p), n, N, A, P), P); 19 | } 20 | 21 | /** 22 | * Fast way to add two points in elliptic curves 23 | * 24 | * @param p First Point you want to add 25 | * @param q Second Point you want to add 26 | * @param A Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod P) 27 | * @param P Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod P) 28 | * @return Point that represents the sum of First and Second Point 29 | */ 30 | 31 | public static Point add(Point p, Point q, BigInteger A, BigInteger P) { 32 | return fromJacobian(jacobianAdd(toJacobian(p), toJacobian(q), A, P), P); 33 | } 34 | 35 | /** 36 | * Extended Euclidean Algorithm. It's the 'division' in elliptic curves 37 | * 38 | * @param x Divisor 39 | * @param n Mod for division 40 | * @return Value representing the division 41 | */ 42 | public static BigInteger inv(BigInteger x, BigInteger n) { 43 | if (x.compareTo(BigInteger.ZERO) == 0) { 44 | return BigInteger.ZERO; 45 | } 46 | BigInteger lm = BigInteger.ONE; 47 | BigInteger hm = BigInteger.ZERO; 48 | BigInteger high = n; 49 | BigInteger low = x.mod(n); 50 | BigInteger r, nm, nw; 51 | while (low.compareTo(BigInteger.ONE) > 0) { 52 | r = high.divide(low); 53 | nm = hm.subtract(lm.multiply(r)); 54 | nw = high.subtract(low.multiply(r)); 55 | high = low; 56 | hm = lm; 57 | low = nw; 58 | lm = nm; 59 | } 60 | return lm.mod(n); 61 | } 62 | 63 | /** 64 | * Convert point to Jacobian coordinates 65 | * 66 | * @param p the point you want to transform 67 | * @return Point in Jacobian coordinates 68 | */ 69 | public static Point toJacobian(Point p) { 70 | 71 | return new Point(p.x, p.y, BigInteger.ONE); 72 | } 73 | 74 | /** 75 | * Convert point back from Jacobian coordinates 76 | * 77 | * @param p the point you want to transform 78 | * @param P Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod P) 79 | * @return Point in default coordinates 80 | */ 81 | public static Point fromJacobian(Point p, BigInteger P) { 82 | BigInteger z = inv(p.z, P); 83 | BigInteger x = p.x.multiply(z.pow(2)).mod(P); 84 | BigInteger y = p.y.multiply(z.pow(3)).mod(P); 85 | return new Point(x, y, BigInteger.ZERO); 86 | } 87 | 88 | /** 89 | * Double a point in elliptic curves 90 | * 91 | * @param p the point you want to transform 92 | * @param A Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod P) 93 | * @param P Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod P) 94 | * @return the result point doubled in elliptic curves 95 | */ 96 | public static Point jacobianDouble(Point p, BigInteger A, BigInteger P) { 97 | if (p.y == null || p.y.equals(BigInteger.ZERO)) { 98 | return new Point(BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO); 99 | } 100 | BigInteger ysq = p.y.pow(2).mod(P); 101 | BigInteger S = BigInteger.valueOf(4).multiply(p.x).multiply(ysq).mod(P); 102 | BigInteger M = BigInteger.valueOf(3).multiply(p.x.pow(2)).add(A.multiply(p.z.pow(4))).mod(P); 103 | BigInteger nx = M.pow(2).subtract(BigInteger.valueOf(2).multiply(S)).mod(P); 104 | BigInteger ny = M.multiply(S.subtract(nx)).subtract(BigInteger.valueOf(8).multiply(ysq.pow(2))).mod(P); 105 | BigInteger nz = BigInteger.valueOf(2).multiply(p.y).multiply(p.z).mod(P); 106 | return new Point(nx, ny, nz); 107 | } 108 | 109 | /** 110 | * Add two points in elliptic curves 111 | * 112 | * @param p First Point you want to add 113 | * @param q Second Point you want to add 114 | * @param A Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod P) 115 | * @param P Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod P) 116 | * @return Point that represents the sum of First and Second Point 117 | */ 118 | public static Point jacobianAdd(Point p, Point q, BigInteger A, BigInteger P) { 119 | if (p.y == null || p.y.equals(BigInteger.ZERO)) { 120 | return q; 121 | } 122 | if (q.y == null || q.y.equals(BigInteger.ZERO)) { 123 | return p; 124 | } 125 | BigInteger U1 = p.x.multiply(q.z.pow(2)).mod(P); 126 | BigInteger U2 = q.x.multiply(p.z.pow(2)).mod(P); 127 | BigInteger S1 = p.y.multiply(q.z.pow(3)).mod(P); 128 | BigInteger S2 = q.y.multiply(p.z.pow(3)).mod(P); 129 | if (U1.compareTo(U2) == 0) { 130 | if (S1.compareTo(S2) != 0) { 131 | return new Point(BigInteger.ZERO, BigInteger.ZERO, BigInteger.ONE); 132 | } 133 | return jacobianDouble(p, A, P); 134 | } 135 | BigInteger H = U2.subtract(U1); 136 | BigInteger R = S2.subtract(S1); 137 | BigInteger H2 = H.multiply(H).mod(P); 138 | BigInteger H3 = H.multiply(H2).mod(P); 139 | BigInteger U1H2 = U1.multiply(H2).mod(P); 140 | BigInteger nx = R.pow(2).subtract(H3).subtract(BigInteger.valueOf(2).multiply(U1H2)).mod(P); 141 | BigInteger ny = R.multiply(U1H2.subtract(nx)).subtract(S1.multiply(H3)).mod(P); 142 | BigInteger nz = H.multiply(p.z).multiply(q.z).mod(P); 143 | return new Point(nx, ny, nz); 144 | } 145 | 146 | /** 147 | * Multiply point and scalar in elliptic curves 148 | * 149 | * @param p First Point to multiply 150 | * @param n Scalar to multiply 151 | * @param N Order of the elliptic curve 152 | * @param A Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod P) 153 | * @param P Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod P) 154 | * @return Point that represents the product of First Point and scalar 155 | */ 156 | public static Point jacobianMultiply(Point p, BigInteger n, BigInteger N, BigInteger A, BigInteger P) { 157 | if (BigInteger.ZERO.compareTo(p.y) == 0 || BigInteger.ZERO.compareTo(n) == 0) { 158 | return new Point(BigInteger.ZERO, BigInteger.ZERO, BigInteger.ONE); 159 | } 160 | if (BigInteger.ONE.compareTo(n) == 0) { 161 | return p; 162 | } 163 | if (n.compareTo(BigInteger.ZERO) < 0 || n.compareTo(N) >= 0) { 164 | return jacobianMultiply(p, n.mod(N), N, A, P); 165 | } 166 | if (n.mod(BigInteger.valueOf(2)).compareTo(BigInteger.ZERO) == 0) { 167 | return jacobianDouble(jacobianMultiply(p, n.divide(BigInteger.valueOf(2)), N, A, P), A, P); 168 | } 169 | if (n.mod(BigInteger.valueOf(2)).compareTo(BigInteger.ONE) == 0) { 170 | return jacobianAdd(jacobianDouble(jacobianMultiply(p, n.divide(BigInteger.valueOf(2)), N, A, P), A, P), p, A, P); 171 | } 172 | return null; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/main/java/com/starkbank/ellipticcurve/Point.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve; 2 | import java.math.BigInteger; 3 | 4 | 5 | public class Point { 6 | 7 | public BigInteger x; 8 | public BigInteger y; 9 | public BigInteger z; 10 | 11 | /** 12 | * 13 | * @param x x 14 | * @param y y 15 | */ 16 | public Point(BigInteger x, BigInteger y) { 17 | this.x = x; 18 | this.y = y; 19 | this.z = BigInteger.ZERO; 20 | } 21 | 22 | /** 23 | * 24 | * @param x x 25 | * @param y y 26 | * @param z z 27 | */ 28 | public Point(BigInteger x, BigInteger y, BigInteger z) { 29 | this.x = x; 30 | this.y = y; 31 | this.z = z; 32 | } 33 | 34 | public boolean isAtInfinity() { 35 | return this.y.equals(BigInteger.ZERO); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/starkbank/ellipticcurve/PrivateKey.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve; 2 | import com.starkbank.ellipticcurve.utils.ByteString; 3 | import com.starkbank.ellipticcurve.utils.Der; 4 | import com.starkbank.ellipticcurve.utils.BinaryAscii; 5 | import com.starkbank.ellipticcurve.utils.RandomInteger; 6 | import java.math.BigInteger; 7 | import java.util.Arrays; 8 | 9 | 10 | public class PrivateKey { 11 | 12 | public Curve curve; 13 | public BigInteger secret; 14 | 15 | /** 16 | * 17 | */ 18 | public PrivateKey() { 19 | this(Curve.secp256k1, null); 20 | secret = RandomInteger.between(BigInteger.ONE, curve.N); 21 | } 22 | 23 | /** 24 | * 25 | * @param curve curve 26 | * @param secret secret 27 | */ 28 | public PrivateKey(Curve curve, BigInteger secret) { 29 | this.curve = curve; 30 | this.secret = secret; 31 | } 32 | 33 | /** 34 | * 35 | * @return PublicKey 36 | */ 37 | public PublicKey publicKey() { 38 | Curve curve = this.curve; 39 | Point publicPoint = Math.multiply(curve.G, this.secret, curve.N, curve.A, curve.P); 40 | return new PublicKey(publicPoint, curve); 41 | } 42 | 43 | /** 44 | * 45 | * @return ByteString 46 | */ 47 | public ByteString toByteString() { 48 | return BinaryAscii.stringFromNumber(this.secret, this.curve.length()); 49 | } 50 | 51 | /** 52 | * 53 | * @return ByteString 54 | */ 55 | public ByteString toDer() { 56 | ByteString encodedPublicKey = this.publicKey().toByteString(true); 57 | return Der.encodeSequence( 58 | Der.encodeInteger(BigInteger.valueOf(1)), 59 | Der.encodeOctetString(this.toByteString()), 60 | Der.encodeConstructed(0, Der.encodeOid(this.curve.oid)), 61 | Der.encodeConstructed(1, Der.encodeBitString(encodedPublicKey))); 62 | } 63 | 64 | /** 65 | * 66 | * @return String 67 | */ 68 | public String toPem() { 69 | return Der.toPem(this.toDer(), "EC PRIVATE KEY"); 70 | } 71 | 72 | 73 | /** 74 | * 75 | * @param string string 76 | * @return PrivateKey 77 | */ 78 | public static PrivateKey fromPem(String string) { 79 | String privkeyPem = string.substring(string.indexOf("-----BEGIN EC PRIVATE KEY-----")); 80 | return PrivateKey.fromDer(Der.fromPem(privkeyPem)); 81 | } 82 | 83 | /** 84 | * 85 | * @param string string 86 | * @return Privatekey 87 | */ 88 | public static PrivateKey fromDer(String string) { 89 | return fromDer(new ByteString(string.getBytes())); 90 | } 91 | 92 | /** 93 | * 94 | * @param string ByteString 95 | * @return PrivateKey 96 | */ 97 | public static PrivateKey fromDer(ByteString string) { 98 | ByteString[] str = Der.removeSequence(string); 99 | ByteString s = str[0]; 100 | ByteString empty = str[1]; 101 | if (!empty.isEmpty()) { 102 | throw new RuntimeException(String.format("trailing junk after DER privkey: %s", BinaryAscii.hexFromBinary(empty))); 103 | } 104 | 105 | Object[] o = Der.removeInteger(s); 106 | long one = Long.valueOf(o[0].toString()); 107 | s = (ByteString) o[1]; 108 | if (one != 1) { 109 | throw new RuntimeException(String.format("expected '1' at start of DER privkey, got %d", one)); 110 | } 111 | 112 | str = Der.removeOctetString(s); 113 | ByteString privkeyStr = str[0]; 114 | s = str[1]; 115 | Object[] t = Der.removeConstructed(s); 116 | long tag = Long.valueOf(t[0].toString()); 117 | ByteString curveOidStr = (ByteString) t[1]; 118 | s = (ByteString) t[2]; 119 | if (tag != 0) { 120 | throw new RuntimeException(String.format("expected tag 0 in DER privkey, got %d", tag)); 121 | } 122 | 123 | o = Der.removeObject(curveOidStr); 124 | long[] oidCurve = (long[]) o[0]; 125 | empty = (ByteString) o[1]; 126 | if (!"".equals(empty.toString())) { 127 | throw new RuntimeException(String.format("trailing junk after DER privkey curve_oid: %s", BinaryAscii.hexFromBinary(empty))); 128 | } 129 | Curve curve = (Curve) Curve.curvesByOid.get(Arrays.hashCode(oidCurve)); 130 | if (curve == null) { 131 | throw new RuntimeException(String.format("Unknown curve with oid %s. I only know about these: %s", Arrays.toString(oidCurve), Arrays.toString(Curve.supportedCurves.toArray()))); 132 | } 133 | 134 | if (privkeyStr.length() < curve.length()) { 135 | int l = curve.length() - privkeyStr.length(); 136 | byte[] bytes = new byte[l + privkeyStr.length()]; 137 | for (int i = 0; i < curve.length() - privkeyStr.length(); i++) { 138 | bytes[i] = 0; 139 | } 140 | byte[] privateKey = privkeyStr.getBytes(); 141 | System.arraycopy(privateKey, 0, bytes, l, bytes.length - l); 142 | privkeyStr = new ByteString(bytes); 143 | } 144 | 145 | return PrivateKey.fromString(privkeyStr, curve); 146 | } 147 | 148 | /** 149 | * 150 | * @param string byteString 151 | * @param curve curve 152 | * @return PrivateKey 153 | */ 154 | public static PrivateKey fromString(ByteString string, Curve curve) { 155 | return new PrivateKey(curve, BinaryAscii.numberFromString(string.getBytes())); 156 | } 157 | 158 | /** 159 | * 160 | * @param string string 161 | * @return PrivateKey 162 | */ 163 | public static PrivateKey fromString(String string) { 164 | return fromString(new ByteString(string.getBytes())); 165 | } 166 | 167 | /** 168 | * 169 | * @param string byteString 170 | * @return PrivateKey 171 | */ 172 | public static PrivateKey fromString(ByteString string) { 173 | return PrivateKey.fromString(string, Curve.secp256k1); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/com/starkbank/ellipticcurve/PublicKey.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve; 2 | import com.starkbank.ellipticcurve.utils.ByteString; 3 | import com.starkbank.ellipticcurve.utils.Der; 4 | import com.starkbank.ellipticcurve.utils.BinaryAscii; 5 | import java.util.Arrays; 6 | import static com.starkbank.ellipticcurve.Curve.secp256k1; 7 | import static com.starkbank.ellipticcurve.Curve.supportedCurves; 8 | 9 | 10 | public class PublicKey { 11 | 12 | public Point point; 13 | public Curve curve; 14 | 15 | /** 16 | * 17 | * @param point point 18 | * @param curve curve 19 | */ 20 | public PublicKey(Point point, Curve curve) { 21 | this.point = point; 22 | this.curve = curve; 23 | } 24 | 25 | /** 26 | * 27 | * @return ByteString 28 | */ 29 | public ByteString toByteString() { 30 | return toByteString(false); 31 | } 32 | 33 | /** 34 | * 35 | * @param encoded encoded 36 | * @return ByteString 37 | */ 38 | public ByteString toByteString(boolean encoded) { 39 | ByteString xStr = BinaryAscii.stringFromNumber(point.x, curve.length()); 40 | ByteString yStr = BinaryAscii.stringFromNumber(point.y, curve.length()); 41 | xStr.insert(yStr.getBytes()); 42 | if(encoded) { 43 | xStr.insert(0, new byte[]{0, 4} ); 44 | } 45 | return xStr; 46 | } 47 | 48 | /** 49 | * 50 | * @return ByteString 51 | */ 52 | public ByteString toDer() { 53 | long[] oidEcPublicKey = new long[]{1, 2, 840, 10045, 2, 1}; 54 | ByteString encodeEcAndOid = Der.encodeSequence(Der.encodeOid(oidEcPublicKey), Der.encodeOid(curve.oid)); 55 | return Der.encodeSequence(encodeEcAndOid, Der.encodeBitString(this.toByteString(true))); 56 | } 57 | 58 | /** 59 | * 60 | * @return String 61 | */ 62 | public String toPem() { 63 | return Der.toPem(this.toDer(), "PUBLIC KEY"); 64 | } 65 | 66 | /** 67 | * 68 | * @param string string 69 | * @return PublicKey 70 | */ 71 | public static PublicKey fromPem(String string) { 72 | return PublicKey.fromDer(Der.fromPem(string)); 73 | } 74 | 75 | /** 76 | * 77 | * @param string byteString 78 | * @return PublicKey 79 | */ 80 | public static PublicKey fromDer(ByteString string) { 81 | ByteString[] str = Der.removeSequence(string); 82 | ByteString s1 = str[0]; 83 | ByteString empty = str[1]; 84 | if (!empty.isEmpty()) { 85 | throw new RuntimeException (String.format("trailing junk after DER pubkey: %s", BinaryAscii.hexFromBinary(empty))); 86 | } 87 | str = Der.removeSequence(s1); 88 | ByteString s2 = str[0]; 89 | ByteString pointStrBitstring = str[1]; 90 | Object[] o = Der.removeObject(s2); 91 | ByteString rest = (ByteString) o[1]; 92 | o = Der.removeObject(rest); 93 | long[] oidCurve = (long[]) o[0]; 94 | empty = (ByteString) o[1]; 95 | if (!empty.isEmpty()) { 96 | throw new RuntimeException (String.format("trailing junk after DER pubkey objects: %s", BinaryAscii.hexFromBinary(empty))); 97 | } 98 | 99 | Curve curve = (Curve) Curve.curvesByOid.get(Arrays.hashCode(oidCurve)); 100 | if (curve == null) { 101 | throw new RuntimeException(String.format("Unknown curve with oid %s. I only know about these: %s", Arrays.toString(oidCurve), Arrays.toString(supportedCurves.toArray()))); 102 | } 103 | 104 | str = Der.removeBitString(pointStrBitstring); 105 | ByteString pointStr = str[0]; 106 | empty = str[1]; 107 | if (!empty.isEmpty()) { 108 | throw new RuntimeException (String.format("trailing junk after pubkey pointstring: %s", BinaryAscii.hexFromBinary(empty))); 109 | } 110 | return PublicKey.fromString(pointStr.substring(2), curve); 111 | } 112 | 113 | /** 114 | * 115 | * @param string byteString 116 | * @param curve curve 117 | * @param validatePoint validatePoint 118 | * @return PublicKey 119 | */ 120 | public static PublicKey fromString(ByteString string, Curve curve, boolean validatePoint) { 121 | int baselen = curve.length(); 122 | 123 | ByteString xs = string.substring(0, baselen); 124 | ByteString ys = string.substring(baselen); 125 | 126 | Point p = new Point(BinaryAscii.numberFromString(xs.getBytes()), BinaryAscii.numberFromString(ys.getBytes())); 127 | 128 | PublicKey publicKey = new PublicKey(p, curve); 129 | if (!validatePoint) { 130 | return publicKey; 131 | } 132 | if (p.isAtInfinity()) { 133 | throw new RuntimeException("Public Key point is at infinity"); 134 | } 135 | if (!curve.contains(p)) { 136 | throw new RuntimeException(String.format("Point (%s,%s) is not valid for curve %s", p.x, p.y, curve.name)); 137 | } 138 | if (!Math.multiply(p, curve.N, curve.N, curve.A, curve.P).isAtInfinity()) { 139 | throw new RuntimeException(String.format("Point (%s,%s) * %s.N is not at infinity", p.x, p.y, curve.name)); 140 | } 141 | return publicKey; 142 | } 143 | 144 | /** 145 | * 146 | * @param string byteString 147 | * @param curve curve 148 | * @return PublicKey 149 | */ 150 | public static PublicKey fromString(ByteString string, Curve curve) { 151 | return fromString(string, curve, true); 152 | } 153 | 154 | /** 155 | * 156 | * @param string byteString 157 | * @param validatePoint validatePoint 158 | * @return PublicKey 159 | */ 160 | public static PublicKey fromString(ByteString string, boolean validatePoint) { 161 | return fromString(string, secp256k1, validatePoint); 162 | } 163 | 164 | /** 165 | * 166 | * @param string byteString 167 | * @return PublicKey 168 | */ 169 | public static PublicKey fromString(ByteString string) { 170 | return fromString(string, true); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/com/starkbank/ellipticcurve/Signature.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve; 2 | import com.starkbank.ellipticcurve.utils.Base64; 3 | import com.starkbank.ellipticcurve.utils.BinaryAscii; 4 | import com.starkbank.ellipticcurve.utils.ByteString; 5 | import com.starkbank.ellipticcurve.utils.Der; 6 | import java.io.IOException; 7 | import java.math.BigInteger; 8 | 9 | 10 | public class Signature { 11 | 12 | public BigInteger r; 13 | public BigInteger s; 14 | 15 | /** 16 | * 17 | * @param r r 18 | * @param s s 19 | */ 20 | public Signature(BigInteger r, BigInteger s) { 21 | this.r = r; 22 | this.s = s; 23 | } 24 | 25 | /** 26 | * 27 | * @return ByteString 28 | */ 29 | public ByteString toDer() { 30 | return Der.encodeSequence(Der.encodeInteger(r), Der.encodeInteger(s)); 31 | } 32 | 33 | /** 34 | * 35 | * @return String 36 | */ 37 | public String toBase64() { 38 | return Base64.encodeBytes(toDer().getBytes()); 39 | } 40 | 41 | /** 42 | * 43 | * @param string byteString 44 | * @return Signature 45 | */ 46 | public static Signature fromDer(ByteString string) { 47 | ByteString[] str = Der.removeSequence(string); 48 | ByteString rs = str[0]; 49 | ByteString empty = str[1]; 50 | if (!empty.isEmpty()) { 51 | throw new RuntimeException(String.format("trailing junk after DER sig: %s", BinaryAscii.hexFromBinary(empty))); 52 | } 53 | Object[] o = Der.removeInteger(rs); 54 | BigInteger r = new BigInteger(o[0].toString()); 55 | ByteString rest = (ByteString) o[1]; 56 | o = Der.removeInteger(rest); 57 | BigInteger s = new BigInteger(o[0].toString()); 58 | empty = (ByteString) o[1]; 59 | if (!empty.isEmpty()) { 60 | throw new RuntimeException(String.format("trailing junk after DER numbers: %s", BinaryAscii.hexFromBinary(empty))); 61 | } 62 | return new Signature(r, s); 63 | } 64 | 65 | /** 66 | * 67 | * @param string byteString 68 | * @return Signature 69 | */ 70 | public static Signature fromBase64(ByteString string) { 71 | ByteString der = null; 72 | try { 73 | der = new ByteString(Base64.decode(string.getBytes())); 74 | } catch (IOException e) { 75 | throw new IllegalArgumentException("Corrupted base64 string! Could not decode base64 from it"); 76 | } 77 | return fromDer(der); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/starkbank/ellipticcurve/utils/Base64.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve.utils; 2 | 3 | /** 4 | *

Encodes and decodes to and from Base64 notation.

5 | *

Homepage: http://iharder.net/base64.

6 | * 7 | *

Example:

8 | * 9 | * String encoded = Base64.encode( myByteArray ); 10 | * byte[] myByteArray = Base64.decode( encoded ); 11 | * 12 | *

The options parameter, which appears in a few places, is used to pass 13 | * several pieces of information to the encoder. In the "higher level" methods such as 14 | * encodeBytes( bytes, options ) the options parameter can be used to indicate such 15 | * things as first gzipping the bytes before encoding them, not inserting linefeeds, 16 | * and encoding using the URL-safe and Ordered dialects.

17 | * 18 | *

Note, according to RFC3548, 19 | * Section 2.1, implementations should not add line feeds unless explicitly told 20 | * to do so. I've got Base64 set to this behavior now, although earlier versions 21 | * broke lines by default.

22 | * 23 | *

The constants defined in Base64 can be OR-ed together to combine options, so you 24 | * might make a call like this:

25 | * 26 | * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES ); 27 | *

to compress the data before encoding it and then making the output have newline characters.

28 | *

Also...

29 | * String encoded = Base64.encodeBytes( crazyString.getBytes() ); 30 | * 31 | * 32 | * 33 | *

34 | * Change Log: 35 | *

36 | * 136 | * 137 | *

138 | * I am placing this code in the Public Domain. Do with it as you will. 139 | * This software comes with no guarantees or warranties but with 140 | * plenty of well-wishing instead! 141 | * Please visit http://iharder.net/base64 142 | * periodically to check for updates or to contribute improvements. 143 | *

144 | * 145 | * @author Robert Harder 146 | * @author rob@iharder.net 147 | * @version 2.3.7 148 | */ 149 | public class Base64 150 | { 151 | 152 | /* ******** P U B L I C F I E L D S ******** */ 153 | 154 | 155 | /** No options specified. Value is zero. */ 156 | public final static int NO_OPTIONS = 0; 157 | 158 | /** Specify encoding in first bit. Value is one. */ 159 | public final static int ENCODE = 1; 160 | 161 | 162 | /** Specify decoding in first bit. Value is zero. */ 163 | public final static int DECODE = 0; 164 | 165 | 166 | /** Specify that data should be gzip-compressed in second bit. Value is two. */ 167 | public final static int GZIP = 2; 168 | 169 | /** Specify that gzipped data should not be automatically gunzipped. */ 170 | public final static int DONT_GUNZIP = 4; 171 | 172 | 173 | /** Do break lines when encoding. Value is 8. */ 174 | public final static int DO_BREAK_LINES = 8; 175 | 176 | /** 177 | * Encode using Base64-like encoding that is URL- and Filename-safe as described 178 | * in Section 4 of RFC3548: 179 | * http://www.faqs.org/rfcs/rfc3548.html. 180 | * It is important to note that data encoded this way is not officially valid Base64, 181 | * or at the very least should not be called Base64 without also specifying that is 182 | * was encoded using the URL- and Filename-safe dialect. 183 | */ 184 | public final static int URL_SAFE = 16; 185 | 186 | 187 | /** 188 | * Encode using the special "ordered" dialect of Base64 described here: 189 | * http://www.faqs.org/qa/rfcc-1940.html. 190 | */ 191 | public final static int ORDERED = 32; 192 | 193 | 194 | /* ******** P R I V A T E F I E L D S ******** */ 195 | 196 | 197 | /** Maximum line length (76) of Base64 output. */ 198 | private final static int MAX_LINE_LENGTH = 76; 199 | 200 | 201 | /** The equals sign (=) as a byte. */ 202 | private final static byte EQUALS_SIGN = (byte)'='; 203 | 204 | 205 | /** The new line character (\n) as a byte. */ 206 | private final static byte NEW_LINE = (byte)'\n'; 207 | 208 | 209 | /** Preferred encoding. */ 210 | private final static String PREFERRED_ENCODING = "US-ASCII"; 211 | 212 | 213 | private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding 214 | private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding 215 | 216 | 217 | /* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ 218 | 219 | /** The 64 valid Base64 values. */ 220 | /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ 221 | private final static byte[] _STANDARD_ALPHABET = { 222 | (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', 223 | (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', 224 | (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', 225 | (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', 226 | (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', 227 | (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', 228 | (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', 229 | (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', 230 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', 231 | (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' 232 | }; 233 | 234 | 235 | /** 236 | * Translates a Base64 value to either its 6-bit reconstruction value 237 | * or a negative number indicating some other meaning. 238 | **/ 239 | private final static byte[] _STANDARD_DECODABET = { 240 | -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 241 | -5,-5, // Whitespace: Tab and Linefeed 242 | -9,-9, // Decimal 11 - 12 243 | -5, // Whitespace: Carriage Return 244 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 245 | -9,-9,-9,-9,-9, // Decimal 27 - 31 246 | -5, // Whitespace: Space 247 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 248 | 62, // Plus sign at decimal 43 249 | -9,-9,-9, // Decimal 44 - 46 250 | 63, // Slash at decimal 47 251 | 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine 252 | -9,-9,-9, // Decimal 58 - 60 253 | -1, // Equals sign at decimal 61 254 | -9,-9,-9, // Decimal 62 - 64 255 | 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' 256 | 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' 257 | -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 258 | 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' 259 | 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' 260 | -9,-9,-9,-9,-9 // Decimal 123 - 127 261 | ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 262 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 263 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 264 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 265 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 266 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 267 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 268 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 269 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 270 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 271 | }; 272 | 273 | 274 | /* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ 275 | 276 | /** 277 | * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: 278 | * http://www.faqs.org/rfcs/rfc3548.html. 279 | * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." 280 | */ 281 | private final static byte[] _URL_SAFE_ALPHABET = { 282 | (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', 283 | (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', 284 | (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', 285 | (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', 286 | (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', 287 | (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', 288 | (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', 289 | (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', 290 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', 291 | (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' 292 | }; 293 | 294 | /** 295 | * Used in decoding URL- and Filename-safe dialects of Base64. 296 | */ 297 | private final static byte[] _URL_SAFE_DECODABET = { 298 | -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 299 | -5,-5, // Whitespace: Tab and Linefeed 300 | -9,-9, // Decimal 11 - 12 301 | -5, // Whitespace: Carriage Return 302 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 303 | -9,-9,-9,-9,-9, // Decimal 27 - 31 304 | -5, // Whitespace: Space 305 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 306 | -9, // Plus sign at decimal 43 307 | -9, // Decimal 44 308 | 62, // Minus sign at decimal 45 309 | -9, // Decimal 46 310 | -9, // Slash at decimal 47 311 | 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine 312 | -9,-9,-9, // Decimal 58 - 60 313 | -1, // Equals sign at decimal 61 314 | -9,-9,-9, // Decimal 62 - 64 315 | 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' 316 | 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' 317 | -9,-9,-9,-9, // Decimal 91 - 94 318 | 63, // Underscore at decimal 95 319 | -9, // Decimal 96 320 | 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' 321 | 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' 322 | -9,-9,-9,-9,-9 // Decimal 123 - 127 323 | ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 324 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 325 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 326 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 327 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 328 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 329 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 330 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 331 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 332 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 333 | }; 334 | 335 | 336 | 337 | /* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ 338 | 339 | /** 340 | * I don't get the point of this technique, but someone requested it, 341 | * and it is described here: 342 | * http://www.faqs.org/qa/rfcc-1940.html. 343 | */ 344 | private final static byte[] _ORDERED_ALPHABET = { 345 | (byte)'-', 346 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', 347 | (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', 348 | (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', 349 | (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', 350 | (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', 351 | (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', 352 | (byte)'_', 353 | (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', 354 | (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', 355 | (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', 356 | (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' 357 | }; 358 | 359 | /** 360 | * Used in decoding the "ordered" dialect of Base64. 361 | */ 362 | private final static byte[] _ORDERED_DECODABET = { 363 | -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 364 | -5,-5, // Whitespace: Tab and Linefeed 365 | -9,-9, // Decimal 11 - 12 366 | -5, // Whitespace: Carriage Return 367 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 368 | -9,-9,-9,-9,-9, // Decimal 27 - 31 369 | -5, // Whitespace: Space 370 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 371 | -9, // Plus sign at decimal 43 372 | -9, // Decimal 44 373 | 0, // Minus sign at decimal 45 374 | -9, // Decimal 46 375 | -9, // Slash at decimal 47 376 | 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine 377 | -9,-9,-9, // Decimal 58 - 60 378 | -1, // Equals sign at decimal 61 379 | -9,-9,-9, // Decimal 62 - 64 380 | 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' 381 | 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' 382 | -9,-9,-9,-9, // Decimal 91 - 94 383 | 37, // Underscore at decimal 95 384 | -9, // Decimal 96 385 | 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' 386 | 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' 387 | -9,-9,-9,-9,-9 // Decimal 123 - 127 388 | ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 389 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 390 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 391 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 392 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 393 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 394 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 395 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 396 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 397 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 398 | }; 399 | 400 | 401 | /* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ 402 | 403 | 404 | /** 405 | * Returns one of the _SOMETHING_ALPHABET byte arrays depending on 406 | * the options specified. 407 | * It's possible, though silly, to specify ORDERED and URLSAFE 408 | * in which case one of them will be picked, though there is 409 | * no guarantee as to which one will be picked. 410 | */ 411 | private final static byte[] getAlphabet( int options ) { 412 | if ((options & URL_SAFE) == URL_SAFE) { 413 | return _URL_SAFE_ALPHABET; 414 | } else if ((options & ORDERED) == ORDERED) { 415 | return _ORDERED_ALPHABET; 416 | } else { 417 | return _STANDARD_ALPHABET; 418 | } 419 | } // end getAlphabet 420 | 421 | 422 | /** 423 | * Returns one of the _SOMETHING_DECODABET byte arrays depending on 424 | * the options specified. 425 | * It's possible, though silly, to specify ORDERED and URL_SAFE 426 | * in which case one of them will be picked, though there is 427 | * no guarantee as to which one will be picked. 428 | */ 429 | private final static byte[] getDecodabet( int options ) { 430 | if( (options & URL_SAFE) == URL_SAFE) { 431 | return _URL_SAFE_DECODABET; 432 | } else if ((options & ORDERED) == ORDERED) { 433 | return _ORDERED_DECODABET; 434 | } else { 435 | return _STANDARD_DECODABET; 436 | } 437 | } // end getAlphabet 438 | 439 | 440 | 441 | /** Defeats instantiation. */ 442 | public Base64(){} 443 | 444 | 445 | 446 | 447 | /* ******** E N C O D I N G M E T H O D S ******** */ 448 | 449 | 450 | /** 451 | * Encodes up to the first three bytes of array threeBytes 452 | * and returns a four-byte array in Base64 notation. 453 | * The actual number of significant bytes in your array is 454 | * given by numSigBytes. 455 | * The array threeBytes needs only be as big as 456 | * numSigBytes. 457 | * Code can reuse a byte array by passing a four-byte array as b4. 458 | * 459 | * @param b4 A reusable byte array to reduce array instantiation 460 | * @param threeBytes the array to convert 461 | * @param numSigBytes the number of significant bytes in your array 462 | * @return four byte array in Base64 notation. 463 | * @since 1.5.1 464 | */ 465 | private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) { 466 | encode3to4( threeBytes, 0, numSigBytes, b4, 0, options ); 467 | return b4; 468 | } // end encode3to4 469 | 470 | 471 | /** 472 | *

Encodes up to three bytes of the array source 473 | * and writes the resulting four Base64 bytes to destination. 474 | * The source and destination arrays can be manipulated 475 | * anywhere along their length by specifying 476 | * srcOffset and destOffset. 477 | * This method does not check to make sure your arrays 478 | * are large enough to accomodate srcOffset + 3 for 479 | * the source array or destOffset + 4 for 480 | * the destination array. 481 | * The actual number of significant bytes in your array is 482 | * given by numSigBytes.

483 | *

This is the lowest level of the encoding methods with 484 | * all possible parameters.

485 | * 486 | * @param source the array to convert 487 | * @param srcOffset the index where conversion begins 488 | * @param numSigBytes the number of significant bytes in your array 489 | * @param destination the array to hold the conversion 490 | * @param destOffset the index where output will be put 491 | * @return the destination array 492 | * @since 1.3 493 | */ 494 | private static byte[] encode3to4( 495 | byte[] source, int srcOffset, int numSigBytes, 496 | byte[] destination, int destOffset, int options ) { 497 | 498 | byte[] ALPHABET = getAlphabet( options ); 499 | 500 | // 1 2 3 501 | // 01234567890123456789012345678901 Bit position 502 | // --------000000001111111122222222 Array position from threeBytes 503 | // --------| || || || | Six bit groups to index ALPHABET 504 | // >>18 >>12 >> 6 >> 0 Right shift necessary 505 | // 0x3f 0x3f 0x3f Additional AND 506 | 507 | // Create buffer with zero-padding if there are only one or two 508 | // significant bytes passed in the array. 509 | // We have to shift left 24 in order to flush out the 1's that appear 510 | // when Java treats a value as negative that is cast from a byte to an int. 511 | int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) 512 | | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) 513 | | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); 514 | 515 | switch( numSigBytes ) 516 | { 517 | case 3: 518 | destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; 519 | destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; 520 | destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; 521 | destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; 522 | return destination; 523 | 524 | case 2: 525 | destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; 526 | destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; 527 | destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; 528 | destination[ destOffset + 3 ] = EQUALS_SIGN; 529 | return destination; 530 | 531 | case 1: 532 | destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; 533 | destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; 534 | destination[ destOffset + 2 ] = EQUALS_SIGN; 535 | destination[ destOffset + 3 ] = EQUALS_SIGN; 536 | return destination; 537 | 538 | default: 539 | return destination; 540 | } // end switch 541 | } // end encode3to4 542 | 543 | 544 | 545 | /** 546 | * Performs Base64 encoding on the raw ByteBuffer, 547 | * writing it to the encoded ByteBuffer. 548 | * This is an experimental feature. Currently it does not 549 | * pass along any options (such as {@link #DO_BREAK_LINES} 550 | * or {@link #GZIP}. 551 | * 552 | * @param raw input buffer 553 | * @param encoded output buffer 554 | * @since 2.3 555 | */ 556 | public static void encode( java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded ){ 557 | byte[] raw3 = new byte[3]; 558 | byte[] enc4 = new byte[4]; 559 | 560 | while( raw.hasRemaining() ){ 561 | int rem = java.lang.Math.min(3,raw.remaining()); 562 | raw.get(raw3,0,rem); 563 | Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); 564 | encoded.put(enc4); 565 | } // end input remaining 566 | } 567 | 568 | 569 | /** 570 | * Performs Base64 encoding on the raw ByteBuffer, 571 | * writing it to the encoded CharBuffer. 572 | * This is an experimental feature. Currently it does not 573 | * pass along any options (such as {@link #DO_BREAK_LINES} 574 | * or {@link #GZIP}. 575 | * 576 | * @param raw input buffer 577 | * @param encoded output buffer 578 | * @since 2.3 579 | */ 580 | public static void encode( java.nio.ByteBuffer raw, java.nio.CharBuffer encoded ){ 581 | byte[] raw3 = new byte[3]; 582 | byte[] enc4 = new byte[4]; 583 | 584 | while( raw.hasRemaining() ){ 585 | int rem = java.lang.Math.min(3,raw.remaining()); 586 | raw.get(raw3,0,rem); 587 | Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); 588 | for( int i = 0; i < 4; i++ ){ 589 | encoded.put( (char)(enc4[i] & 0xFF) ); 590 | } 591 | } // end input remaining 592 | } 593 | 594 | 595 | 596 | 597 | /** 598 | * Serializes an object and returns the Base64-encoded 599 | * version of that serialized object. 600 | * 601 | *

As of v 2.3, if the object 602 | * cannot be serialized or there is another error, 603 | * the method will throw an java.io.IOException. This is new to v2.3! 604 | * In earlier versions, it just returned a null value, but 605 | * in retrospect that's a pretty poor way to handle it.

606 | * 607 | * The object is not GZip-compressed before being encoded. 608 | * 609 | * @param serializableObject The object to encode 610 | * @return The Base64-encoded object 611 | * @throws java.io.IOException if there is an error 612 | * @throws NullPointerException if serializedObject is null 613 | * @since 1.4 614 | */ 615 | public static String encodeObject( java.io.Serializable serializableObject ) 616 | throws java.io.IOException { 617 | return encodeObject( serializableObject, NO_OPTIONS ); 618 | } // end encodeObject 619 | 620 | 621 | 622 | /** 623 | * Serializes an object and returns the Base64-encoded 624 | * version of that serialized object. 625 | * 626 | *

As of v 2.3, if the object 627 | * cannot be serialized or there is another error, 628 | * the method will throw an java.io.IOException. This is new to v2.3! 629 | * In earlier versions, it just returned a null value, but 630 | * in retrospect that's a pretty poor way to handle it.

631 | * 632 | * The object is not GZip-compressed before being encoded. 633 | *

634 | * Example options:

 635 |      *   GZIP: gzip-compresses object before encoding it.
 636 |      *   DO_BREAK_LINES: break lines at 76 characters
 637 |      * 
638 | *

639 | * Example: encodeObject( myObj, Base64.GZIP ) or 640 | *

641 | * Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES ) 642 | * 643 | * @param serializableObject The object to encode 644 | * @param options Specified options 645 | * @return The Base64-encoded object 646 | * @see Base64#GZIP 647 | * @see Base64#DO_BREAK_LINES 648 | * @throws java.io.IOException if there is an error 649 | * @since 2.0 650 | */ 651 | public static String encodeObject( java.io.Serializable serializableObject, int options ) 652 | throws java.io.IOException { 653 | 654 | if( serializableObject == null ){ 655 | throw new NullPointerException( "Cannot serialize a null object." ); 656 | } // end if: null 657 | 658 | // Streams 659 | java.io.ByteArrayOutputStream baos = null; 660 | java.io.OutputStream b64os = null; 661 | java.util.zip.GZIPOutputStream gzos = null; 662 | java.io.ObjectOutputStream oos = null; 663 | 664 | 665 | try { 666 | // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream 667 | baos = new java.io.ByteArrayOutputStream(); 668 | b64os = new OutputStream( baos, ENCODE | options ); 669 | if( (options & GZIP) != 0 ){ 670 | // Gzip 671 | gzos = new java.util.zip.GZIPOutputStream(b64os); 672 | oos = new java.io.ObjectOutputStream( gzos ); 673 | } else { 674 | // Not gzipped 675 | oos = new java.io.ObjectOutputStream( b64os ); 676 | } 677 | oos.writeObject( serializableObject ); 678 | } // end try 679 | catch( java.io.IOException e ) { 680 | // Catch it and then throw it immediately so that 681 | // the finally{} block is called for cleanup. 682 | throw e; 683 | } // end catch 684 | finally { 685 | try{ oos.close(); } catch( Exception e ){} 686 | try{ gzos.close(); } catch( Exception e ){} 687 | try{ b64os.close(); } catch( Exception e ){} 688 | try{ baos.close(); } catch( Exception e ){} 689 | } // end finally 690 | 691 | // Return value according to relevant encoding. 692 | try { 693 | return new String( baos.toByteArray(), PREFERRED_ENCODING ); 694 | } // end try 695 | catch (java.io.UnsupportedEncodingException uue){ 696 | // Fall back to some Java default 697 | return new String( baos.toByteArray() ); 698 | } // end catch 699 | 700 | } // end encode 701 | 702 | 703 | 704 | /** 705 | * Encodes a byte array into Base64 notation. 706 | * Does not GZip-compress data. 707 | * 708 | * @param source The data to convert 709 | * @return The data in Base64-encoded form 710 | * @throws NullPointerException if source array is null 711 | * @since 1.4 712 | */ 713 | public static String encodeBytes( byte[] source ) { 714 | // Since we're not going to have the GZIP encoding turned on, 715 | // we're not going to have an java.io.IOException thrown, so 716 | // we should not force the user to have to catch it. 717 | String encoded = null; 718 | try { 719 | encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); 720 | } catch (java.io.IOException ex) { 721 | assert false : ex.getMessage(); 722 | } // end catch 723 | assert encoded != null; 724 | return encoded; 725 | } // end encodeBytes 726 | 727 | 728 | 729 | /** 730 | * Encodes a byte array into Base64 notation. 731 | *

732 | * Example options:

 733 |      *   GZIP: gzip-compresses object before encoding it.
 734 |      *   DO_BREAK_LINES: break lines at 76 characters
 735 |      *     Note: Technically, this makes your encoding non-compliant.
 736 |      * 
737 | *

738 | * Example: encodeBytes( myData, Base64.GZIP ) or 739 | *

740 | * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) 741 | * 742 | * 743 | *

As of v 2.3, if there is an error with the GZIP stream, 744 | * the method will throw an java.io.IOException. This is new to v2.3! 745 | * In earlier versions, it just returned a null value, but 746 | * in retrospect that's a pretty poor way to handle it.

747 | * 748 | * 749 | * @param source The data to convert 750 | * @param options Specified options 751 | * @return The Base64-encoded data as a String 752 | * @see Base64#GZIP 753 | * @see Base64#DO_BREAK_LINES 754 | * @throws java.io.IOException if there is an error 755 | * @throws NullPointerException if source array is null 756 | * @since 2.0 757 | */ 758 | public static String encodeBytes( byte[] source, int options ) throws java.io.IOException { 759 | return encodeBytes( source, 0, source.length, options ); 760 | } // end encodeBytes 761 | 762 | 763 | /** 764 | * Encodes a byte array into Base64 notation. 765 | * Does not GZip-compress data. 766 | * 767 | *

As of v 2.3, if there is an error, 768 | * the method will throw an java.io.IOException. This is new to v2.3! 769 | * In earlier versions, it just returned a null value, but 770 | * in retrospect that's a pretty poor way to handle it.

771 | * 772 | * 773 | * @param source The data to convert 774 | * @param off Offset in array where conversion should begin 775 | * @param len Length of data to convert 776 | * @return The Base64-encoded data as a String 777 | * @throws NullPointerException if source array is null 778 | * @throws IllegalArgumentException if source array, offset, or length are invalid 779 | * @since 1.4 780 | */ 781 | public static String encodeBytes( byte[] source, int off, int len ) { 782 | // Since we're not going to have the GZIP encoding turned on, 783 | // we're not going to have an java.io.IOException thrown, so 784 | // we should not force the user to have to catch it. 785 | String encoded = null; 786 | try { 787 | encoded = encodeBytes( source, off, len, NO_OPTIONS ); 788 | } catch (java.io.IOException ex) { 789 | assert false : ex.getMessage(); 790 | } // end catch 791 | assert encoded != null; 792 | return encoded; 793 | } // end encodeBytes 794 | 795 | 796 | 797 | /** 798 | * Encodes a byte array into Base64 notation. 799 | *

800 | * Example options:

 801 |      *   GZIP: gzip-compresses object before encoding it.
 802 |      *   DO_BREAK_LINES: break lines at 76 characters
 803 |      *     Note: Technically, this makes your encoding non-compliant.
 804 |      * 
805 | *

806 | * Example: encodeBytes( myData, Base64.GZIP ) or 807 | *

808 | * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) 809 | * 810 | * 811 | *

As of v 2.3, if there is an error with the GZIP stream, 812 | * the method will throw an java.io.IOException. This is new to v2.3! 813 | * In earlier versions, it just returned a null value, but 814 | * in retrospect that's a pretty poor way to handle it.

815 | * 816 | * 817 | * @param source The data to convert 818 | * @param off Offset in array where conversion should begin 819 | * @param len Length of data to convert 820 | * @param options Specified options 821 | * @return The Base64-encoded data as a String 822 | * @see Base64#GZIP 823 | * @see Base64#DO_BREAK_LINES 824 | * @throws java.io.IOException if there is an error 825 | * @throws NullPointerException if source array is null 826 | * @throws IllegalArgumentException if source array, offset, or length are invalid 827 | * @since 2.0 828 | */ 829 | public static String encodeBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { 830 | byte[] encoded = encodeBytesToBytes( source, off, len, options ); 831 | 832 | // Return value according to relevant encoding. 833 | try { 834 | return new String( encoded, PREFERRED_ENCODING ); 835 | } // end try 836 | catch (java.io.UnsupportedEncodingException uue) { 837 | return new String( encoded ); 838 | } // end catch 839 | 840 | } // end encodeBytes 841 | 842 | 843 | 844 | 845 | /** 846 | * Similar to {@link #encodeBytes(byte[])} but returns 847 | * a byte array instead of instantiating a String. This is more efficient 848 | * if you're working with I/O streams and have large data sets to encode. 849 | * 850 | * 851 | * @param source The data to convert 852 | * @return The Base64-encoded data as a byte[] (of ASCII characters) 853 | * @throws NullPointerException if source array is null 854 | * @since 2.3.1 855 | */ 856 | public static byte[] encodeBytesToBytes( byte[] source ) { 857 | byte[] encoded = null; 858 | try { 859 | encoded = encodeBytesToBytes( source, 0, source.length, Base64.NO_OPTIONS ); 860 | } catch( java.io.IOException ex ) { 861 | assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); 862 | } 863 | return encoded; 864 | } 865 | 866 | 867 | /** 868 | * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns 869 | * a byte array instead of instantiating a String. This is more efficient 870 | * if you're working with I/O streams and have large data sets to encode. 871 | * 872 | * 873 | * @param source The data to convert 874 | * @param off Offset in array where conversion should begin 875 | * @param len Length of data to convert 876 | * @param options Specified options 877 | * @return The Base64-encoded data as a String 878 | * @see Base64#GZIP 879 | * @see Base64#DO_BREAK_LINES 880 | * @throws java.io.IOException if there is an error 881 | * @throws NullPointerException if source array is null 882 | * @throws IllegalArgumentException if source array, offset, or length are invalid 883 | * @since 2.3.1 884 | */ 885 | public static byte[] encodeBytesToBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { 886 | 887 | if( source == null ){ 888 | throw new NullPointerException( "Cannot serialize a null array." ); 889 | } // end if: null 890 | 891 | if( off < 0 ){ 892 | throw new IllegalArgumentException( "Cannot have negative offset: " + off ); 893 | } // end if: off < 0 894 | 895 | if( len < 0 ){ 896 | throw new IllegalArgumentException( "Cannot have length offset: " + len ); 897 | } // end if: len < 0 898 | 899 | if( off + len > source.length ){ 900 | throw new IllegalArgumentException( 901 | String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length)); 902 | } // end if: off < 0 903 | 904 | 905 | 906 | // Compress? 907 | if( (options & GZIP) != 0 ) { 908 | java.io.ByteArrayOutputStream baos = null; 909 | java.util.zip.GZIPOutputStream gzos = null; 910 | OutputStream b64os = null; 911 | 912 | try { 913 | // GZip -> Base64 -> ByteArray 914 | baos = new java.io.ByteArrayOutputStream(); 915 | b64os = new OutputStream( baos, ENCODE | options ); 916 | gzos = new java.util.zip.GZIPOutputStream( b64os ); 917 | 918 | gzos.write( source, off, len ); 919 | gzos.close(); 920 | } // end try 921 | catch( java.io.IOException e ) { 922 | // Catch it and then throw it immediately so that 923 | // the finally{} block is called for cleanup. 924 | throw e; 925 | } // end catch 926 | finally { 927 | try{ gzos.close(); } catch( Exception e ){} 928 | try{ b64os.close(); } catch( Exception e ){} 929 | try{ baos.close(); } catch( Exception e ){} 930 | } // end finally 931 | 932 | return baos.toByteArray(); 933 | } // end if: compress 934 | 935 | // Else, don't compress. Better not to use streams at all then. 936 | else { 937 | boolean breakLines = (options & DO_BREAK_LINES) != 0; 938 | 939 | //int len43 = len * 4 / 3; 940 | //byte[] outBuff = new byte[ ( len43 ) // Main 4:3 941 | // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding 942 | // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines 943 | // Try to determine more precisely how big the array needs to be. 944 | // If we get it right, we don't have to do an array copy, and 945 | // we save a bunch of memory. 946 | int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding 947 | if( breakLines ){ 948 | encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters 949 | } 950 | byte[] outBuff = new byte[ encLen ]; 951 | 952 | 953 | int d = 0; 954 | int e = 0; 955 | int len2 = len - 2; 956 | int lineLength = 0; 957 | for( ; d < len2; d+=3, e+=4 ) { 958 | encode3to4( source, d+off, 3, outBuff, e, options ); 959 | 960 | lineLength += 4; 961 | if( breakLines && lineLength >= MAX_LINE_LENGTH ) 962 | { 963 | outBuff[e+4] = NEW_LINE; 964 | e++; 965 | lineLength = 0; 966 | } // end if: end of line 967 | } // en dfor: each piece of array 968 | 969 | if( d < len ) { 970 | encode3to4( source, d+off, len - d, outBuff, e, options ); 971 | e += 4; 972 | } // end if: some padding needed 973 | 974 | 975 | // Only resize array if we didn't guess it right. 976 | if( e <= outBuff.length - 1 ){ 977 | // If breaking lines and the last byte falls right at 978 | // the line length (76 bytes per line), there will be 979 | // one extra byte, and the array will need to be resized. 980 | // Not too bad of an estimate on array size, I'd say. 981 | byte[] finalOut = new byte[e]; 982 | System.arraycopy(outBuff,0, finalOut,0,e); 983 | //System.err.println("Having to resize array from " + outBuff.length + " to " + e ); 984 | return finalOut; 985 | } else { 986 | //System.err.println("No need to resize array."); 987 | return outBuff; 988 | } 989 | 990 | } // end else: don't compress 991 | 992 | } // end encodeBytesToBytes 993 | 994 | 995 | 996 | 997 | 998 | /* ******** D E C O D I N G M E T H O D S ******** */ 999 | 1000 | 1001 | /** 1002 | * Decodes four bytes from array source 1003 | * and writes the resulting bytes (up to three of them) 1004 | * to destination. 1005 | * The source and destination arrays can be manipulated 1006 | * anywhere along their length by specifying 1007 | * srcOffset and destOffset. 1008 | * This method does not check to make sure your arrays 1009 | * are large enough to accomodate srcOffset + 4 for 1010 | * the source array or destOffset + 3 for 1011 | * the destination array. 1012 | * This method returns the actual number of bytes that 1013 | * were converted from the Base64 encoding. 1014 | *

This is the lowest level of the decoding methods with 1015 | * all possible parameters.

1016 | * 1017 | * 1018 | * @param source the array to convert 1019 | * @param srcOffset the index where conversion begins 1020 | * @param destination the array to hold the conversion 1021 | * @param destOffset the index where output will be put 1022 | * @param options alphabet type is pulled from this (standard, url-safe, ordered) 1023 | * @return the number of decoded bytes converted 1024 | * @throws NullPointerException if source or destination arrays are null 1025 | * @throws IllegalArgumentException if srcOffset or destOffset are invalid 1026 | * or there is not enough room in the array. 1027 | * @since 1.3 1028 | */ 1029 | private static int decode4to3( 1030 | byte[] source, int srcOffset, 1031 | byte[] destination, int destOffset, int options ) { 1032 | 1033 | // Lots of error checking and exception throwing 1034 | if( source == null ){ 1035 | throw new NullPointerException( "Source array was null." ); 1036 | } // end if 1037 | if( destination == null ){ 1038 | throw new NullPointerException( "Destination array was null." ); 1039 | } // end if 1040 | if( srcOffset < 0 || srcOffset + 3 >= source.length ){ 1041 | throw new IllegalArgumentException( String.format( 1042 | "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) ); 1043 | } // end if 1044 | if( destOffset < 0 || destOffset +2 >= destination.length ){ 1045 | throw new IllegalArgumentException( String.format( 1046 | "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) ); 1047 | } // end if 1048 | 1049 | 1050 | byte[] DECODABET = getDecodabet( options ); 1051 | 1052 | // Example: Dk== 1053 | if( source[ srcOffset + 2] == EQUALS_SIGN ) { 1054 | // Two ways to do the same thing. Don't know which way I like best. 1055 | //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) 1056 | // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); 1057 | int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) 1058 | | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); 1059 | 1060 | destination[ destOffset ] = (byte)( outBuff >>> 16 ); 1061 | return 1; 1062 | } 1063 | 1064 | // Example: DkL= 1065 | else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) { 1066 | // Two ways to do the same thing. Don't know which way I like best. 1067 | //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) 1068 | // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) 1069 | // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); 1070 | int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) 1071 | | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) 1072 | | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); 1073 | 1074 | destination[ destOffset ] = (byte)( outBuff >>> 16 ); 1075 | destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); 1076 | return 2; 1077 | } 1078 | 1079 | // Example: DkLE 1080 | else { 1081 | // Two ways to do the same thing. Don't know which way I like best. 1082 | //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) 1083 | // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) 1084 | // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) 1085 | // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); 1086 | int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) 1087 | | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) 1088 | | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) 1089 | | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); 1090 | 1091 | 1092 | destination[ destOffset ] = (byte)( outBuff >> 16 ); 1093 | destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); 1094 | destination[ destOffset + 2 ] = (byte)( outBuff ); 1095 | 1096 | return 3; 1097 | } 1098 | } // end decodeToBytes 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | /** 1105 | * Low-level access to decoding ASCII characters in 1106 | * the form of a byte array. Ignores GUNZIP option, if 1107 | * it's set. This is not generally a recommended method, 1108 | * although it is used internally as part of the decoding process. 1109 | * Special case: if len = 0, an empty array is returned. Still, 1110 | * if you need more speed and reduced memory footprint (and aren't 1111 | * gzipping), consider this method. 1112 | * 1113 | * @param source The Base64 encoded data 1114 | * @return decoded data 1115 | * @throws java.io.IOException java.io.IOException 1116 | * @since 2.3.1 1117 | */ 1118 | public static byte[] decode( byte[] source ) 1119 | throws java.io.IOException { 1120 | byte[] decoded = null; 1121 | // try { 1122 | decoded = decode( source, 0, source.length, Base64.NO_OPTIONS ); 1123 | // } catch( java.io.IOException ex ) { 1124 | // assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); 1125 | // } 1126 | return decoded; 1127 | } 1128 | 1129 | 1130 | 1131 | /** 1132 | * Low-level access to decoding ASCII characters in 1133 | * the form of a byte array. Ignores GUNZIP option, if 1134 | * it's set. This is not generally a recommended method, 1135 | * although it is used internally as part of the decoding process. 1136 | * Special case: if len = 0, an empty array is returned. Still, 1137 | * if you need more speed and reduced memory footprint (and aren't 1138 | * gzipping), consider this method. 1139 | * 1140 | * @param source The Base64 encoded data 1141 | * @param off The offset of where to begin decoding 1142 | * @param len The length of characters to decode 1143 | * @param options Can specify options such as alphabet type to use 1144 | * @return decoded data 1145 | * @throws java.io.IOException If bogus characters exist in source data 1146 | * @since 1.3 1147 | */ 1148 | public static byte[] decode( byte[] source, int off, int len, int options ) 1149 | throws java.io.IOException { 1150 | 1151 | // Lots of error checking and exception throwing 1152 | if( source == null ){ 1153 | throw new NullPointerException( "Cannot decode null source array." ); 1154 | } // end if 1155 | if( off < 0 || off + len > source.length ){ 1156 | throw new IllegalArgumentException( String.format( 1157 | "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) ); 1158 | } // end if 1159 | 1160 | if( len == 0 ){ 1161 | return new byte[0]; 1162 | }else if( len < 4 ){ 1163 | throw new IllegalArgumentException( 1164 | "Base64-encoded string must have at least four characters, but length specified was " + len ); 1165 | } // end if 1166 | 1167 | byte[] DECODABET = getDecodabet( options ); 1168 | 1169 | int len34 = len * 3 / 4; // Estimate on array size 1170 | byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output 1171 | int outBuffPosn = 0; // Keep track of where we're writing 1172 | 1173 | byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space 1174 | int b4Posn = 0; // Keep track of four byte input buffer 1175 | int i = 0; // Source array counter 1176 | byte sbiDecode = 0; // Special value from DECODABET 1177 | 1178 | for( i = off; i < off+len; i++ ) { // Loop through source 1179 | 1180 | sbiDecode = DECODABET[ source[i]&0xFF ]; 1181 | 1182 | // White space, Equals sign, or legit Base64 character 1183 | // Note the values such as -5 and -9 in the 1184 | // DECODABETs at the top of the file. 1185 | if( sbiDecode >= WHITE_SPACE_ENC ) { 1186 | if( sbiDecode >= EQUALS_SIGN_ENC ) { 1187 | b4[ b4Posn++ ] = source[i]; // Save non-whitespace 1188 | if( b4Posn > 3 ) { // Time to decode? 1189 | outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); 1190 | b4Posn = 0; 1191 | 1192 | // If that was the equals sign, break out of 'for' loop 1193 | if( source[i] == EQUALS_SIGN ) { 1194 | break; 1195 | } // end if: equals sign 1196 | } // end if: quartet built 1197 | } // end if: equals sign or better 1198 | } // end if: white space, equals sign or better 1199 | else { 1200 | // There's a bad input character in the Base64 stream. 1201 | throw new java.io.IOException( String.format( 1202 | "Bad Base64 input character decimal %d in array position %d", ((int)source[i])&0xFF, i ) ); 1203 | } // end else: 1204 | } // each input character 1205 | 1206 | byte[] out = new byte[ outBuffPosn ]; 1207 | System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); 1208 | return out; 1209 | } // end decode 1210 | 1211 | 1212 | 1213 | 1214 | /** 1215 | * Decodes data from Base64 notation, automatically 1216 | * detecting gzip-compressed data and decompressing it. 1217 | * 1218 | * @param s the string to decode 1219 | * @return the decoded data 1220 | * @throws java.io.IOException If there is a problem 1221 | * @since 1.4 1222 | */ 1223 | public static byte[] decode( String s ) throws java.io.IOException { 1224 | return decode( s, NO_OPTIONS ); 1225 | } 1226 | 1227 | 1228 | 1229 | /** 1230 | * Decodes data from Base64 notation, automatically 1231 | * detecting gzip-compressed data and decompressing it. 1232 | * 1233 | * @param s the string to decode 1234 | * @param options encode options such as URL_SAFE 1235 | * @return the decoded data 1236 | * @throws java.io.IOException if there is an error 1237 | * @throws NullPointerException if s is null 1238 | * @since 1.4 1239 | */ 1240 | public static byte[] decode( String s, int options ) throws java.io.IOException { 1241 | 1242 | if( s == null ){ 1243 | throw new NullPointerException( "Input string was null." ); 1244 | } // end if 1245 | 1246 | byte[] bytes; 1247 | try { 1248 | bytes = s.getBytes( PREFERRED_ENCODING ); 1249 | } // end try 1250 | catch( java.io.UnsupportedEncodingException uee ) { 1251 | bytes = s.getBytes(); 1252 | } // end catch 1253 | // 1254 | 1255 | // Decode 1256 | bytes = decode( bytes, 0, bytes.length, options ); 1257 | 1258 | // Check to see if it's gzip-compressed 1259 | // GZIP Magic Two-Byte Number: 0x8b1f (35615) 1260 | boolean dontGunzip = (options & DONT_GUNZIP) != 0; 1261 | if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) { 1262 | 1263 | int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); 1264 | if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) { 1265 | java.io.ByteArrayInputStream bais = null; 1266 | java.util.zip.GZIPInputStream gzis = null; 1267 | java.io.ByteArrayOutputStream baos = null; 1268 | byte[] buffer = new byte[2048]; 1269 | int length = 0; 1270 | 1271 | try { 1272 | baos = new java.io.ByteArrayOutputStream(); 1273 | bais = new java.io.ByteArrayInputStream( bytes ); 1274 | gzis = new java.util.zip.GZIPInputStream( bais ); 1275 | 1276 | while( ( length = gzis.read( buffer ) ) >= 0 ) { 1277 | baos.write(buffer,0,length); 1278 | } // end while: reading input 1279 | 1280 | // No error? Get new bytes. 1281 | bytes = baos.toByteArray(); 1282 | 1283 | } // end try 1284 | catch( java.io.IOException e ) { 1285 | e.printStackTrace(); 1286 | // Just return originally-decoded bytes 1287 | } // end catch 1288 | finally { 1289 | try{ baos.close(); } catch( Exception e ){} 1290 | try{ gzis.close(); } catch( Exception e ){} 1291 | try{ bais.close(); } catch( Exception e ){} 1292 | } // end finally 1293 | 1294 | } // end if: gzipped 1295 | } // end if: bytes.length >= 2 1296 | 1297 | return bytes; 1298 | } // end decode 1299 | 1300 | 1301 | 1302 | /** 1303 | * Attempts to decode Base64 data and deserialize a Java 1304 | * Object within. Returns null if there was an error. 1305 | * 1306 | * @param encodedObject The Base64 data to decode 1307 | * @return The decoded and deserialized object 1308 | * @throws NullPointerException if encodedObject is null 1309 | * @throws java.io.IOException if there is a general error 1310 | * @throws ClassNotFoundException if the decoded object is of a 1311 | * class that cannot be found by the JVM 1312 | * @since 1.5 1313 | */ 1314 | public static Object decodeToObject( String encodedObject ) 1315 | throws java.io.IOException, ClassNotFoundException { 1316 | return decodeToObject(encodedObject,NO_OPTIONS,null); 1317 | } 1318 | 1319 | 1320 | /** 1321 | * Attempts to decode Base64 data and deserialize a Java 1322 | * Object within. Returns null if there was an error. 1323 | * If loader is not null, it will be the class loader 1324 | * used when deserializing. 1325 | * 1326 | * @param encodedObject The Base64 data to decode 1327 | * @param options Various parameters related to decoding 1328 | * @param loader Optional class loader to use in deserializing classes. 1329 | * @return The decoded and deserialized object 1330 | * @throws NullPointerException if encodedObject is null 1331 | * @throws java.io.IOException if there is a general error 1332 | * @throws ClassNotFoundException if the decoded object is of a 1333 | * class that cannot be found by the JVM 1334 | * @since 2.3.4 1335 | */ 1336 | public static Object decodeToObject( 1337 | String encodedObject, int options, final ClassLoader loader ) 1338 | throws java.io.IOException, ClassNotFoundException { 1339 | 1340 | // Decode and gunzip if necessary 1341 | byte[] objBytes = decode( encodedObject, options ); 1342 | 1343 | java.io.ByteArrayInputStream bais = null; 1344 | java.io.ObjectInputStream ois = null; 1345 | Object obj = null; 1346 | 1347 | try { 1348 | bais = new java.io.ByteArrayInputStream( objBytes ); 1349 | 1350 | // If no custom class loader is provided, use Java's builtin OIS. 1351 | if( loader == null ){ 1352 | ois = new java.io.ObjectInputStream( bais ); 1353 | } // end if: no loader provided 1354 | 1355 | // Else make a customized object input stream that uses 1356 | // the provided class loader. 1357 | else { 1358 | ois = new java.io.ObjectInputStream(bais){ 1359 | @Override 1360 | public Class resolveClass(java.io.ObjectStreamClass streamClass) 1361 | throws java.io.IOException, ClassNotFoundException { 1362 | Class c = Class.forName(streamClass.getName(), false, loader); 1363 | if( c == null ){ 1364 | return super.resolveClass(streamClass); 1365 | } else { 1366 | return c; // Class loader knows of this class. 1367 | } // end else: not null 1368 | } // end resolveClass 1369 | }; // end ois 1370 | } // end else: no custom class loader 1371 | 1372 | obj = ois.readObject(); 1373 | } // end try 1374 | catch( java.io.IOException e ) { 1375 | throw e; // Catch and throw in order to execute finally{} 1376 | } // end catch 1377 | catch( ClassNotFoundException e ) { 1378 | throw e; // Catch and throw in order to execute finally{} 1379 | } // end catch 1380 | finally { 1381 | try{ bais.close(); } catch( Exception e ){} 1382 | try{ ois.close(); } catch( Exception e ){} 1383 | } // end finally 1384 | 1385 | return obj; 1386 | } // end decodeObject 1387 | 1388 | 1389 | 1390 | /** 1391 | * Convenience method for encoding data to a file. 1392 | * 1393 | *

As of v 2.3, if there is a error, 1394 | * the method will throw an java.io.IOException. This is new to v2.3! 1395 | * In earlier versions, it just returned false, but 1396 | * in retrospect that's a pretty poor way to handle it.

1397 | * 1398 | * @param dataToEncode byte array of data to encode in base64 form 1399 | * @param filename Filename for saving encoded data 1400 | * @throws java.io.IOException if there is an error 1401 | * @throws NullPointerException if dataToEncode is null 1402 | * @since 2.1 1403 | */ 1404 | public static void encodeToFile( byte[] dataToEncode, String filename ) 1405 | throws java.io.IOException { 1406 | 1407 | if( dataToEncode == null ){ 1408 | throw new NullPointerException( "Data to encode was null." ); 1409 | } // end iff 1410 | 1411 | OutputStream bos = null; 1412 | try { 1413 | bos = new OutputStream( 1414 | new java.io.FileOutputStream( filename ), Base64.ENCODE ); 1415 | bos.write( dataToEncode ); 1416 | } // end try 1417 | catch( java.io.IOException e ) { 1418 | throw e; // Catch and throw to execute finally{} block 1419 | } // end catch: java.io.IOException 1420 | finally { 1421 | try{ bos.close(); } catch( Exception e ){} 1422 | } // end finally 1423 | 1424 | } // end encodeToFile 1425 | 1426 | 1427 | /** 1428 | * Convenience method for decoding data to a file. 1429 | * 1430 | *

As of v 2.3, if there is a error, 1431 | * the method will throw an java.io.IOException. This is new to v2.3! 1432 | * In earlier versions, it just returned false, but 1433 | * in retrospect that's a pretty poor way to handle it.

1434 | * 1435 | * @param dataToDecode Base64-encoded data as a string 1436 | * @param filename Filename for saving decoded data 1437 | * @throws java.io.IOException if there is an error 1438 | * @since 2.1 1439 | */ 1440 | public static void decodeToFile( String dataToDecode, String filename ) 1441 | throws java.io.IOException { 1442 | 1443 | OutputStream bos = null; 1444 | try{ 1445 | bos = new OutputStream( 1446 | new java.io.FileOutputStream( filename ), Base64.DECODE ); 1447 | bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); 1448 | } // end try 1449 | catch( java.io.IOException e ) { 1450 | throw e; // Catch and throw to execute finally{} block 1451 | } // end catch: java.io.IOException 1452 | finally { 1453 | try{ bos.close(); } catch( Exception e ){} 1454 | } // end finally 1455 | 1456 | } // end decodeToFile 1457 | 1458 | 1459 | 1460 | 1461 | /** 1462 | * Convenience method for reading a base64-encoded 1463 | * file and decoding it. 1464 | * 1465 | *

As of v 2.3, if there is a error, 1466 | * the method will throw an java.io.IOException. This is new to v2.3! 1467 | * In earlier versions, it just returned false, but 1468 | * in retrospect that's a pretty poor way to handle it.

1469 | * 1470 | * @param filename Filename for reading encoded data 1471 | * @return decoded byte array 1472 | * @throws java.io.IOException if there is an error 1473 | * @since 2.1 1474 | */ 1475 | public static byte[] decodeFromFile( String filename ) 1476 | throws java.io.IOException { 1477 | 1478 | byte[] decodedData = null; 1479 | InputStream bis = null; 1480 | try 1481 | { 1482 | // Set up some useful variables 1483 | java.io.File file = new java.io.File( filename ); 1484 | byte[] buffer = null; 1485 | int length = 0; 1486 | int numBytes = 0; 1487 | 1488 | // Check for size of file 1489 | if( file.length() > Integer.MAX_VALUE ) 1490 | { 1491 | throw new java.io.IOException( "File is too big for this convenience method (" + file.length() + " bytes)." ); 1492 | } // end if: file too big for int index 1493 | buffer = new byte[ (int)file.length() ]; 1494 | 1495 | // Open a stream 1496 | bis = new InputStream( 1497 | new java.io.BufferedInputStream( 1498 | new java.io.FileInputStream( file ) ), Base64.DECODE ); 1499 | 1500 | // Read until done 1501 | while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { 1502 | length += numBytes; 1503 | } // end while 1504 | 1505 | // Save in a variable to return 1506 | decodedData = new byte[ length ]; 1507 | System.arraycopy( buffer, 0, decodedData, 0, length ); 1508 | 1509 | } // end try 1510 | catch( java.io.IOException e ) { 1511 | throw e; // Catch and release to execute finally{} 1512 | } // end catch: java.io.IOException 1513 | finally { 1514 | try{ bis.close(); } catch( Exception e) {} 1515 | } // end finally 1516 | 1517 | return decodedData; 1518 | } // end decodeFromFile 1519 | 1520 | 1521 | 1522 | /** 1523 | * Convenience method for reading a binary file 1524 | * and base64-encoding it. 1525 | * 1526 | *

As of v 2.3, if there is a error, 1527 | * the method will throw an java.io.IOException. This is new to v2.3! 1528 | * In earlier versions, it just returned false, but 1529 | * in retrospect that's a pretty poor way to handle it.

1530 | * 1531 | * @param filename Filename for reading binary data 1532 | * @return base64-encoded string 1533 | * @throws java.io.IOException if there is an error 1534 | * @since 2.1 1535 | */ 1536 | public static String encodeFromFile( String filename ) 1537 | throws java.io.IOException { 1538 | 1539 | String encodedData = null; 1540 | InputStream bis = null; 1541 | try 1542 | { 1543 | // Set up some useful variables 1544 | java.io.File file = new java.io.File( filename ); 1545 | byte[] buffer = new byte[ java.lang.Math.max((int)(file.length() * 1.4+1),40) ]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5) 1546 | int length = 0; 1547 | int numBytes = 0; 1548 | 1549 | // Open a stream 1550 | bis = new InputStream( 1551 | new java.io.BufferedInputStream( 1552 | new java.io.FileInputStream( file ) ), Base64.ENCODE ); 1553 | 1554 | // Read until done 1555 | while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { 1556 | length += numBytes; 1557 | } // end while 1558 | 1559 | // Save in a variable to return 1560 | encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); 1561 | 1562 | } // end try 1563 | catch( java.io.IOException e ) { 1564 | throw e; // Catch and release to execute finally{} 1565 | } // end catch: java.io.IOException 1566 | finally { 1567 | try{ bis.close(); } catch( Exception e) {} 1568 | } // end finally 1569 | 1570 | return encodedData; 1571 | } // end encodeFromFile 1572 | 1573 | /** 1574 | * Reads infile and encodes it to outfile. 1575 | * 1576 | * @param infile Input file 1577 | * @param outfile Output file 1578 | * @throws java.io.IOException if there is an error 1579 | * @since 2.2 1580 | */ 1581 | public static void encodeFileToFile( String infile, String outfile ) 1582 | throws java.io.IOException { 1583 | 1584 | String encoded = Base64.encodeFromFile( infile ); 1585 | java.io.OutputStream out = null; 1586 | try{ 1587 | out = new java.io.BufferedOutputStream( 1588 | new java.io.FileOutputStream( outfile ) ); 1589 | out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output. 1590 | } // end try 1591 | catch( java.io.IOException e ) { 1592 | throw e; // Catch and release to execute finally{} 1593 | } // end catch 1594 | finally { 1595 | try { out.close(); } 1596 | catch( Exception ex ){} 1597 | } // end finally 1598 | } // end encodeFileToFile 1599 | 1600 | 1601 | /** 1602 | * Reads infile and decodes it to outfile. 1603 | * 1604 | * @param infile Input file 1605 | * @param outfile Output file 1606 | * @throws java.io.IOException if there is an error 1607 | * @since 2.2 1608 | */ 1609 | public static void decodeFileToFile( String infile, String outfile ) 1610 | throws java.io.IOException { 1611 | 1612 | byte[] decoded = Base64.decodeFromFile( infile ); 1613 | java.io.OutputStream out = null; 1614 | try{ 1615 | out = new java.io.BufferedOutputStream( 1616 | new java.io.FileOutputStream( outfile ) ); 1617 | out.write( decoded ); 1618 | } // end try 1619 | catch( java.io.IOException e ) { 1620 | throw e; // Catch and release to execute finally{} 1621 | } // end catch 1622 | finally { 1623 | try { out.close(); } 1624 | catch( Exception ex ){} 1625 | } // end finally 1626 | } // end decodeFileToFile 1627 | 1628 | 1629 | /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ 1630 | 1631 | 1632 | 1633 | /** 1634 | * A {@link InputStream} will read data from another 1635 | * java.io.InputStream, given in the constructor, 1636 | * and encode/decode to/from Base64 notation on the fly. 1637 | * 1638 | * @see Base64 1639 | * @since 1.3 1640 | */ 1641 | public static class InputStream extends java.io.FilterInputStream { 1642 | 1643 | private boolean encode; // Encoding or decoding 1644 | private int position; // Current position in the buffer 1645 | private byte[] buffer; // Small buffer holding converted data 1646 | private int bufferLength; // Length of buffer (3 or 4) 1647 | private int numSigBytes; // Number of meaningful bytes in the buffer 1648 | private int lineLength; 1649 | private boolean breakLines; // Break lines at less than 80 characters 1650 | private int options; // Record options used to create the stream. 1651 | private byte[] decodabet; // Local copies to avoid extra method calls 1652 | 1653 | 1654 | /** 1655 | * Constructs a {@link InputStream} in DECODE mode. 1656 | * 1657 | * @param in the java.io.InputStream from which to read data. 1658 | * @since 1.3 1659 | */ 1660 | public InputStream( java.io.InputStream in ) { 1661 | this( in, DECODE ); 1662 | } // end constructor 1663 | 1664 | 1665 | /** 1666 | * Constructs a {@link InputStream} in 1667 | * either ENCODE or DECODE mode. 1668 | *

1669 | * Valid options:

1670 |          *   ENCODE or DECODE: Encode or Decode as data is read.
1671 |          *   DO_BREAK_LINES: break lines at 76 characters
1672 |          *     (only meaningful when encoding)
1673 |          * 
1674 | *

1675 | * Example: new Base64.InputStream( in, Base64.DECODE ) 1676 | * 1677 | * 1678 | * @param in the java.io.InputStream from which to read data. 1679 | * @param options Specified options 1680 | * @see Base64#ENCODE 1681 | * @see Base64#DECODE 1682 | * @see Base64#DO_BREAK_LINES 1683 | * @since 2.0 1684 | */ 1685 | public InputStream( java.io.InputStream in, int options ) { 1686 | 1687 | super( in ); 1688 | this.options = options; // Record for later 1689 | this.breakLines = (options & DO_BREAK_LINES) > 0; 1690 | this.encode = (options & ENCODE) > 0; 1691 | this.bufferLength = encode ? 4 : 3; 1692 | this.buffer = new byte[ bufferLength ]; 1693 | this.position = -1; 1694 | this.lineLength = 0; 1695 | this.decodabet = getDecodabet(options); 1696 | } // end constructor 1697 | 1698 | /** 1699 | * Reads enough of the input stream to convert 1700 | * to/from Base64 and returns the next byte. 1701 | * 1702 | * @return next byte 1703 | * @since 1.3 1704 | */ 1705 | @Override 1706 | public int read() throws java.io.IOException { 1707 | 1708 | // Do we need to get data? 1709 | if( position < 0 ) { 1710 | if( encode ) { 1711 | byte[] b3 = new byte[3]; 1712 | int numBinaryBytes = 0; 1713 | for( int i = 0; i < 3; i++ ) { 1714 | int b = in.read(); 1715 | 1716 | // If end of stream, b is -1. 1717 | if( b >= 0 ) { 1718 | b3[i] = (byte)b; 1719 | numBinaryBytes++; 1720 | } else { 1721 | break; // out of for loop 1722 | } // end else: end of stream 1723 | 1724 | } // end for: each needed input byte 1725 | 1726 | if( numBinaryBytes > 0 ) { 1727 | encode3to4( b3, 0, numBinaryBytes, buffer, 0, options ); 1728 | position = 0; 1729 | numSigBytes = 4; 1730 | } // end if: got data 1731 | else { 1732 | return -1; // Must be end of stream 1733 | } // end else 1734 | } // end if: encoding 1735 | 1736 | // Else decoding 1737 | else { 1738 | byte[] b4 = new byte[4]; 1739 | int i = 0; 1740 | for( i = 0; i < 4; i++ ) { 1741 | // Read four "meaningful" bytes: 1742 | int b = 0; 1743 | do{ b = in.read(); } 1744 | while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC ); 1745 | 1746 | if( b < 0 ) { 1747 | break; // Reads a -1 if end of stream 1748 | } // end if: end of stream 1749 | 1750 | b4[i] = (byte)b; 1751 | } // end for: each needed input byte 1752 | 1753 | if( i == 4 ) { 1754 | numSigBytes = decode4to3( b4, 0, buffer, 0, options ); 1755 | position = 0; 1756 | } // end if: got four characters 1757 | else if( i == 0 ){ 1758 | return -1; 1759 | } // end else if: also padded correctly 1760 | else { 1761 | // Must have broken out from above. 1762 | throw new java.io.IOException( "Improperly padded Base64 input." ); 1763 | } // end 1764 | 1765 | } // end else: decode 1766 | } // end else: get data 1767 | 1768 | // Got data? 1769 | if( position >= 0 ) { 1770 | // End of relevant data? 1771 | if( /*!encode &&*/ position >= numSigBytes ){ 1772 | return -1; 1773 | } // end if: got data 1774 | 1775 | if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) { 1776 | lineLength = 0; 1777 | return '\n'; 1778 | } // end if 1779 | else { 1780 | lineLength++; // This isn't important when decoding 1781 | // but throwing an extra "if" seems 1782 | // just as wasteful. 1783 | 1784 | int b = buffer[ position++ ]; 1785 | 1786 | if( position >= bufferLength ) { 1787 | position = -1; 1788 | } // end if: end 1789 | 1790 | return b & 0xFF; // This is how you "cast" a byte that's 1791 | // intended to be unsigned. 1792 | } // end else 1793 | } // end if: position >= 0 1794 | 1795 | // Else error 1796 | else { 1797 | throw new java.io.IOException( "Error in Base64 code reading stream." ); 1798 | } // end else 1799 | } // end read 1800 | 1801 | 1802 | /** 1803 | * Calls {@link #read()} repeatedly until the end of stream 1804 | * is reached or len bytes are read. 1805 | * Returns number of bytes read into array or -1 if 1806 | * end of stream is encountered. 1807 | * 1808 | * @param dest array to hold values 1809 | * @param off offset for array 1810 | * @param len max number of bytes to read into array 1811 | * @return bytes read into array or -1 if end of stream is encountered. 1812 | * @since 1.3 1813 | */ 1814 | @Override 1815 | public int read( byte[] dest, int off, int len ) 1816 | throws java.io.IOException { 1817 | int i; 1818 | int b; 1819 | for( i = 0; i < len; i++ ) { 1820 | b = read(); 1821 | 1822 | if( b >= 0 ) { 1823 | dest[off + i] = (byte) b; 1824 | } 1825 | else if( i == 0 ) { 1826 | return -1; 1827 | } 1828 | else { 1829 | break; // Out of 'for' loop 1830 | } // Out of 'for' loop 1831 | } // end for: each byte read 1832 | return i; 1833 | } // end read 1834 | 1835 | } // end inner class InputStream 1836 | 1837 | 1838 | 1839 | 1840 | 1841 | 1842 | /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ 1843 | 1844 | 1845 | 1846 | /** 1847 | * A {@link OutputStream} will write data to another 1848 | * java.io.OutputStream, given in the constructor, 1849 | * and encode/decode to/from Base64 notation on the fly. 1850 | * 1851 | * @see Base64 1852 | * @since 1.3 1853 | */ 1854 | public static class OutputStream extends java.io.FilterOutputStream { 1855 | 1856 | private boolean encode; 1857 | private int position; 1858 | private byte[] buffer; 1859 | private int bufferLength; 1860 | private int lineLength; 1861 | private boolean breakLines; 1862 | private byte[] b4; // Scratch used in a few places 1863 | private boolean suspendEncoding; 1864 | private int options; // Record for later 1865 | private byte[] decodabet; // Local copies to avoid extra method calls 1866 | 1867 | /** 1868 | * Constructs a {@link OutputStream} in ENCODE mode. 1869 | * 1870 | * @param out the java.io.OutputStream to which data will be written. 1871 | * @since 1.3 1872 | */ 1873 | public OutputStream( java.io.OutputStream out ) { 1874 | this( out, ENCODE ); 1875 | } // end constructor 1876 | 1877 | 1878 | /** 1879 | * Constructs a {@link OutputStream} in 1880 | * either ENCODE or DECODE mode. 1881 | *

1882 | * Valid options:

1883 |          *   ENCODE or DECODE: Encode or Decode as data is read.
1884 |          *   DO_BREAK_LINES: don't break lines at 76 characters
1885 |          *     (only meaningful when encoding)
1886 |          * 
1887 | *

1888 | * Example: new Base64.OutputStream( out, Base64.ENCODE ) 1889 | * 1890 | * @param out the java.io.OutputStream to which data will be written. 1891 | * @param options Specified options. 1892 | * @see Base64#ENCODE 1893 | * @see Base64#DECODE 1894 | * @see Base64#DO_BREAK_LINES 1895 | * @since 1.3 1896 | */ 1897 | public OutputStream( java.io.OutputStream out, int options ) { 1898 | super( out ); 1899 | this.breakLines = (options & DO_BREAK_LINES) != 0; 1900 | this.encode = (options & ENCODE) != 0; 1901 | this.bufferLength = encode ? 3 : 4; 1902 | this.buffer = new byte[ bufferLength ]; 1903 | this.position = 0; 1904 | this.lineLength = 0; 1905 | this.suspendEncoding = false; 1906 | this.b4 = new byte[4]; 1907 | this.options = options; 1908 | this.decodabet = getDecodabet(options); 1909 | } // end constructor 1910 | 1911 | 1912 | /** 1913 | * Writes the byte to the output stream after 1914 | * converting to/from Base64 notation. 1915 | * When encoding, bytes are buffered three 1916 | * at a time before the output stream actually 1917 | * gets a write() call. 1918 | * When decoding, bytes are buffered four 1919 | * at a time. 1920 | * 1921 | * @param theByte the byte to write 1922 | * @since 1.3 1923 | */ 1924 | @Override 1925 | public void write(int theByte) 1926 | throws java.io.IOException { 1927 | // Encoding suspended? 1928 | if( suspendEncoding ) { 1929 | this.out.write( theByte ); 1930 | return; 1931 | } // end if: supsended 1932 | 1933 | // Encode? 1934 | if( encode ) { 1935 | buffer[ position++ ] = (byte)theByte; 1936 | if( position >= bufferLength ) { // Enough to encode. 1937 | 1938 | this.out.write( encode3to4( b4, buffer, bufferLength, options ) ); 1939 | 1940 | lineLength += 4; 1941 | if( breakLines && lineLength >= MAX_LINE_LENGTH ) { 1942 | this.out.write( NEW_LINE ); 1943 | lineLength = 0; 1944 | } // end if: end of line 1945 | 1946 | position = 0; 1947 | } // end if: enough to output 1948 | } // end if: encoding 1949 | 1950 | // Else, Decoding 1951 | else { 1952 | // Meaningful Base64 character? 1953 | if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) { 1954 | buffer[ position++ ] = (byte)theByte; 1955 | if( position >= bufferLength ) { // Enough to output. 1956 | 1957 | int len = Base64.decode4to3( buffer, 0, b4, 0, options ); 1958 | out.write( b4, 0, len ); 1959 | position = 0; 1960 | } // end if: enough to output 1961 | } // end if: meaningful base64 character 1962 | else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) { 1963 | throw new java.io.IOException( "Invalid character in Base64 data." ); 1964 | } // end else: not white space either 1965 | } // end else: decoding 1966 | } // end write 1967 | 1968 | 1969 | 1970 | /** 1971 | * Calls {@link #write(int)} repeatedly until len 1972 | * bytes are written. 1973 | * 1974 | * @param theBytes array from which to read bytes 1975 | * @param off offset for array 1976 | * @param len max number of bytes to read into array 1977 | * @since 1.3 1978 | */ 1979 | @Override 1980 | public void write( byte[] theBytes, int off, int len ) 1981 | throws java.io.IOException { 1982 | // Encoding suspended? 1983 | if( suspendEncoding ) { 1984 | this.out.write( theBytes, off, len ); 1985 | return; 1986 | } // end if: supsended 1987 | 1988 | for( int i = 0; i < len; i++ ) { 1989 | write( theBytes[ off + i ] ); 1990 | } // end for: each byte written 1991 | 1992 | } // end write 1993 | 1994 | 1995 | 1996 | /** 1997 | * Method added by PHIL. [Thanks, PHIL. -Rob] 1998 | * This pads the buffer without closing the stream. 1999 | * @throws java.io.IOException if there's an error. 2000 | */ 2001 | public void flushBase64() throws java.io.IOException { 2002 | if( position > 0 ) { 2003 | if( encode ) { 2004 | out.write( encode3to4( b4, buffer, position, options ) ); 2005 | position = 0; 2006 | } // end if: encoding 2007 | else { 2008 | throw new java.io.IOException( "Base64 input not properly padded." ); 2009 | } // end else: decoding 2010 | } // end if: buffer partially full 2011 | 2012 | } // end flush 2013 | 2014 | 2015 | /** 2016 | * Flushes and closes (I think, in the superclass) the stream. 2017 | * 2018 | * @since 1.3 2019 | */ 2020 | @Override 2021 | public void close() throws java.io.IOException { 2022 | // 1. Ensure that pending characters are written 2023 | flushBase64(); 2024 | 2025 | // 2. Actually close the stream 2026 | // Base class both flushes and closes. 2027 | super.close(); 2028 | 2029 | buffer = null; 2030 | out = null; 2031 | } // end close 2032 | 2033 | 2034 | 2035 | /** 2036 | * Suspends encoding of the stream. 2037 | * May be helpful if you need to embed a piece of 2038 | * base64-encoded data in a stream. 2039 | * 2040 | * @throws java.io.IOException if there's an error flushing 2041 | * @since 1.5.1 2042 | */ 2043 | public void suspendEncoding() throws java.io.IOException { 2044 | flushBase64(); 2045 | this.suspendEncoding = true; 2046 | } // end suspendEncoding 2047 | 2048 | 2049 | /** 2050 | * Resumes encoding of the stream. 2051 | * May be helpful if you need to embed a piece of 2052 | * base64-encoded data in a stream. 2053 | * 2054 | * @since 1.5.1 2055 | */ 2056 | public void resumeEncoding() { 2057 | this.suspendEncoding = false; 2058 | } // end resumeEncoding 2059 | 2060 | 2061 | 2062 | } // end inner class OutputStream 2063 | 2064 | 2065 | } // end class Base64 2066 | -------------------------------------------------------------------------------- /src/main/java/com/starkbank/ellipticcurve/utils/BinaryAscii.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve.utils; 2 | import java.math.BigInteger; 3 | import java.util.Arrays; 4 | 5 | 6 | public final class BinaryAscii { 7 | 8 | /** 9 | * 10 | * @param string byteString 11 | * @return String 12 | */ 13 | public static String hexFromBinary(ByteString string) { 14 | return hexFromBinary(string.getBytes()); 15 | } 16 | 17 | /** 18 | * 19 | * @param bytes byte[] 20 | * @return String 21 | */ 22 | public static String hexFromBinary(byte[] bytes) { 23 | StringBuilder hexString = new StringBuilder(); 24 | 25 | for (byte aByte : bytes) { 26 | String hex = Integer.toHexString(0xFF & aByte); 27 | if (hex.length() == 1) { 28 | hexString.append('0'); 29 | } 30 | hexString.append(hex); 31 | } 32 | return hexString.toString(); 33 | } 34 | 35 | /** 36 | * 37 | * @param string string 38 | * @return byte[] 39 | */ 40 | public static byte[] binaryFromHex(String string) { 41 | byte[] bytes = new BigInteger(string, 16).toByteArray(); 42 | int i = 0; 43 | while (i < bytes.length && bytes[i] == 0) { 44 | i++; 45 | } 46 | return Arrays.copyOfRange(bytes, i, bytes.length); 47 | } 48 | 49 | /** 50 | * 51 | * @param c c 52 | * @return byte[] 53 | */ 54 | public static byte[] toBytes(int c) { 55 | return new byte[]{(byte) c}; 56 | } 57 | 58 | /** 59 | * Get a number representation of a string 60 | * 61 | * @param string String to be converted in a number 62 | * @return Number in hex from string 63 | */ 64 | public static BigInteger numberFromString(byte[] string) { 65 | return new BigInteger(BinaryAscii.hexFromBinary(string), 16); 66 | } 67 | 68 | /** 69 | * Get a string representation of a number 70 | * 71 | * @param number number to be converted in a string 72 | * @param length length max number of character for the string 73 | * @return hexadecimal string 74 | */ 75 | public static ByteString stringFromNumber(BigInteger number, int length) { 76 | String fmtStr = "%0" + String.valueOf(2 * length) + "x"; 77 | String hexString = String.format(fmtStr, number); 78 | return new ByteString(BinaryAscii.binaryFromHex(hexString)); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/starkbank/ellipticcurve/utils/ByteString.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve.utils; 2 | import java.io.UnsupportedEncodingException; 3 | import java.util.Arrays; 4 | 5 | 6 | public class ByteString { 7 | private byte[] bytes; 8 | 9 | /** 10 | * 11 | */ 12 | public ByteString() { 13 | bytes = new byte[]{}; 14 | } 15 | 16 | /** 17 | * 18 | * @param bytes byte[] 19 | */ 20 | public ByteString(byte[] bytes) { 21 | this.bytes = bytes; 22 | } 23 | 24 | /** 25 | * 26 | * @param index index 27 | * @return short 28 | */ 29 | public short getShort(int index) { 30 | return (short) (bytes[index] & 0xFF); 31 | } 32 | 33 | /** 34 | * 35 | * @param start start 36 | * @return ByteString 37 | */ 38 | public ByteString substring(int start) { 39 | return substring(start, bytes.length); 40 | } 41 | 42 | /** 43 | * 44 | * @param start start 45 | * @param end end 46 | * @return ByteString 47 | */ 48 | public ByteString substring(int start, int end) { 49 | if (end > bytes.length) { 50 | end = bytes.length; 51 | } 52 | if (end < 0) { 53 | end = bytes.length - end; 54 | } 55 | if (start > end) { 56 | return new ByteString(); 57 | } 58 | 59 | return new ByteString(Arrays.copyOfRange(bytes, start, end)); 60 | } 61 | 62 | /** 63 | * 64 | * @return byte[] 65 | */ 66 | public byte[] getBytes() { 67 | return Arrays.copyOf(bytes, bytes.length); 68 | } 69 | 70 | /** 71 | * 72 | * @return int 73 | */ 74 | public int length() { 75 | return bytes.length; 76 | } 77 | 78 | /** 79 | * 80 | * @return boolean 81 | */ 82 | public boolean isEmpty() { 83 | return bytes.length == 0; 84 | } 85 | 86 | /** 87 | * 88 | * @param b b 89 | */ 90 | public void insert(byte[] b) { 91 | this.insert(bytes.length, b); 92 | } 93 | 94 | /** 95 | * 96 | * @param index index 97 | * @param b b 98 | */ 99 | public void insert(int index, byte[] b) { 100 | byte[] result = new byte[b.length + bytes.length]; 101 | System.arraycopy(bytes, 0, result, 0, index); 102 | System.arraycopy(b, 0, result, index, b.length); 103 | if (index < bytes.length) { 104 | System.arraycopy(bytes, index, result, b.length + index, bytes.length - index); 105 | } 106 | this.bytes = result; 107 | } 108 | 109 | /** 110 | * 111 | * @param index index 112 | * @param value value 113 | */ 114 | public void replace(int index, byte value) { 115 | bytes[index] = value; 116 | } 117 | 118 | /** 119 | * 120 | * @return string 121 | */ 122 | @Override 123 | public String toString() { 124 | if (bytes.length == 0) { 125 | return ""; 126 | } 127 | try { 128 | return new String(bytes, "ASCII"); 129 | } catch (UnsupportedEncodingException e) { 130 | throw new RuntimeException(); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/com/starkbank/ellipticcurve/utils/Der.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve.utils; 2 | 3 | import java.io.IOException; 4 | import java.math.BigInteger; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import static com.starkbank.ellipticcurve.utils.BinaryAscii.*; 9 | 10 | 11 | public class Der { 12 | 13 | private Der() { 14 | throw new UnsupportedOperationException("Der is a utility class and cannot be instantiated"); 15 | } 16 | 17 | /** 18 | * 19 | * @param encodedPieces encodedPieces 20 | * @return ByteString 21 | */ 22 | public static ByteString encodeSequence(ByteString... encodedPieces) { 23 | int totalLen = 0; 24 | ByteString stringPieces = new ByteString(toBytes(0x30)); 25 | for (ByteString p : encodedPieces) { 26 | totalLen += p.length(); 27 | stringPieces.insert(p.getBytes()); 28 | } 29 | stringPieces.insert(1, encodeLength(totalLen).getBytes()); 30 | return stringPieces; 31 | } 32 | 33 | /** 34 | * 35 | * @param length length 36 | * @return ByteString 37 | */ 38 | public static ByteString encodeLength(int length) { 39 | assert length >= 0; 40 | if (length < 0x80) { 41 | return new ByteString(toBytes(length)); 42 | } 43 | String hexString = String.format("%x", length); 44 | if (hexString.length() % 2 != 0) { 45 | hexString = "0" + hexString; 46 | } 47 | ByteString s = new ByteString(binaryFromHex(hexString)); 48 | s.insert(0, toBytes((0x80 | s.length()))); 49 | return s; 50 | 51 | } 52 | 53 | /** 54 | * 55 | * @param r r 56 | * @return ByteString 57 | */ 58 | public static ByteString encodeInteger(BigInteger r) { 59 | assert r.compareTo(BigInteger.ZERO) >= 0; 60 | String h = String.format("%x", r); 61 | if (h.length() % 2 != 0) { 62 | h = "0" + h; 63 | } 64 | ByteString s = new ByteString(binaryFromHex(h)); 65 | short num = s.getShort(0); 66 | if (num <= 0x7F) { 67 | s.insert(0, toBytes(s.length())); 68 | s.insert(0, toBytes(0x02)); 69 | return s; 70 | } 71 | int length = s.length(); 72 | s.insert(0, toBytes(0x00)); 73 | s.insert(0, toBytes((length + 1))); 74 | s.insert(0, toBytes(0x02)); 75 | return s; 76 | } 77 | 78 | /** 79 | * 80 | * @param n n 81 | * @return ByteString 82 | */ 83 | public static ByteString encodeNumber(long n) { 84 | ByteString b128Digits = new ByteString(); 85 | while (n != 0) { 86 | b128Digits.insert(0, toBytes((int) (n & 0x7f) | 0x80)); 87 | n = n >> 7; 88 | } 89 | if (b128Digits.isEmpty()) { 90 | b128Digits.insert(toBytes(0)); 91 | } 92 | int lastIndex = b128Digits.length() - 1; 93 | b128Digits.replace(lastIndex, (byte) (b128Digits.getShort(lastIndex) & 0x7f)); 94 | return b128Digits; 95 | } 96 | 97 | /** 98 | * 99 | * @param pieces pieces 100 | * @return ByteString 101 | */ 102 | public static ByteString encodeOid(long... pieces) { 103 | long first = pieces[0]; 104 | long second = pieces[1]; 105 | assert first <= 2; 106 | assert second <= 39; 107 | ByteString body = new ByteString(); 108 | for (int i = 2; i < pieces.length; i++) { 109 | body.insert(encodeNumber(pieces[i]).getBytes()); 110 | } 111 | body.insert(0, toBytes((int) (40 * first + second))); 112 | body.insert(0, encodeLength(body.length()).getBytes()); 113 | body.insert(0, toBytes(0x06)); 114 | return body; 115 | } 116 | 117 | /** 118 | * 119 | * @param s s 120 | * @return ByteString 121 | */ 122 | public static ByteString encodeBitString(ByteString s) { 123 | s.insert(0, encodeLength(s.length()).getBytes()); 124 | s.insert(0, toBytes(0x03)); 125 | return s; 126 | } 127 | 128 | /** 129 | * 130 | * @param s s 131 | * @return ByteString 132 | */ 133 | public static ByteString encodeOctetString(ByteString s) { 134 | s.insert(0, encodeLength(s.length()).getBytes()); 135 | s.insert(0, toBytes(0x04)); 136 | return s; 137 | } 138 | 139 | /** 140 | * 141 | * @param tag tag 142 | * @param value value 143 | * @return ByteString 144 | */ 145 | public static ByteString encodeConstructed(long tag, ByteString value) { 146 | value.insert(0, encodeLength(value.length()).getBytes()); 147 | value.insert(0, toBytes((int) (0xa0 + tag))); 148 | return value; 149 | } 150 | 151 | /** 152 | * 153 | * @param string string 154 | * @return int[] 155 | */ 156 | public static int[] readLength(ByteString string) { 157 | short num = string.getShort(0); 158 | if ((num & 0x80) == 0) { 159 | return new int[]{num & 0x7f, 1}; 160 | } 161 | 162 | int llen = num & 0x7f; 163 | if (llen > string.length() - 1) { 164 | throw new RuntimeException("ran out of length bytes"); 165 | } 166 | return new int[]{Integer.valueOf(hexFromBinary(string.substring(1, 1 + llen)), 16), 1 + llen}; 167 | } 168 | 169 | /** 170 | * 171 | * @param string string 172 | * @return int[] 173 | */ 174 | public static int[] readNumber(ByteString string) { 175 | int number = 0; 176 | int llen = 0; 177 | for (; ; ) { 178 | if (llen > string.length()) { 179 | throw new RuntimeException("ran out of length bytes"); 180 | } 181 | number = number << 7; 182 | short d = string.getShort(llen); 183 | number += (d & 0x7f); 184 | llen += 1; 185 | if ((d & 0x80) == 0) 186 | break; 187 | } 188 | return new int[]{number, llen}; 189 | } 190 | 191 | /** 192 | * 193 | * @param string string 194 | * @return ByteString[] 195 | */ 196 | public static ByteString[] removeSequence(ByteString string) { 197 | short n = string.getShort(0); 198 | if (n != 0x30) { 199 | throw new RuntimeException(String.format("wanted sequence (0x30), got 0x%02x", n)); 200 | } 201 | int[] l = readLength(string.substring(1)); 202 | long endseq = 1 + l[0] + l[1]; 203 | return new ByteString[]{string.substring(1 + l[1], (int) endseq), string.substring((int) endseq)}; 204 | } 205 | 206 | /** 207 | * 208 | * @param string string 209 | * @return Object[] 210 | */ 211 | public static Object[] removeInteger(ByteString string) { 212 | short n = string.getShort(0); 213 | if (n != 0x02) { 214 | throw new RuntimeException(String.format("wanted integer (0x02), got 0x%02x", n)); 215 | } 216 | int[] l = readLength(string.substring(1)); 217 | int length = l[0]; 218 | int llen = l[1]; 219 | ByteString numberbytes = string.substring(1 + llen, 1 + llen + length); 220 | ByteString rest = string.substring(1 + llen + length); 221 | short nbytes = numberbytes.getShort(0); 222 | assert nbytes < 0x80; 223 | return new Object[]{new BigInteger(hexFromBinary(numberbytes), 16), rest}; 224 | } 225 | 226 | /** 227 | * 228 | * @param string string 229 | * @return Object[] 230 | */ 231 | public static Object[] removeObject(ByteString string) { 232 | int n = string.getShort(0); 233 | if (n != 0x06) { 234 | throw new RuntimeException(String.format("wanted object (0x06), got 0x%02x", n)); 235 | } 236 | int[] l = readLength(string.substring(1)); 237 | int length = l[0]; 238 | int lengthlength = l[1]; 239 | ByteString body = string.substring(1 + lengthlength, 1 + lengthlength + length); 240 | ByteString rest = string.substring(1 + lengthlength + length); 241 | List numbers = new ArrayList(); 242 | while (!body.isEmpty()) { 243 | l = readNumber(body); 244 | n = l[0]; 245 | int ll = l[1]; 246 | numbers.add(n); 247 | body = body.substring(ll); 248 | } 249 | long n0 = Integer.valueOf(numbers.remove(0).toString()); 250 | long first = n0 / 40; 251 | long second = n0 - (40 * first); 252 | numbers.add(0, first); 253 | numbers.add(1, second); 254 | long[] numbersArray = new long[numbers.size()]; 255 | for (int i = 0; i < numbers.size(); i++) { 256 | numbersArray[i] = Long.valueOf(numbers.get(i).toString()); 257 | } 258 | return new Object[]{numbersArray, rest}; 259 | } 260 | 261 | /** 262 | * 263 | * @param string string 264 | * @return ByteString 265 | */ 266 | public static ByteString[] removeBitString(ByteString string) { 267 | short n = string.getShort(0); 268 | if (n != 0x03) { 269 | throw new RuntimeException(String.format("wanted bitstring (0x03), got 0x%02x", n)); 270 | } 271 | int[] l = readLength(string.substring(1)); 272 | int length = l[0]; 273 | int llen = l[1]; 274 | ByteString body = string.substring(1 + llen, 1 + llen + length); 275 | ByteString rest = string.substring(1 + llen + length); 276 | return new ByteString[]{body, rest}; 277 | } 278 | 279 | /** 280 | * 281 | * @param string string 282 | * @return ByteString[] 283 | */ 284 | public static ByteString[] removeOctetString(ByteString string) { 285 | short n = string.getShort(0); 286 | if (n != 0x04) { 287 | throw new RuntimeException(String.format("wanted octetstring (0x04), got 0x%02x", n)); 288 | } 289 | int[] l = readLength(string.substring(1)); 290 | int length = l[0]; 291 | int llen = l[1]; 292 | ByteString body = string.substring(1 + llen, 1 + llen + length); 293 | ByteString rest = string.substring(1 + llen + length); 294 | return new ByteString[]{body, rest}; 295 | } 296 | 297 | /** 298 | * 299 | * @param string string 300 | * @return Object[] 301 | */ 302 | public static Object[] removeConstructed(ByteString string) { 303 | short s0 = string.getShort(0); 304 | if ((s0 & 0xe0) != 0xa0) { 305 | throw new RuntimeException(String.format("wanted constructed tag (0xa0-0xbf), got 0x%02x", s0)); 306 | } 307 | int tag = s0 & 0x1f; 308 | int[] l = readLength(string.substring(1)); 309 | int length = l[0]; 310 | int llen = l[1]; 311 | ByteString body = string.substring(1 + llen, 1 + llen + length); 312 | ByteString rest = string.substring(1 + llen + length); 313 | return new Object[]{tag, body, rest}; 314 | } 315 | 316 | /** 317 | * 318 | * @param pem pem 319 | * @return ByteString 320 | */ 321 | public static ByteString fromPem(String pem) { 322 | String[] pieces = pem.split("\n"); 323 | StringBuilder d = new StringBuilder(); 324 | for (String p : pieces) { 325 | if (!p.isEmpty() && !p.startsWith("-----")) { 326 | d.append(p.trim()); 327 | } 328 | } 329 | try { 330 | return new ByteString(Base64.decode(d.toString())); 331 | } catch (IOException e) { 332 | throw new IllegalArgumentException("Corrupted pem string! Could not decode base64 from it"); 333 | } 334 | } 335 | 336 | /** 337 | * 338 | * @param der der 339 | * @param name name 340 | * @return String 341 | */ 342 | public static String toPem(ByteString der, String name) { 343 | String b64 = Base64.encodeBytes(der.getBytes()); 344 | StringBuilder lines = new StringBuilder(); 345 | lines.append(String.format("-----BEGIN %s-----\n", name)); 346 | for (int start = 0; start < b64.length(); start += 64) { 347 | int end = start + 64 > b64.length() ? b64.length() : start + 64; 348 | lines.append(String.format("%s\n", b64.substring(start, end))); 349 | } 350 | lines.append(String.format("-----END %s-----\n", name)); 351 | return lines.toString(); 352 | } 353 | } -------------------------------------------------------------------------------- /src/main/java/com/starkbank/ellipticcurve/utils/File.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve.utils; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.*; 5 | 6 | /* 7 | *This class handles the fileinput types as filename string 8 | *If using bytearray file as "signatureBinary.txt" 9 | *Use the readByte method 10 | **/ 11 | public class File { 12 | 13 | /** 14 | * 15 | * @param fileName fileName 16 | * @return String 17 | */ 18 | public static String read(String fileName) 19 | { 20 | String content = ""; 21 | try 22 | { 23 | content = new String(Files.readAllBytes(Paths.get(fileName))); 24 | } 25 | catch (IOException e) 26 | { 27 | e.printStackTrace(); 28 | } 29 | return content; 30 | } 31 | 32 | /** 33 | * 34 | * @param fileName fileName 35 | * @return byte[] 36 | */ 37 | public static byte[] readBytes(String fileName) 38 | { 39 | byte[] content = null; 40 | try 41 | { 42 | content = Files.readAllBytes(Paths.get(fileName)); 43 | } 44 | catch (IOException e) 45 | { 46 | e.printStackTrace(); 47 | } 48 | 49 | return content; 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /src/main/java/com/starkbank/ellipticcurve/utils/RandomInteger.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve.utils; 2 | import java.math.BigInteger; 3 | import java.security.SecureRandom; 4 | import java.util.Random; 5 | 6 | 7 | public class RandomInteger { 8 | 9 | /** 10 | * 11 | * @param start start 12 | * @param end end 13 | * @return BigInteger 14 | */ 15 | public static BigInteger between(BigInteger start, BigInteger end) { 16 | Random random = new SecureRandom(); 17 | return new BigInteger(end.toByteArray().length * 8 - 1, random).abs().add(start); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/starkbank/ellipticcurve/EcdsaTest.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve; 2 | import org.junit.Test; 3 | 4 | import java.math.BigInteger; 5 | 6 | import static org.junit.Assert.assertFalse; 7 | import static org.junit.Assert.assertTrue; 8 | 9 | 10 | public class EcdsaTest { 11 | 12 | @Test 13 | public void testVerifyRightMessage() { 14 | PrivateKey privateKey = new PrivateKey(); 15 | PublicKey publicKey = privateKey.publicKey(); 16 | 17 | String message = "This is the right message"; 18 | 19 | Signature signature = Ecdsa.sign(message, privateKey); 20 | assertTrue(Ecdsa.verify(message, signature, publicKey)); 21 | } 22 | 23 | @Test 24 | public void testVerifyWrongMessage() { 25 | PrivateKey privateKey = new PrivateKey(); 26 | PublicKey publicKey = privateKey.publicKey(); 27 | 28 | String message1 = "This is the right message"; 29 | String message2 = "This is the wrong message"; 30 | 31 | Signature signature = Ecdsa.sign(message1, privateKey); 32 | 33 | assertFalse(Ecdsa.verify(message2, signature, publicKey)); 34 | } 35 | 36 | @Test 37 | public void testZeroSignature() { 38 | PrivateKey privateKey = new PrivateKey(); 39 | PublicKey publicKey = privateKey.publicKey(); 40 | 41 | String message = "This is the right message"; 42 | 43 | assertFalse(Ecdsa.verify(message, new Signature(BigInteger.ZERO, BigInteger.ZERO), publicKey)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/starkbank/ellipticcurve/OpenSSLTest.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve; 2 | import com.starkbank.ellipticcurve.utils.ByteString; 3 | import org.junit.Test; 4 | import java.io.IOException; 5 | import java.net.URISyntaxException; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | 9 | public class OpenSSLTest { 10 | 11 | @Test 12 | public void testAssign() throws URISyntaxException, IOException { 13 | // Generated by:openssl ecparam -name secp256k1 - genkey - out privateKey.pem 14 | String privateKeyPem = Utils.readFileAsString("privateKey.pem"); 15 | 16 | PrivateKey privateKey = PrivateKey.fromPem(privateKeyPem); 17 | 18 | String message = Utils.readFileAsString("message.txt"); 19 | 20 | Signature signature = Ecdsa.sign(message, privateKey); 21 | 22 | PublicKey publicKey = privateKey.publicKey(); 23 | 24 | assertTrue(Ecdsa.verify(message, signature, publicKey)); 25 | } 26 | 27 | @Test 28 | public void testVerifySignature() throws IOException, URISyntaxException { 29 | // openssl ec -in privateKey.pem - pubout - out publicKey.pem 30 | String publicKeyPem = Utils.readFileAsString("publicKey.pem"); 31 | // openssl dgst -sha256 -sign privateKey.pem -out signature.binary message.txt 32 | ByteString signatureBin = new ByteString(Utils.readFileAsBytes("signature.binary")); 33 | 34 | String message = Utils.readFileAsString("message.txt"); 35 | 36 | PublicKey publicKey = PublicKey.fromPem(publicKeyPem); 37 | 38 | Signature signature = Signature.fromDer(signatureBin); 39 | 40 | assertTrue(Ecdsa.verify(message, signature, publicKey)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/com/starkbank/ellipticcurve/PrivateKeyTest.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve; 2 | import com.starkbank.ellipticcurve.utils.ByteString; 3 | import org.junit.Test; 4 | import static org.junit.Assert.assertEquals; 5 | 6 | 7 | public class PrivateKeyTest { 8 | 9 | @Test 10 | public void testPemConversion() { 11 | PrivateKey privateKey1 = new PrivateKey(); 12 | String pem = privateKey1.toPem(); 13 | PrivateKey privateKey2 = PrivateKey.fromPem(pem); 14 | assertEquals(privateKey1.secret, privateKey2.secret); 15 | assertEquals(privateKey1.curve, privateKey2.curve); 16 | } 17 | 18 | @Test 19 | public void testDerConversion() { 20 | PrivateKey privateKey1 = new PrivateKey(); 21 | ByteString der = privateKey1.toDer(); 22 | PrivateKey privateKey2 = PrivateKey.fromDer(der); 23 | assertEquals(privateKey1.secret, privateKey2.secret); 24 | assertEquals(privateKey1.curve, privateKey2.curve); 25 | } 26 | 27 | @Test 28 | public void testStringConversion() { 29 | PrivateKey privateKey1 = new PrivateKey(); 30 | ByteString string = privateKey1.toByteString(); 31 | PrivateKey privateKey2 = PrivateKey.fromString(string); 32 | assertEquals(privateKey1.secret, privateKey2.secret); 33 | assertEquals(privateKey1.curve, privateKey2.curve); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/starkbank/ellipticcurve/PublicKeyTest.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve; 2 | import com.starkbank.ellipticcurve.utils.ByteString; 3 | import org.junit.Test; 4 | import static org.junit.Assert.assertEquals; 5 | 6 | 7 | public class PublicKeyTest { 8 | 9 | @Test 10 | public void testPemConversion() { 11 | PrivateKey privateKey = new PrivateKey(); 12 | PublicKey publicKey1 = privateKey.publicKey(); 13 | String pem = publicKey1.toPem(); 14 | PublicKey publicKey2 = PublicKey.fromPem(pem); 15 | assertEquals(publicKey1.point.x, publicKey2.point.x); 16 | assertEquals(publicKey1.point.y, publicKey2.point.y); 17 | assertEquals(publicKey1.curve, publicKey2.curve); 18 | } 19 | 20 | @Test 21 | public void testDerConversion() { 22 | PrivateKey privateKey = new PrivateKey(); 23 | PublicKey publicKey1 = privateKey.publicKey(); 24 | ByteString der = publicKey1.toDer(); 25 | PublicKey publicKey2 = PublicKey.fromDer(der); 26 | assertEquals(publicKey1.point.x, publicKey2.point.x); 27 | assertEquals(publicKey1.point.y, publicKey2.point.y); 28 | assertEquals(publicKey1.curve, publicKey2.curve); 29 | } 30 | 31 | @Test 32 | public void testStringConversion() { 33 | PrivateKey privateKey = new PrivateKey(); 34 | PublicKey publicKey1 = privateKey.publicKey(); 35 | ByteString string = publicKey1.toByteString(); 36 | PublicKey publicKey2 = PublicKey.fromString(string); 37 | assertEquals(publicKey1.point.x, publicKey2.point.x); 38 | assertEquals(publicKey1.point.y, publicKey2.point.y); 39 | assertEquals(publicKey1.curve, publicKey2.curve); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/starkbank/ellipticcurve/SignatureTest.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve; 2 | import com.starkbank.ellipticcurve.utils.ByteString; 3 | import org.junit.Test; 4 | import static org.junit.Assert.assertEquals; 5 | 6 | 7 | public class SignatureTest { 8 | 9 | @Test 10 | public void testDerConversion() { 11 | PrivateKey privateKey = new PrivateKey(); 12 | String message = "This is a text message"; 13 | 14 | Signature signature1 = Ecdsa.sign(message, privateKey); 15 | 16 | ByteString der = signature1.toDer(); 17 | 18 | Signature signature2 = Signature.fromDer(der); 19 | 20 | assertEquals(signature1.r, signature2.r); 21 | assertEquals(signature1.s, signature2.s); 22 | } 23 | 24 | @Test 25 | public void testBase64Conversion() { 26 | PrivateKey privateKey = new PrivateKey(); 27 | String message = "This is a text message"; 28 | 29 | Signature signature1 = Ecdsa.sign(message, privateKey); 30 | 31 | String base64 = signature1.toBase64(); 32 | 33 | Signature signature2 = Signature.fromBase64(new ByteString(base64.getBytes())); 34 | 35 | assertEquals(signature1.r, signature2.r); 36 | assertEquals(signature1.s, signature2.s); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/starkbank/ellipticcurve/Utils.java: -------------------------------------------------------------------------------- 1 | package com.starkbank.ellipticcurve; 2 | import java.io.IOException; 3 | import java.io.RandomAccessFile; 4 | import java.net.URISyntaxException; 5 | 6 | 7 | class Utils { 8 | 9 | static String readFileAsString(String path) throws URISyntaxException, IOException { 10 | return new String(readFileAsBytes(path), "ASCII"); 11 | } 12 | 13 | static byte[] readFileAsBytes(String path) throws URISyntaxException { 14 | return read(ClassLoader.getSystemClassLoader().getResource(path).toURI().getPath()); 15 | } 16 | 17 | private static byte[] read(String path) { 18 | try { 19 | RandomAccessFile f = new RandomAccessFile(path, "r"); 20 | if (f.length() > Integer.MAX_VALUE) 21 | throw new RuntimeException("File is too large"); 22 | byte[] b = new byte[(int) f.length()]; 23 | f.readFully(b); 24 | if (f.getFilePointer() != f.length()) 25 | throw new RuntimeException("File length changed while reading"); 26 | return b; 27 | } catch (IOException e) { 28 | throw new RuntimeException("Could not read file"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/resources/message.txt: -------------------------------------------------------------------------------- 1 | { 2 | "transaction": { 3 | "amount": 50000, 4 | "receiver": "", 5 | "description": "Sample transfer between workspaces", 6 | "externalId": "123456", 7 | "tags": ["john", "smith"] 8 | } 9 | } -------------------------------------------------------------------------------- /src/test/resources/privateKey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BgUrgQQACg== 3 | -----END EC PARAMETERS----- 4 | -----BEGIN EC PRIVATE KEY----- 5 | MHQCAQEEIODvZuS34wFbt0X53+P5EnSj6tMjfVK01dD1dgDH02RzoAcGBSuBBAAK 6 | oUQDQgAE/nvHu/SQQaos9TUljQsUuKI15Zr5SabPrbwtbfT/408rkVVzq8vAisbB 7 | RmpeRREXj5aog/Mq8RrdYy75W9q/Ig== 8 | -----END EC PRIVATE KEY----- 9 | -------------------------------------------------------------------------------- /src/test/resources/publicKey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE/nvHu/SQQaos9TUljQsUuKI15Zr5SabP 3 | rbwtbfT/408rkVVzq8vAisbBRmpeRREXj5aog/Mq8RrdYy75W9q/Ig== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /src/test/resources/signature.binary: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starkbank/ecdsa-java/1a133705dfa47090914658a8f90b2101c7e62092/src/test/resources/signature.binary -------------------------------------------------------------------------------- /src/test/resources/signatureBinary.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starkbank/ecdsa-java/1a133705dfa47090914658a8f90b2101c7e62092/src/test/resources/signatureBinary.txt --------------------------------------------------------------------------------