├── .gitignore ├── .idea └── libraries │ ├── Maven__junit_junit_maven_plugin_4_11.xml │ ├── Maven__org_hamcrest_hamcrest_all_1_3.xml │ └── Maven__org_hamcrest_hamcrest_core_1_3.xml ├── .travis.yml ├── LICENSE.txt ├── Literature ├── A complete set of addition laws for incomplete Edwards curves.pdf ├── Curves, Codes, and Cryptography.pdf ├── EdDSA signatures and Ed25519.pdf ├── High-speed high-security signatures.pdf ├── NaCl on 8-bit AVR Microcontrollers.pdf ├── Optimizing double-base elliptic-curve-single-scalar multiplication.pdf ├── Scalar-multiplication algorithms.pdf ├── Twisted Edwards Curves Revisited.pdf └── Twisted Edwards Curves.pdf ├── README.md ├── eddsa.iml ├── jitpack.yml ├── pom.xml ├── src └── net │ └── i2p │ └── crypto │ └── eddsa │ ├── EdDSAEngine.java │ ├── EdDSAKey.java │ ├── EdDSAPrivateKey.java │ ├── EdDSAPublicKey.java │ ├── EdDSASecurityProvider.java │ ├── KeyFactory.java │ ├── KeyPairGenerator.java │ ├── Utils.java │ ├── math │ ├── Constants.java │ ├── Curve.java │ ├── Encoding.java │ ├── Field.java │ ├── FieldElement.java │ ├── GroupElement.java │ ├── ScalarOps.java │ ├── bigint │ │ ├── BigIntegerFieldElement.java │ │ ├── BigIntegerLittleEndianEncoding.java │ │ ├── BigIntegerScalarOps.java │ │ └── package.html │ ├── ed25519 │ │ ├── Ed25519FieldElement.java │ │ ├── Ed25519LittleEndianEncoding.java │ │ ├── Ed25519ScalarOps.java │ │ └── package.html │ └── package.html │ ├── package.html │ └── spec │ ├── EdDSAGenParameterSpec.java │ ├── EdDSANamedCurveSpec.java │ ├── EdDSANamedCurveTable.java │ ├── EdDSAParameterSpec.java │ ├── EdDSAPrivateKeySpec.java │ ├── EdDSAPublicKeySpec.java │ └── package.html └── test └── net └── i2p └── crypto └── eddsa ├── Ed25519TestVectors.java ├── EdDSAEngineTest.java ├── EdDSAPrivateKeyTest.java ├── EdDSAPublicKeyTest.java ├── EdDSASecurityProviderTest.java ├── UtilsTest.java ├── math ├── AbstractFieldElementTest.java ├── ConstantsTest.java ├── GroupElementTest.java ├── MathUtils.java ├── PrecomputationTestVectors.java ├── baseDblPrecmp ├── basePrecmp ├── bigint │ ├── BigIntegerFieldElementTest.java │ └── BigIntegerScalarOpsTest.java └── ed25519 │ ├── Ed25519FieldElementTest.java │ ├── Ed25519LittleEndianEncodingTest.java │ └── Ed25519ScalarOpsTest.java ├── spec ├── EdDSANamedCurveTableTest.java └── EdDSAPrivateKeySpecTest.java └── test.data /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /target/ 3 | /.settings/ 4 | *.class 5 | *.iml 6 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__junit_junit_maven_plugin_4_11.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_hamcrest_hamcrest_all_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | dist: trusty 3 | 4 | jdk: 5 | - oraclejdk11 6 | - oraclejdk9 7 | - oraclejdk8 8 | - openjdk11 9 | - openjdk10 10 | - openjdk9 11 | - openjdk8 12 | 13 | matrix: 14 | include: 15 | - jdk: openjdk7 16 | install: 17 | - mvn test -DskipTests=true -B -V 18 | - jdk_switcher use oraclejdk8 19 | - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true -B -V 20 | - jdk_switcher use $TRAVIS_JDK_VERSION 21 | 22 | install: 23 | - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true -B -V 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | 123 | For more information, please see https://creativecommons.org/publicdomain/zero/1.0/ 124 | -------------------------------------------------------------------------------- /Literature/A complete set of addition laws for incomplete Edwards curves.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/str4d/ed25519-java/e0ac35769db8553fb714b09f0d3f3d2b001fd033/Literature/A complete set of addition laws for incomplete Edwards curves.pdf -------------------------------------------------------------------------------- /Literature/Curves, Codes, and Cryptography.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/str4d/ed25519-java/e0ac35769db8553fb714b09f0d3f3d2b001fd033/Literature/Curves, Codes, and Cryptography.pdf -------------------------------------------------------------------------------- /Literature/EdDSA signatures and Ed25519.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/str4d/ed25519-java/e0ac35769db8553fb714b09f0d3f3d2b001fd033/Literature/EdDSA signatures and Ed25519.pdf -------------------------------------------------------------------------------- /Literature/High-speed high-security signatures.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/str4d/ed25519-java/e0ac35769db8553fb714b09f0d3f3d2b001fd033/Literature/High-speed high-security signatures.pdf -------------------------------------------------------------------------------- /Literature/NaCl on 8-bit AVR Microcontrollers.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/str4d/ed25519-java/e0ac35769db8553fb714b09f0d3f3d2b001fd033/Literature/NaCl on 8-bit AVR Microcontrollers.pdf -------------------------------------------------------------------------------- /Literature/Optimizing double-base elliptic-curve-single-scalar multiplication.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/str4d/ed25519-java/e0ac35769db8553fb714b09f0d3f3d2b001fd033/Literature/Optimizing double-base elliptic-curve-single-scalar multiplication.pdf -------------------------------------------------------------------------------- /Literature/Scalar-multiplication algorithms.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/str4d/ed25519-java/e0ac35769db8553fb714b09f0d3f3d2b001fd033/Literature/Scalar-multiplication algorithms.pdf -------------------------------------------------------------------------------- /Literature/Twisted Edwards Curves Revisited.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/str4d/ed25519-java/e0ac35769db8553fb714b09f0d3f3d2b001fd033/Literature/Twisted Edwards Curves Revisited.pdf -------------------------------------------------------------------------------- /Literature/Twisted Edwards Curves.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/str4d/ed25519-java/e0ac35769db8553fb714b09f0d3f3d2b001fd033/Literature/Twisted Edwards Curves.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EdDSA-Java 2 | ========== 3 | 4 | [![Build Status](https://travis-ci.org/str4d/ed25519-java.svg?branch=master)](https://travis-ci.org/str4d/ed25519-java) 5 | 6 | This is an implementation of EdDSA in Java. Structurally, it is based on the ref10 implementation in SUPERCOP 7 | (see https://ed25519.cr.yp.to/software.html). 8 | 9 | There are two internal implementations: 10 | - A port of the radix-2^51 operations in ref10 - fast and constant-time, but only useful for Ed25519. 11 | - A generic version using BigIntegers for calculation - a bit slower and not constant-time, but compatible 12 | with any EdDSA parameter specification. 13 | 14 | 15 | To use 16 | ------ 17 | 18 | Download the latest .jar from the releases tab and place it in your classpath. 19 | 20 | Gradle users: 21 | 22 | ``` 23 | compile 'net.i2p.crypto:eddsa:0.3.0' 24 | ``` 25 | 26 | Java 7 and above are supported. 27 | 28 | The JUnit4 tests require the Hamcrest library `hamcrest-all.jar`. 29 | 30 | This code is released to the public domain and can be used for any purpose. See `LICENSE.txt` for details. 31 | 32 | Disclaimer 33 | ---------- 34 | 35 | There are **no** guarantees that this is secure for all cases, and users should 36 | review the code themselves before depending on it. PRs that fix bugs or improve 37 | reviewability are very welcome. Additionally: 38 | 39 | - The unit test suite includes tests against 40 | [the data from the original Python implementation](https://ed25519.cr.yp.to/python/sign.input). 41 | - The code (as of 97cea3f0d910fc627c7b57b1bc4d783cdd0c2a4a) was reviewed by 42 | [an independent developer](https://github.com/BloodyRookie). 43 | - The code (as of dc9f58f2c874463c15465326efc040d17a627b3a) was audited by an independent third party, 44 | and the one issue found [was fixed](https://github.com/str4d/ed25519-java/pull/31). 45 | 46 | Code comparison 47 | --------------- 48 | 49 | For ease of following, here are the main methods in ref10 and their equivalents in this codebase: 50 | 51 | | EdDSA Operation | ref10 function | Java function | 52 | | --------------- | -------------- | ------------- | 53 | | Generate keypair | `crypto_sign_keypair` | `EdDSAPrivateKeySpec` constructor | 54 | | Sign message | `crypto_sign` | `EdDSAEngine.engineSign` | 55 | | Verify signature | `crypto_sign_open` | `EdDSAEngine.engineVerify` | 56 | 57 | | EdDSA point arithmetic | ref10 function | Java function | 58 | | ---------------------- | -------------- | ------------- | 59 | | `R = b * B` | `ge_scalarmult_base` | `GroupElement.scalarMultiply` | 60 | | `R = a*A + b*B` | `ge_double_scalarmult_vartime` | `GroupElement.doubleScalarMultiplyVariableTime` | 61 | | `R = 2 * P` | `ge_p2_dbl` | `GroupElement.dbl` | 62 | | `R = P + Q` | `ge_madd`, `ge_add` | `GroupElement.madd`, `GroupElement.add` | 63 | | `R = P - Q` | `ge_msub`, `ge_sub` | `GroupElement.msub`, `GroupElement.sub` | 64 | 65 | 66 | Important changes 67 | ----------------- 68 | 69 | ### 0.3.0 70 | 71 | - The library has been extensively profiled for contention issues in a multi-threaded environment. The only 72 | remaining potential contention is in `EdDSANamedCurveTable.defineCurve()`, which will be rarely called. 73 | - The public constant for the curve name has returned as `ED_25519`, and the curve specification has a public 74 | constant `ED_25519_CURVE_SPEC` to avoid repeated lookups when converting to and from encoded form for the 75 | public or private keys. 76 | - `GroupElement` is now completely immutable, and all fields final, to avoid the need for `synchronized` 77 | blocks over mutable fields. This required some new constructors and paths to construction. 78 | - `EdDSAPublicKeySpec.getNegativeA()` and `EdDSAPublicKey.getNegativeA()` now evaluate lazily, taking 79 | advantage of the immutability of `GroupElement.negate()`. This boosts the performance of the public key 80 | constructor when the key is just being passed around rather than used. 81 | - Support for X509Key wrapped EdDSA public keys. 82 | 83 | ### 0.2.0 84 | 85 | - Ed25519 is now named `Ed25519` in `EdDSANamedCurveTable`, and the previous public constant (containing the 86 | older inaccurate name) has been removed. 87 | 88 | Credits 89 | ------- 90 | 91 | - The Ed25519 class was originally ported by k3d3 from 92 | [the Python Ed25519 reference implementation](https://ed25519.cr.yp.to/python/ed25519.py). 93 | - Useful comments and tweaks were found in 94 | [the GNUnet implementation of Ed25519](https://gnunet.org/svn/gnunet-java/src/main/java/org/gnunet/util/crypto/) 95 | (based on k3d3's class). 96 | - [BloodyRookie](https://github.com/BloodyRookie) reviewed the code, adding many useful comments, unit tests 97 | and literature. 98 | -------------------------------------------------------------------------------- /eddsa.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - oraclejdk8 3 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | net.i2p.crypto 5 | eddsa 6 | 0.3.0 7 | EdDSA-Java 8 | bundle 9 | Implementation of EdDSA in Java 10 | https://github.com/str4d/ed25519-java 11 | 12 | UTF-8 13 | 14 | 15 | 16 | CC0 1.0 Universal 17 | https://creativecommons.org/publicdomain/zero/1.0/ 18 | repo 19 | 20 | 21 | 22 | 23 | str4d 24 | Jack Grigg 25 | thestr4d@gmail.com 26 | 27 | 28 | 29 | scm:git:git://github.com/str4d/ed25519-java.git 30 | scm:git:git@github.com:str4d/ed25519-java.git 31 | https://github.com/str4d/ed25519-java 32 | 33 | 34 | 35 | ossrh 36 | https://oss.sonatype.org/content/repositories/snapshots 37 | 38 | 39 | 40 | src 41 | test 42 | 43 | 44 | test 45 | 46 | **/*.java 47 | 48 | 49 | 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-compiler-plugin 54 | 55 | 1.7 56 | 1.7 57 | ${project.build.sourceEncoding} 58 | 59 | 3.8.0 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-surefire-plugin 64 | 3.0.0-M3 65 | 66 | 67 | org.apache.felix 68 | maven-bundle-plugin 69 | 3.5.1 70 | true 71 | 72 | 73 | 74 | net.i2p.crypto.eddsa, 75 | net.i2p.crypto.eddsa.spec 76 | 77 | 78 | net.i2p.crypto.eddsa.math.* 79 | 80 | 81 | net.i2p.crypto.eddsa 82 | 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-source-plugin 89 | 3.0.1 90 | 91 | 92 | attach-sources 93 | 94 | jar-no-fork 95 | 96 | 97 | 98 | 99 | 100 | org.apache.maven.plugins 101 | maven-javadoc-plugin 102 | 3.0.1 103 | 104 | UTF-8 105 | UTF-8 106 | UTF-8 107 | --allow-script-in-comments 108 |
<script type='text/x-mathjax-config'> 109 | MathJax.Hub.Config({ 110 | tex2jax: { 111 | inlineMath: [ ['$','$'] ], 112 | processEscapes: true 113 | } 114 | }); 115 | </script> 116 | <script type='text/javascript' src='https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'></script>
117 |
118 | 119 | 120 | attach-javadocs 121 | 122 | jar 123 | 124 | 125 | 126 |
127 | 128 | org.apache.maven.plugins 129 | maven-gpg-plugin 130 | 1.6 131 | 132 | 133 | sign-artifacts 134 | verify 135 | 136 | sign 137 | 138 | 139 | 140 | 141 | 142 | org.sonatype.plugins 143 | nexus-staging-maven-plugin 144 | 1.6.8 145 | true 146 | 147 | ossrh 148 | https://oss.sonatype.org/ 149 | true 150 | 151 | 152 |
153 |
154 | 155 | 156 | org.hamcrest 157 | hamcrest-all 158 | 1.3 159 | test 160 | 161 | 162 | junit 163 | junit 164 | 4.12 165 | maven-plugin 166 | test 167 | 168 | 169 | 170 | 171 | zzz 172 | zzz@mail.i2p 173 | 174 | 175 | mbakkar 176 | Mohamed.b.bakkar@gmail.com 177 | 178 | 179 | Philippe Marchesseault 180 | pmarches@gmail.com 181 | 182 | 183 | BloodyRookie 184 | nemproject@gmx.de 185 | 186 | 187 | Jan Willem Janssen 188 | janwillem.janssen@luminis.eu 189 | 190 | 191 | Jim Keener 192 | jim@jimkeener.com 193 | 194 | 195 | Lyor Goldstein 196 | lyor.goldstein@gmail.com 197 | 198 | 199 | Ilya Maykov 200 | ilyam@fb.com 201 | 202 | 203 | Ross Nicoll 204 | ross.nicoll@r3.com 205 | 206 | 207 | Mark Raynsford 208 | code@io7m.com 209 | 210 | 211 | rick.parker 212 | rick.parker@r3cev.com 213 | 214 | 215 | Christian Sailer 216 | christian.sailer@r3.com 217 | 218 | 219 |
220 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/EdDSAEngine.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa; 13 | 14 | import java.io.ByteArrayOutputStream; 15 | import java.nio.ByteBuffer; 16 | import java.security.InvalidAlgorithmParameterException; 17 | import java.security.InvalidKeyException; 18 | import java.security.MessageDigest; 19 | import java.security.NoSuchAlgorithmException; 20 | import java.security.PrivateKey; 21 | import java.security.PublicKey; 22 | import java.security.Signature; 23 | import java.security.SignatureException; 24 | import java.security.spec.AlgorithmParameterSpec; 25 | import java.security.spec.InvalidKeySpecException; 26 | import java.security.spec.X509EncodedKeySpec; 27 | import java.util.Arrays; 28 | 29 | import net.i2p.crypto.eddsa.math.Curve; 30 | import net.i2p.crypto.eddsa.math.GroupElement; 31 | import net.i2p.crypto.eddsa.math.ScalarOps; 32 | 33 | /** 34 | * Signing and verification for EdDSA. 35 | *

36 | * The EdDSA sign and verify algorithms do not interact well with 37 | * the Java Signature API, as one or more update() methods must be 38 | * called before sign() or verify(). Using the standard API, 39 | * this implementation must copy and buffer all data passed in 40 | * via update(). 41 | *

42 | * This implementation offers two ways to avoid this copying, 43 | * but only if all data to be signed or verified is available 44 | * in a single byte array. 45 | *

46 | *Option 1: 47 | *

    48 | *
  1. Call initSign() or initVerify() as usual. 49 | *
  2. Call setParameter(ONE_SHOT_MODE) 50 | *
  3. Call update(byte[]) or update(byte[], int, int) exactly once 51 | *
  4. Call sign() or verify() as usual. 52 | *
  5. If doing additional one-shot signs or verifies with this object, you must 53 | * call setParameter(ONE_SHOT_MODE) each time 54 | *
55 | * 56 | *

57 | *Option 2: 58 | *

    59 | *
  1. Call initSign() or initVerify() as usual. 60 | *
  2. Call one of the signOneShot() or verifyOneShot() methods. 61 | *
  3. If doing additional one-shot signs or verifies with this object, 62 | * just call signOneShot() or verifyOneShot() again. 63 | *
64 | * 65 | * @author str4d 66 | * 67 | */ 68 | public final class EdDSAEngine extends Signature { 69 | public static final String SIGNATURE_ALGORITHM = "NONEwithEdDSA"; 70 | 71 | private MessageDigest digest; 72 | private ByteArrayOutputStream baos; 73 | private EdDSAKey key; 74 | private boolean oneShotMode; 75 | private byte[] oneShotBytes; 76 | private int oneShotOffset; 77 | private int oneShotLength; 78 | 79 | /** 80 | * To efficiently sign or verify data in one shot, pass this to setParameters() 81 | * after initSign() or initVerify() but BEFORE THE FIRST AND ONLY 82 | * update(data) or update(data, off, len). The data reference will be saved 83 | * and then used in sign() or verify() without copying the data. 84 | * Violate these rules and you will get a SignatureException. 85 | */ 86 | public static final AlgorithmParameterSpec ONE_SHOT_MODE = new OneShotSpec(); 87 | 88 | private static class OneShotSpec implements AlgorithmParameterSpec {} 89 | 90 | /** 91 | * No specific EdDSA-internal hash requested, allows any EdDSA key. 92 | */ 93 | public EdDSAEngine() { 94 | super(SIGNATURE_ALGORITHM); 95 | } 96 | 97 | /** 98 | * Specific EdDSA-internal hash requested, only matching keys will be allowed. 99 | * @param digest the hash algorithm that keys must have to sign or verify. 100 | */ 101 | public EdDSAEngine(MessageDigest digest) { 102 | this(); 103 | this.digest = digest; 104 | } 105 | 106 | private void reset() { 107 | if (digest != null) 108 | digest.reset(); 109 | if (baos != null) 110 | baos.reset(); 111 | oneShotMode = false; 112 | oneShotBytes = null; 113 | } 114 | 115 | @Override 116 | protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { 117 | reset(); 118 | if (privateKey instanceof EdDSAPrivateKey) { 119 | EdDSAPrivateKey privKey = (EdDSAPrivateKey) privateKey; 120 | key = privKey; 121 | 122 | if (digest == null) { 123 | // Instantiate the digest from the key parameters 124 | try { 125 | digest = MessageDigest.getInstance(key.getParams().getHashAlgorithm()); 126 | } catch (NoSuchAlgorithmException e) { 127 | throw new InvalidKeyException("cannot get required digest " + key.getParams().getHashAlgorithm() + " for private key."); 128 | } 129 | } else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm())) 130 | throw new InvalidKeyException("Key hash algorithm does not match chosen digest"); 131 | digestInitSign(privKey); 132 | } else { 133 | throw new InvalidKeyException("cannot identify EdDSA private key: " + privateKey.getClass()); 134 | } 135 | } 136 | 137 | private void digestInitSign(EdDSAPrivateKey privKey) { 138 | // Preparing for hash 139 | // r = H(h_b,...,h_2b-1,M) 140 | int b = privKey.getParams().getCurve().getField().getb(); 141 | digest.update(privKey.getH(), b/8, b/4 - b/8); 142 | } 143 | 144 | @Override 145 | protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { 146 | reset(); 147 | if (publicKey instanceof EdDSAPublicKey) { 148 | key = (EdDSAPublicKey) publicKey; 149 | 150 | if (digest == null) { 151 | // Instantiate the digest from the key parameters 152 | try { 153 | digest = MessageDigest.getInstance(key.getParams().getHashAlgorithm()); 154 | } catch (NoSuchAlgorithmException e) { 155 | throw new InvalidKeyException("cannot get required digest " + key.getParams().getHashAlgorithm() + " for private key."); 156 | } 157 | } else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm())) 158 | throw new InvalidKeyException("Key hash algorithm does not match chosen digest"); 159 | } else if (publicKey.getFormat().equals("X.509")) { 160 | // X509Certificate will sometimes contain an X509Key rather than the EdDSAPublicKey itself; the contained 161 | // key is valid but needs to be instanced as an EdDSAPublicKey before it can be used. 162 | EdDSAPublicKey parsedPublicKey; 163 | try { 164 | parsedPublicKey = new EdDSAPublicKey(new X509EncodedKeySpec(publicKey.getEncoded())); 165 | } catch (InvalidKeySpecException ex) { 166 | throw new InvalidKeyException("cannot handle X.509 EdDSA public key: " + publicKey.getAlgorithm()); 167 | } 168 | engineInitVerify(parsedPublicKey); 169 | } else { 170 | throw new InvalidKeyException("cannot identify EdDSA public key: " + publicKey.getClass()); 171 | } 172 | } 173 | 174 | /** 175 | * @throws SignatureException if in one-shot mode 176 | */ 177 | @Override 178 | protected void engineUpdate(byte b) throws SignatureException { 179 | if (oneShotMode) 180 | throw new SignatureException("unsupported in one-shot mode"); 181 | if (baos == null) 182 | baos = new ByteArrayOutputStream(256); 183 | baos.write(b); 184 | } 185 | 186 | /** 187 | * @throws SignatureException if one-shot rules are violated 188 | */ 189 | @Override 190 | protected void engineUpdate(byte[] b, int off, int len) 191 | throws SignatureException { 192 | if (oneShotMode) { 193 | if (oneShotBytes != null) 194 | throw new SignatureException("update() already called"); 195 | oneShotBytes = b; 196 | oneShotOffset = off; 197 | oneShotLength = len; 198 | } else { 199 | if (baos == null) 200 | baos = new ByteArrayOutputStream(256); 201 | baos.write(b, off, len); 202 | } 203 | } 204 | 205 | @Override 206 | protected byte[] engineSign() throws SignatureException { 207 | try { 208 | return x_engineSign(); 209 | } finally { 210 | reset(); 211 | // must leave the object ready to sign again with 212 | // the same key, as required by the API 213 | EdDSAPrivateKey privKey = (EdDSAPrivateKey) key; 214 | digestInitSign(privKey); 215 | } 216 | } 217 | 218 | private byte[] x_engineSign() throws SignatureException { 219 | Curve curve = key.getParams().getCurve(); 220 | ScalarOps sc = key.getParams().getScalarOps(); 221 | byte[] a = ((EdDSAPrivateKey) key).geta(); 222 | 223 | byte[] message; 224 | int offset, length; 225 | if (oneShotMode) { 226 | if (oneShotBytes == null) 227 | throw new SignatureException("update() not called first"); 228 | message = oneShotBytes; 229 | offset = oneShotOffset; 230 | length = oneShotLength; 231 | } else { 232 | if (baos == null) 233 | message = new byte[0]; 234 | else 235 | message = baos.toByteArray(); 236 | offset = 0; 237 | length = message.length; 238 | } 239 | // r = H(h_b,...,h_2b-1,M) 240 | digest.update(message, offset, length); 241 | byte[] r = digest.digest(); 242 | 243 | // r mod l 244 | // Reduces r from 64 bytes to 32 bytes 245 | r = sc.reduce(r); 246 | 247 | // R = rB 248 | GroupElement R = key.getParams().getB().scalarMultiply(r); 249 | byte[] Rbyte = R.toByteArray(); 250 | 251 | // S = (r + H(Rbar,Abar,M)*a) mod l 252 | digest.update(Rbyte); 253 | digest.update(((EdDSAPrivateKey) key).getAbyte()); 254 | digest.update(message, offset, length); 255 | byte[] h = digest.digest(); 256 | h = sc.reduce(h); 257 | byte[] S = sc.multiplyAndAdd(h, a, r); 258 | 259 | // R+S 260 | int b = curve.getField().getb(); 261 | ByteBuffer out = ByteBuffer.allocate(b/4); 262 | out.put(Rbyte).put(S); 263 | return out.array(); 264 | } 265 | 266 | @Override 267 | protected boolean engineVerify(byte[] sigBytes) throws SignatureException { 268 | try { 269 | return x_engineVerify(sigBytes); 270 | } finally { 271 | reset(); 272 | } 273 | } 274 | 275 | private boolean x_engineVerify(byte[] sigBytes) throws SignatureException { 276 | Curve curve = key.getParams().getCurve(); 277 | int b = curve.getField().getb(); 278 | if (sigBytes.length != b/4) 279 | throw new SignatureException("signature length is wrong"); 280 | 281 | // R is first b/8 bytes of sigBytes, S is second b/8 bytes 282 | digest.update(sigBytes, 0, b/8); 283 | digest.update(((EdDSAPublicKey) key).getAbyte()); 284 | // h = H(Rbar,Abar,M) 285 | byte[] message; 286 | int offset, length; 287 | if (oneShotMode) { 288 | if (oneShotBytes == null) 289 | throw new SignatureException("update() not called first"); 290 | message = oneShotBytes; 291 | offset = oneShotOffset; 292 | length = oneShotLength; 293 | } else { 294 | if (baos == null) 295 | message = new byte[0]; 296 | else 297 | message = baos.toByteArray(); 298 | offset = 0; 299 | length = message.length; 300 | } 301 | digest.update(message, offset, length); 302 | byte[] h = digest.digest(); 303 | 304 | // h mod l 305 | h = key.getParams().getScalarOps().reduce(h); 306 | 307 | byte[] Sbyte = Arrays.copyOfRange(sigBytes, b/8, b/4); 308 | // R = SB - H(Rbar,Abar,M)A 309 | GroupElement R = key.getParams().getB().doubleScalarMultiplyVariableTime( 310 | ((EdDSAPublicKey) key).getNegativeA(), h, Sbyte); 311 | 312 | // Variable time. This should be okay, because there are no secret 313 | // values used anywhere in verification. 314 | byte[] Rcalc = R.toByteArray(); 315 | for (int i = 0; i < Rcalc.length; i++) { 316 | if (Rcalc[i] != sigBytes[i]) 317 | return false; 318 | } 319 | return true; 320 | } 321 | 322 | /** 323 | * To efficiently sign all the data in one shot, if it is available, 324 | * use this method, which will avoid copying the data. 325 | * 326 | * Same as: 327 | *
328 |      *  setParameter(ONE_SHOT_MODE)
329 |      *  update(data)
330 |      *  sig = sign()
331 |      *
332 | * 333 | * @param data the message to be signed 334 | * @return the signature 335 | * @throws SignatureException if update() already called 336 | * @see #ONE_SHOT_MODE 337 | */ 338 | public byte[] signOneShot(byte[] data) throws SignatureException { 339 | return signOneShot(data, 0, data.length); 340 | } 341 | 342 | /** 343 | * To efficiently sign all the data in one shot, if it is available, 344 | * use this method, which will avoid copying the data. 345 | * 346 | * Same as: 347 | *
348 |      *  setParameter(ONE_SHOT_MODE)
349 |      *  update(data, off, len)
350 |      *  sig = sign()
351 |      *
352 | * 353 | * @param data byte array containing the message to be signed 354 | * @param off the start of the message inside data 355 | * @param len the length of the message 356 | * @return the signature 357 | * @throws SignatureException if update() already called 358 | * @see #ONE_SHOT_MODE 359 | */ 360 | public byte[] signOneShot(byte[] data, int off, int len) throws SignatureException { 361 | oneShotMode = true; 362 | update(data, off, len); 363 | return sign(); 364 | } 365 | 366 | /** 367 | * To efficiently verify all the data in one shot, if it is available, 368 | * use this method, which will avoid copying the data. 369 | * 370 | * Same as: 371 | *
372 |      *  setParameter(ONE_SHOT_MODE)
373 |      *  update(data)
374 |      *  ok = verify(signature)
375 |      *
376 | * 377 | * @param data the message that was signed 378 | * @param signature of the message 379 | * @return true if the signature is valid, false otherwise 380 | * @throws SignatureException if update() already called 381 | * @see #ONE_SHOT_MODE 382 | */ 383 | public boolean verifyOneShot(byte[] data, byte[] signature) throws SignatureException { 384 | return verifyOneShot(data, 0, data.length, signature, 0, signature.length); 385 | } 386 | 387 | /** 388 | * To efficiently verify all the data in one shot, if it is available, 389 | * use this method, which will avoid copying the data. 390 | * 391 | * Same as: 392 | *
393 |      *  setParameter(ONE_SHOT_MODE)
394 |      *  update(data, off, len)
395 |      *  ok = verify(signature)
396 |      *
397 | * 398 | * @param data byte array containing the message that was signed 399 | * @param off the start of the message inside data 400 | * @param len the length of the message 401 | * @param signature of the message 402 | * @return true if the signature is valid, false otherwise 403 | * @throws SignatureException if update() already called 404 | * @see #ONE_SHOT_MODE 405 | */ 406 | public boolean verifyOneShot(byte[] data, int off, int len, byte[] signature) throws SignatureException { 407 | return verifyOneShot(data, off, len, signature, 0, signature.length); 408 | } 409 | 410 | /** 411 | * To efficiently verify all the data in one shot, if it is available, 412 | * use this method, which will avoid copying the data. 413 | * 414 | * Same as: 415 | *
416 |      *  setParameter(ONE_SHOT_MODE)
417 |      *  update(data)
418 |      *  ok = verify(signature, sigoff, siglen)
419 |      *
420 | * 421 | * @param data the message that was signed 422 | * @param signature byte array containing the signature 423 | * @param sigoff the start of the signature 424 | * @param siglen the length of the signature 425 | * @return true if the signature is valid, false otherwise 426 | * @throws SignatureException if update() already called 427 | * @see #ONE_SHOT_MODE 428 | */ 429 | public boolean verifyOneShot(byte[] data, byte[] signature, int sigoff, int siglen) throws SignatureException { 430 | return verifyOneShot(data, 0, data.length, signature, sigoff, siglen); 431 | } 432 | 433 | /** 434 | * To efficiently verify all the data in one shot, if it is available, 435 | * use this method, which will avoid copying the data. 436 | * 437 | * Same as: 438 | *
439 |      *  setParameter(ONE_SHOT_MODE)
440 |      *  update(data, off, len)
441 |      *  ok = verify(signature, sigoff, siglen)
442 |      *
443 | * 444 | * @param data byte array containing the message that was signed 445 | * @param off the start of the message inside data 446 | * @param len the length of the message 447 | * @param signature byte array containing the signature 448 | * @param sigoff the start of the signature 449 | * @param siglen the length of the signature 450 | * @return true if the signature is valid, false otherwise 451 | * @throws SignatureException if update() already called 452 | * @see #ONE_SHOT_MODE 453 | */ 454 | public boolean verifyOneShot(byte[] data, int off, int len, byte[] signature, int sigoff, int siglen) throws SignatureException { 455 | oneShotMode = true; 456 | update(data, off, len); 457 | return verify(signature, sigoff, siglen); 458 | } 459 | 460 | /** 461 | * @throws InvalidAlgorithmParameterException if spec is ONE_SHOT_MODE and update() already called 462 | * @see #ONE_SHOT_MODE 463 | */ 464 | @Override 465 | protected void engineSetParameter(AlgorithmParameterSpec spec) throws InvalidAlgorithmParameterException { 466 | if (spec.equals(ONE_SHOT_MODE)) { 467 | if (oneShotBytes != null || (baos != null && baos.size() > 0)) 468 | throw new InvalidAlgorithmParameterException("update() already called"); 469 | oneShotMode = true; 470 | } else { 471 | super.engineSetParameter(spec); 472 | } 473 | } 474 | 475 | /** 476 | * @deprecated 477 | */ 478 | @Override 479 | protected void engineSetParameter(String param, Object value) { 480 | throw new UnsupportedOperationException("engineSetParameter unsupported"); 481 | } 482 | 483 | /** 484 | * @deprecated 485 | */ 486 | @Override 487 | protected Object engineGetParameter(String param) { 488 | throw new UnsupportedOperationException("engineSetParameter unsupported"); 489 | } 490 | } 491 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/EdDSAKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa; 13 | 14 | import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; 15 | 16 | /** 17 | * Common interface for all EdDSA keys. 18 | * @author str4d 19 | */ 20 | public interface EdDSAKey { 21 | /** 22 | * The reported key algorithm for all EdDSA keys 23 | */ 24 | String KEY_ALGORITHM = "EdDSA"; 25 | 26 | /** 27 | * @return a parameter specification representing the EdDSA domain 28 | * parameters for the key. 29 | */ 30 | EdDSAParameterSpec getParams(); 31 | } 32 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/EdDSAPrivateKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa; 13 | 14 | import java.security.PrivateKey; 15 | import java.security.spec.InvalidKeySpecException; 16 | import java.security.spec.PKCS8EncodedKeySpec; 17 | import java.util.Arrays; 18 | 19 | import net.i2p.crypto.eddsa.math.GroupElement; 20 | import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; 21 | import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; 22 | import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; 23 | 24 | /** 25 | * An EdDSA private key. 26 | *

27 | * For compatibility with older releases, decoding supports both RFC 8410 and an 28 | * older draft specifications. 29 | * 30 | * @author str4d 31 | * @see RFC 8410 32 | * @see Older draft 34 | * specification 35 | */ 36 | public class EdDSAPrivateKey implements EdDSAKey, PrivateKey { 37 | private static final long serialVersionUID = 23495873459878957L; 38 | private final byte[] seed; 39 | private final byte[] h; 40 | private final byte[] a; 41 | private final GroupElement A; 42 | private final byte[] Abyte; 43 | private final EdDSAParameterSpec edDsaSpec; 44 | 45 | // OID 1.3.101.xxx 46 | private static final int OID_OLD = 100; 47 | private static final int OID_ED25519 = 112; 48 | private static final int OID_BYTE = 11; 49 | private static final int IDLEN_BYTE = 6; 50 | 51 | public EdDSAPrivateKey(EdDSAPrivateKeySpec spec) { 52 | this.seed = spec.getSeed(); 53 | this.h = spec.getH(); 54 | this.a = spec.geta(); 55 | this.A = spec.getA(); 56 | this.Abyte = this.A.toByteArray(); 57 | this.edDsaSpec = spec.getParams(); 58 | } 59 | 60 | public EdDSAPrivateKey(PKCS8EncodedKeySpec spec) throws InvalidKeySpecException { 61 | this(new EdDSAPrivateKeySpec(decode(spec.getEncoded()), 62 | EdDSANamedCurveTable.ED_25519_CURVE_SPEC)); 63 | } 64 | 65 | @Override 66 | public String getAlgorithm() { 67 | return KEY_ALGORITHM; 68 | } 69 | 70 | @Override 71 | public String getFormat() { 72 | return "PKCS#8"; 73 | } 74 | 75 | /** 76 | * Returns the private key in its canonical encoding. 77 | *

78 | * This implements the following specs: 79 | *

    80 | *
  • General encoding: https://tools.ietf.org/html/rfc8410
  • 81 | *
  • Key encoding: https://tools.ietf.org/html/rfc8032
  • 82 | *
83 | *

84 | * This encodes the seed. It will return null if constructed from a spec which 85 | * was directly constructed from H, in which case seed is null. 86 | *

87 | * For keys in older formats, decoding and then re-encoding is sufficient to 88 | * migrate them to the canonical encoding. 89 | *

90 | * Relevant spec quotes: 91 | * 92 | *

 93 |      *  OneAsymmetricKey ::= SEQUENCE {
 94 |      *    version Version,
 95 |      *    privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
 96 |      *    privateKey PrivateKey,
 97 |      *    attributes [0] IMPLICIT Attributes OPTIONAL,
 98 |      *    ...,
 99 |      *    [[2: publicKey [1] IMPLICIT PublicKey OPTIONAL ]],
100 |      *    ...
101 |      *  }
102 |      *
103 |      *  Version ::= INTEGER
104 |      *  PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
105 |      *  PrivateKey ::= OCTET STRING
106 |      *  PublicKey ::= BIT STRING
107 |      *  Attributes ::= SET OF Attribute
108 |      * 
109 | * 110 | *
111 |      *  ... when encoding a OneAsymmetricKey object, the private key is wrapped
112 |      *  in a CurvePrivateKey object and wrapped by the OCTET STRING of the
113 |      *  "privateKey" field.
114 |      *
115 |      *  CurvePrivateKey ::= OCTET STRING
116 |      * 
117 | * 118 | *
119 |      *  AlgorithmIdentifier  ::=  SEQUENCE  {
120 |      *    algorithm   OBJECT IDENTIFIER,
121 |      *    parameters  ANY DEFINED BY algorithm OPTIONAL
122 |      *  }
123 |      *
124 |      *  For all of the OIDs, the parameters MUST be absent.
125 |      * 
126 | * 127 | *
128 |      *  id-Ed25519   OBJECT IDENTIFIER ::= { 1 3 101 112 }
129 |      * 
130 | * 131 | * @return 48 bytes for Ed25519, null for other curves 132 | */ 133 | @Override 134 | public byte[] getEncoded() { 135 | if (!edDsaSpec.equals(EdDSANamedCurveTable.ED_25519_CURVE_SPEC)) 136 | return null; 137 | if (seed == null) 138 | return null; 139 | int totlen = 16 + seed.length; 140 | byte[] rv = new byte[totlen]; 141 | int idx = 0; 142 | // sequence 143 | rv[idx++] = 0x30; 144 | rv[idx++] = (byte) (totlen - 2); 145 | // version 146 | rv[idx++] = 0x02; 147 | rv[idx++] = 1; 148 | // v1 - no public key included 149 | rv[idx++] = 0; 150 | // Algorithm Identifier 151 | // sequence 152 | rv[idx++] = 0x30; 153 | rv[idx++] = 5; 154 | // OID 155 | // https://msdn.microsoft.com/en-us/library/windows/desktop/bb540809%28v=vs.85%29.aspx 156 | rv[idx++] = 0x06; 157 | rv[idx++] = 3; 158 | rv[idx++] = (1 * 40) + 3; 159 | rv[idx++] = 101; 160 | rv[idx++] = (byte) OID_ED25519; 161 | // params - absent 162 | // PrivateKey 163 | rv[idx++] = 0x04; // octet string 164 | rv[idx++] = (byte) (2 + seed.length); 165 | // CurvePrivateKey 166 | rv[idx++] = 0x04; // octet string 167 | rv[idx++] = (byte) seed.length; 168 | // the key 169 | System.arraycopy(seed, 0, rv, idx, seed.length); 170 | return rv; 171 | } 172 | 173 | /** 174 | * Extracts the private key bytes from the provided encoding. 175 | *

176 | * This will decode data conforming to RFC 8410 or as inferred from the older 177 | * draft spec at https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04. 178 | *

179 | * Per RFC 8410 section 3, this function WILL accept a parameter value of 180 | * NULL, as it is required for interoperability with the default Java keystore. 181 | * Other implementations MUST NOT copy this behaviour from here unless they also 182 | * need to read keys from the default Java keystore. 183 | *

184 | * This is really dumb for now. It does not use a general-purpose ASN.1 decoder. 185 | * See also getEncoded(). 186 | * 187 | * @return 32 bytes for Ed25519, throws for other curves 188 | */ 189 | private static byte[] decode(byte[] d) throws InvalidKeySpecException { 190 | try { 191 | // 192 | // Setup and OID check 193 | // 194 | int totlen = 48; 195 | int idlen = 5; 196 | int doid = d[OID_BYTE]; 197 | if (doid == OID_OLD) { 198 | totlen = 49; 199 | idlen = 8; 200 | } else if (doid == OID_ED25519) { 201 | // Detect parameter value of NULL 202 | if (d[IDLEN_BYTE] == 7) { 203 | totlen = 50; 204 | idlen = 7; 205 | } 206 | } else { 207 | throw new InvalidKeySpecException("unsupported key spec"); 208 | } 209 | 210 | // 211 | // Pre-decoding check 212 | // 213 | if (d.length != totlen) { 214 | throw new InvalidKeySpecException("invalid key spec length"); 215 | } 216 | 217 | // 218 | // Decoding 219 | // 220 | int idx = 0; 221 | if (d[idx++] != 0x30 || 222 | d[idx++] != (totlen - 2) || 223 | d[idx++] != 0x02 || 224 | d[idx++] != 1 || 225 | d[idx++] != 0 || 226 | d[idx++] != 0x30 || 227 | d[idx++] != idlen || 228 | d[idx++] != 0x06 || 229 | d[idx++] != 3 || 230 | d[idx++] != (1 * 40) + 3 || 231 | d[idx++] != 101) { 232 | throw new InvalidKeySpecException("unsupported key spec"); 233 | } 234 | idx++; // OID, checked above 235 | // parameters only with old OID 236 | if (doid == OID_OLD) { 237 | if (d[idx++] != 0x0a || 238 | d[idx++] != 1 || 239 | d[idx++] != 1) { 240 | throw new InvalidKeySpecException("unsupported key spec"); 241 | } 242 | } else { 243 | // Handle parameter value of NULL 244 | // 245 | // Quoting RFC 8410 section 3: 246 | // > For all of the OIDs, the parameters MUST be absent. 247 | // > 248 | // > It is possible to find systems that require the parameters to be 249 | // > present. This can be due to either a defect in the original 1997 250 | // > syntax or a programming error where developers never got input where 251 | // > this was not true. The optimal solution is to fix these systems; 252 | // > where this is not possible, the problem needs to be restricted to 253 | // > that subsystem and not propagated to the Internet. 254 | // 255 | // Java's default keystore puts it in (when decoding as PKCS8 and then 256 | // re-encoding to pass on), so we must accept it. 257 | if (idlen == 7) { 258 | if (d[idx++] != 0x05 || 259 | d[idx++] != 0) { 260 | throw new InvalidKeySpecException("unsupported key spec"); 261 | } 262 | } 263 | // PrivateKey wrapping the CurvePrivateKey 264 | if (d[idx++] != 0x04 || 265 | d[idx++] != 34) { 266 | throw new InvalidKeySpecException("unsupported key spec"); 267 | } 268 | } 269 | if (d[idx++] != 0x04 || 270 | d[idx++] != 32) { 271 | throw new InvalidKeySpecException("unsupported key spec"); 272 | } 273 | byte[] rv = new byte[32]; 274 | System.arraycopy(d, idx, rv, 0, 32); 275 | return rv; 276 | } catch (IndexOutOfBoundsException ioobe) { 277 | throw new InvalidKeySpecException(ioobe); 278 | } 279 | } 280 | 281 | @Override 282 | public EdDSAParameterSpec getParams() { 283 | return edDsaSpec; 284 | } 285 | 286 | /** 287 | * @return will be null if constructed from a spec which was directly 288 | * constructed from H 289 | */ 290 | public byte[] getSeed() { 291 | return seed; 292 | } 293 | 294 | /** 295 | * @return the hash of the seed 296 | */ 297 | public byte[] getH() { 298 | return h; 299 | } 300 | 301 | /** 302 | * @return the private key 303 | */ 304 | public byte[] geta() { 305 | return a; 306 | } 307 | 308 | /** 309 | * @return the public key 310 | */ 311 | public GroupElement getA() { 312 | return A; 313 | } 314 | 315 | /** 316 | * @return the public key 317 | */ 318 | public byte[] getAbyte() { 319 | return Abyte; 320 | } 321 | 322 | @Override 323 | public int hashCode() { 324 | return Arrays.hashCode(seed); 325 | } 326 | 327 | @Override 328 | public boolean equals(Object o) { 329 | if (o == this) 330 | return true; 331 | if (!(o instanceof EdDSAPrivateKey)) 332 | return false; 333 | EdDSAPrivateKey pk = (EdDSAPrivateKey) o; 334 | return Arrays.equals(seed, pk.getSeed()) && 335 | edDsaSpec.equals(pk.getParams()); 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/EdDSAPublicKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa; 13 | 14 | import java.security.PublicKey; 15 | import java.security.spec.InvalidKeySpecException; 16 | import java.security.spec.X509EncodedKeySpec; 17 | import java.util.Arrays; 18 | 19 | import net.i2p.crypto.eddsa.math.GroupElement; 20 | import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; 21 | import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; 22 | import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; 23 | 24 | /** 25 | * An EdDSA public key. 26 | *

27 | * For compatibility with older releases, decoding supports both RFC 8410 and an 28 | * older draft specification. 29 | * 30 | * @author str4d 31 | * @see RFC 8410 32 | * @see Older draft 34 | * specification 35 | */ 36 | public class EdDSAPublicKey implements EdDSAKey, PublicKey { 37 | private static final long serialVersionUID = 9837459837498475L; 38 | private final GroupElement A; 39 | private GroupElement Aneg = null; 40 | private final byte[] Abyte; 41 | private final EdDSAParameterSpec edDsaSpec; 42 | 43 | // OID 1.3.101.xxx 44 | private static final int OID_OLD = 100; 45 | private static final int OID_ED25519 = 112; 46 | private static final int OID_BYTE = 8; 47 | private static final int IDLEN_BYTE = 3; 48 | 49 | public EdDSAPublicKey(EdDSAPublicKeySpec spec) { 50 | this.A = spec.getA(); 51 | this.Abyte = this.A.toByteArray(); 52 | this.edDsaSpec = spec.getParams(); 53 | } 54 | 55 | public EdDSAPublicKey(X509EncodedKeySpec spec) throws InvalidKeySpecException { 56 | this(new EdDSAPublicKeySpec(decode(spec.getEncoded()), 57 | EdDSANamedCurveTable.ED_25519_CURVE_SPEC)); 58 | } 59 | 60 | @Override 61 | public String getAlgorithm() { 62 | return KEY_ALGORITHM; 63 | } 64 | 65 | @Override 66 | public String getFormat() { 67 | return "X.509"; 68 | } 69 | 70 | /** 71 | * Returns the public key in its canonical encoding. 72 | *

73 | * This implements the following specs: 74 | *

    75 | *
  • General encoding: https://tools.ietf.org/html/rfc8410
  • 76 | *
  • Key encoding: https://tools.ietf.org/html/rfc8032
  • 77 | *
78 | *

79 | * For keys in older formats, decoding and then re-encoding is sufficient to 80 | * migrate them to the canonical encoding. 81 | *

82 | * Relevant spec quotes: 83 | * 84 | *

 85 |      *  In the X.509 certificate, the subjectPublicKeyInfo field has the
 86 |      *  SubjectPublicKeyInfo type, which has the following ASN.1 syntax:
 87 |      *
 88 |      *  SubjectPublicKeyInfo  ::=  SEQUENCE  {
 89 |      *    algorithm         AlgorithmIdentifier,
 90 |      *    subjectPublicKey  BIT STRING
 91 |      *  }
 92 |      * 
93 | * 94 | *
 95 |      *  AlgorithmIdentifier  ::=  SEQUENCE  {
 96 |      *    algorithm   OBJECT IDENTIFIER,
 97 |      *    parameters  ANY DEFINED BY algorithm OPTIONAL
 98 |      *  }
 99 |      *
100 |      *  For all of the OIDs, the parameters MUST be absent.
101 |      * 
102 | * 103 | *
104 |      *  id-Ed25519   OBJECT IDENTIFIER ::= { 1 3 101 112 }
105 |      * 
106 | * 107 | * @return 44 bytes for Ed25519, null for other curves 108 | */ 109 | @Override 110 | public byte[] getEncoded() { 111 | if (!edDsaSpec.equals(EdDSANamedCurveTable.ED_25519_CURVE_SPEC)) 112 | return null; 113 | int totlen = 12 + Abyte.length; 114 | byte[] rv = new byte[totlen]; 115 | int idx = 0; 116 | // sequence 117 | rv[idx++] = 0x30; 118 | rv[idx++] = (byte) (totlen - 2); 119 | // Algorithm Identifier 120 | // sequence 121 | rv[idx++] = 0x30; 122 | rv[idx++] = 5; 123 | // OID 124 | // https://msdn.microsoft.com/en-us/library/windows/desktop/bb540809%28v=vs.85%29.aspx 125 | rv[idx++] = 0x06; 126 | rv[idx++] = 3; 127 | rv[idx++] = (1 * 40) + 3; 128 | rv[idx++] = 101; 129 | rv[idx++] = (byte) OID_ED25519; 130 | // params - absent 131 | // the key 132 | rv[idx++] = 0x03; // bit string 133 | rv[idx++] = (byte) (1 + Abyte.length); 134 | rv[idx++] = 0; // number of trailing unused bits 135 | System.arraycopy(Abyte, 0, rv, idx, Abyte.length); 136 | return rv; 137 | } 138 | 139 | /** 140 | * Extracts the public key bytes from the provided encoding. 141 | *

142 | * This will decode data conforming to RFC 8410 or the older draft spec at 143 | * https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04. 144 | *

145 | * Per RFC 8410 section 3, this function WILL accept a parameter value of 146 | * NULL, as it is required for interoperability with the default Java keystore. 147 | * Other implementations MUST NOT copy this behaviour from here unless they also 148 | * need to read keys from the default Java keystore. 149 | *

150 | * This is really dumb for now. It does not use a general-purpose ASN.1 decoder. 151 | * See also getEncoded(). 152 | * 153 | * @return 32 bytes for Ed25519, throws for other curves 154 | */ 155 | private static byte[] decode(byte[] d) throws InvalidKeySpecException { 156 | try { 157 | // 158 | // Setup and OID check 159 | // 160 | int totlen = 44; 161 | int idlen = 5; 162 | int doid = d[OID_BYTE]; 163 | if (doid == OID_OLD) { 164 | totlen = 47; 165 | idlen = 8; 166 | } else if (doid == OID_ED25519) { 167 | // Detect parameter value of NULL 168 | if (d[IDLEN_BYTE] == 7) { 169 | totlen = 46; 170 | idlen = 7; 171 | } 172 | } else { 173 | throw new InvalidKeySpecException("unsupported key spec"); 174 | } 175 | 176 | // 177 | // Pre-decoding check 178 | // 179 | if (d.length != totlen) { 180 | throw new InvalidKeySpecException("invalid key spec length"); 181 | } 182 | 183 | // 184 | // Decoding 185 | // 186 | int idx = 0; 187 | if (d[idx++] != 0x30 || 188 | d[idx++] != (totlen - 2) || 189 | d[idx++] != 0x30 || 190 | d[idx++] != idlen || 191 | d[idx++] != 0x06 || 192 | d[idx++] != 3 || 193 | d[idx++] != (1 * 40) + 3 || 194 | d[idx++] != 101) { 195 | throw new InvalidKeySpecException("unsupported key spec"); 196 | } 197 | idx++; // OID, checked above 198 | // parameters only with old OID 199 | if (doid == OID_OLD) { 200 | if (d[idx++] != 0x0a || 201 | d[idx++] != 1 || 202 | d[idx++] != 1) { 203 | throw new InvalidKeySpecException("unsupported key spec"); 204 | } 205 | } else { 206 | // Handle parameter value of NULL 207 | // 208 | // Quoting RFC 8410 section 3: 209 | // > For all of the OIDs, the parameters MUST be absent. 210 | // > 211 | // > It is possible to find systems that require the parameters to be 212 | // > present. This can be due to either a defect in the original 1997 213 | // > syntax or a programming error where developers never got input where 214 | // > this was not true. The optimal solution is to fix these systems; 215 | // > where this is not possible, the problem needs to be restricted to 216 | // > that subsystem and not propagated to the Internet. 217 | // 218 | // Java's default keystore puts it in (when decoding as PKCS8 and then 219 | // re-encoding to pass on), so we must accept it. 220 | if (idlen == 7) { 221 | if (d[idx++] != 0x05 || 222 | d[idx++] != 0) { 223 | throw new InvalidKeySpecException("unsupported key spec"); 224 | } 225 | } 226 | } 227 | if (d[idx++] != 0x03 || 228 | d[idx++] != 33 || 229 | d[idx++] != 0) { 230 | throw new InvalidKeySpecException("unsupported key spec"); 231 | } 232 | byte[] rv = new byte[32]; 233 | System.arraycopy(d, idx, rv, 0, 32); 234 | return rv; 235 | } catch (IndexOutOfBoundsException ioobe) { 236 | throw new InvalidKeySpecException(ioobe); 237 | } 238 | } 239 | 240 | @Override 241 | public EdDSAParameterSpec getParams() { 242 | return edDsaSpec; 243 | } 244 | 245 | public GroupElement getA() { 246 | return A; 247 | } 248 | 249 | public GroupElement getNegativeA() { 250 | // Only read Aneg once, otherwise read re-ordering might occur between 251 | // here and return. Requires all GroupElement's fields to be final. 252 | GroupElement ourAneg = Aneg; 253 | if(ourAneg == null) { 254 | ourAneg = A.negate(); 255 | Aneg = ourAneg; 256 | } 257 | return ourAneg; 258 | } 259 | 260 | public byte[] getAbyte() { 261 | return Abyte; 262 | } 263 | 264 | @Override 265 | public int hashCode() { 266 | return Arrays.hashCode(Abyte); 267 | } 268 | 269 | @Override 270 | public boolean equals(Object o) { 271 | if (o == this) 272 | return true; 273 | if (!(o instanceof EdDSAPublicKey)) 274 | return false; 275 | EdDSAPublicKey pk = (EdDSAPublicKey) o; 276 | return Arrays.equals(Abyte, pk.getAbyte()) && 277 | edDsaSpec.equals(pk.getParams()); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/EdDSASecurityProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa; 13 | 14 | import java.security.AccessController; 15 | import java.security.PrivilegedAction; 16 | import java.security.Provider; 17 | import java.security.Security; 18 | 19 | /** 20 | * A security {@link Provider} that can be registered via {@link Security#addProvider(Provider)} 21 | * 22 | * @author str4d 23 | */ 24 | public class EdDSASecurityProvider extends Provider { 25 | private static final long serialVersionUID = 1210027906682292307L; 26 | public static final String PROVIDER_NAME = "EdDSA"; 27 | 28 | public EdDSASecurityProvider() { 29 | super(PROVIDER_NAME, 0.3 /* should match POM major.minor version */, "str4d " + PROVIDER_NAME + " security provider wrapper"); 30 | 31 | AccessController.doPrivileged(new PrivilegedAction() { 32 | @Override 33 | public Object run() { 34 | setup(); 35 | return null; 36 | } 37 | }); 38 | } 39 | 40 | protected void setup() { 41 | // See https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/HowToImplAProvider.html 42 | put("KeyFactory." + EdDSAKey.KEY_ALGORITHM, "net.i2p.crypto.eddsa.KeyFactory"); 43 | put("KeyPairGenerator." + EdDSAKey.KEY_ALGORITHM, "net.i2p.crypto.eddsa.KeyPairGenerator"); 44 | put("Signature." + EdDSAEngine.SIGNATURE_ALGORITHM, "net.i2p.crypto.eddsa.EdDSAEngine"); 45 | 46 | // OID Mappings 47 | // See section "Mapping from OID to name". 48 | // The Key* -> OID mappings correspond to the default algorithm in KeyPairGenerator. 49 | // 50 | // From RFC 8410 section 3: 51 | // id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 } 52 | put("Alg.Alias.KeyFactory.1.3.101.112", EdDSAKey.KEY_ALGORITHM); 53 | put("Alg.Alias.KeyFactory.OID.1.3.101.112", EdDSAKey.KEY_ALGORITHM); 54 | put("Alg.Alias.KeyPairGenerator.1.3.101.112", EdDSAKey.KEY_ALGORITHM); 55 | put("Alg.Alias.KeyPairGenerator.OID.1.3.101.112", EdDSAKey.KEY_ALGORITHM); 56 | put("Alg.Alias.Signature.1.3.101.112", EdDSAEngine.SIGNATURE_ALGORITHM); 57 | put("Alg.Alias.Signature.OID.1.3.101.112", EdDSAEngine.SIGNATURE_ALGORITHM); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/KeyFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa; 13 | 14 | import java.security.InvalidKeyException; 15 | import java.security.Key; 16 | import java.security.KeyFactorySpi; 17 | import java.security.PrivateKey; 18 | import java.security.PublicKey; 19 | import java.security.spec.InvalidKeySpecException; 20 | import java.security.spec.KeySpec; 21 | import java.security.spec.PKCS8EncodedKeySpec; 22 | import java.security.spec.X509EncodedKeySpec; 23 | 24 | import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; 25 | import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; 26 | 27 | /** 28 | * @author str4d 29 | * 30 | */ 31 | public final class KeyFactory extends KeyFactorySpi { 32 | 33 | protected PrivateKey engineGeneratePrivate(KeySpec keySpec) 34 | throws InvalidKeySpecException { 35 | if (keySpec instanceof EdDSAPrivateKeySpec) { 36 | return new EdDSAPrivateKey((EdDSAPrivateKeySpec) keySpec); 37 | } 38 | if (keySpec instanceof PKCS8EncodedKeySpec) { 39 | return new EdDSAPrivateKey((PKCS8EncodedKeySpec) keySpec); 40 | } 41 | throw new InvalidKeySpecException("key spec not recognised: " + keySpec.getClass()); 42 | } 43 | 44 | protected PublicKey engineGeneratePublic(KeySpec keySpec) 45 | throws InvalidKeySpecException { 46 | if (keySpec instanceof EdDSAPublicKeySpec) { 47 | return new EdDSAPublicKey((EdDSAPublicKeySpec) keySpec); 48 | } 49 | if (keySpec instanceof X509EncodedKeySpec) { 50 | return new EdDSAPublicKey((X509EncodedKeySpec) keySpec); 51 | } 52 | throw new InvalidKeySpecException("key spec not recognised: " + keySpec.getClass()); 53 | } 54 | 55 | @SuppressWarnings("unchecked") 56 | protected T engineGetKeySpec(Key key, Class keySpec) 57 | throws InvalidKeySpecException { 58 | if (keySpec.isAssignableFrom(EdDSAPublicKeySpec.class) && key instanceof EdDSAPublicKey) { 59 | EdDSAPublicKey k = (EdDSAPublicKey) key; 60 | if (k.getParams() != null) { 61 | return (T) new EdDSAPublicKeySpec(k.getA(), k.getParams()); 62 | } 63 | } else if (keySpec.isAssignableFrom(EdDSAPrivateKeySpec.class) && key instanceof EdDSAPrivateKey) { 64 | EdDSAPrivateKey k = (EdDSAPrivateKey) key; 65 | if (k.getParams() != null) { 66 | return (T) new EdDSAPrivateKeySpec(k.getSeed(), k.getH(), k.geta(), k.getA(), k.getParams()); 67 | } 68 | } 69 | throw new InvalidKeySpecException("not implemented yet " + key + " " + keySpec); 70 | } 71 | 72 | protected Key engineTranslateKey(Key key) throws InvalidKeyException { 73 | throw new InvalidKeyException("No other EdDSA key providers known"); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/KeyPairGenerator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa; 13 | 14 | import java.security.InvalidAlgorithmParameterException; 15 | import java.security.InvalidParameterException; 16 | import java.security.KeyPair; 17 | import java.security.KeyPairGeneratorSpi; 18 | import java.security.SecureRandom; 19 | import java.security.spec.AlgorithmParameterSpec; 20 | import java.util.Hashtable; 21 | 22 | import net.i2p.crypto.eddsa.spec.EdDSAGenParameterSpec; 23 | import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec; 24 | import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; 25 | import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; 26 | import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; 27 | import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; 28 | 29 | /** 30 | * Default keysize is 256 (Ed25519) 31 | */ 32 | public final class KeyPairGenerator extends KeyPairGeneratorSpi { 33 | private static final int DEFAULT_KEYSIZE = 256; 34 | private EdDSAParameterSpec edParams; 35 | private SecureRandom random; 36 | private boolean initialized; 37 | 38 | private static final Hashtable edParameters; 39 | 40 | static { 41 | edParameters = new Hashtable(); 42 | 43 | edParameters.put(Integer.valueOf(256), new EdDSAGenParameterSpec(EdDSANamedCurveTable.ED_25519)); 44 | } 45 | 46 | public void initialize(int keysize, SecureRandom random) { 47 | AlgorithmParameterSpec edParams = edParameters.get(Integer.valueOf(keysize)); 48 | if (edParams == null) 49 | throw new InvalidParameterException("unknown key type."); 50 | try { 51 | initialize(edParams, random); 52 | } catch (InvalidAlgorithmParameterException e) { 53 | throw new InvalidParameterException("key type not configurable."); 54 | } 55 | } 56 | 57 | @Override 58 | public void initialize(AlgorithmParameterSpec params, SecureRandom random) throws InvalidAlgorithmParameterException { 59 | if (params instanceof EdDSAParameterSpec) { 60 | edParams = (EdDSAParameterSpec) params; 61 | } else if (params instanceof EdDSAGenParameterSpec) { 62 | edParams = createNamedCurveSpec(((EdDSAGenParameterSpec) params).getName()); 63 | } else 64 | throw new InvalidAlgorithmParameterException("parameter object not a EdDSAParameterSpec"); 65 | 66 | this.random = random; 67 | initialized = true; 68 | } 69 | 70 | public KeyPair generateKeyPair() { 71 | if (!initialized) 72 | initialize(DEFAULT_KEYSIZE, new SecureRandom()); 73 | 74 | byte[] seed = new byte[edParams.getCurve().getField().getb()/8]; 75 | random.nextBytes(seed); 76 | 77 | EdDSAPrivateKeySpec privKey = new EdDSAPrivateKeySpec(seed, edParams); 78 | EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(privKey.getA(), edParams); 79 | 80 | return new KeyPair(new EdDSAPublicKey(pubKey), new EdDSAPrivateKey(privKey)); 81 | } 82 | 83 | /** 84 | * Create an EdDSANamedCurveSpec from the provided curve name. The current 85 | * implementation fetches the pre-created curve spec from a table. 86 | * @param curveName the EdDSA named curve. 87 | * @return the specification for the named curve. 88 | * @throws InvalidAlgorithmParameterException if the named curve is unknown. 89 | */ 90 | protected EdDSANamedCurveSpec createNamedCurveSpec(String curveName) throws InvalidAlgorithmParameterException { 91 | EdDSANamedCurveSpec spec = EdDSANamedCurveTable.getByName(curveName); 92 | if (spec == null) { 93 | throw new InvalidAlgorithmParameterException("unknown curve name: " + curveName); 94 | } 95 | return spec; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/Utils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa; 13 | 14 | /** 15 | * Basic utilities for EdDSA. 16 | * Not for external use, not maintained as a public API. 17 | * 18 | * @author str4d 19 | * 20 | */ 21 | public class Utils { 22 | /** 23 | * Constant-time byte comparison. 24 | * @param b a byte 25 | * @param c a byte 26 | * @return 1 if b and c are equal, 0 otherwise. 27 | */ 28 | public static int equal(int b, int c) { 29 | int result = 0; 30 | int xor = b ^ c; 31 | for (int i = 0; i < 8; i++) { 32 | result |= xor >> i; 33 | } 34 | return (result ^ 0x01) & 0x01; 35 | } 36 | 37 | /** 38 | * Constant-time byte[] comparison. 39 | * @param b a byte[] 40 | * @param c a byte[] 41 | * @return 1 if b and c are equal, 0 otherwise. 42 | */ 43 | public static int equal(byte[] b, byte[] c) { 44 | int result = 0; 45 | for (int i = 0; i < 32; i++) { 46 | result |= b[i] ^ c[i]; 47 | } 48 | 49 | return equal(result, 0); 50 | } 51 | 52 | /** 53 | * Constant-time determine if byte is negative. 54 | * @param b the byte to check. 55 | * @return 1 if the byte is negative, 0 otherwise. 56 | */ 57 | public static int negative(int b) { 58 | return (b >> 8) & 1; 59 | } 60 | 61 | /** 62 | * Get the i'th bit of a byte array. 63 | * @param h the byte array. 64 | * @param i the bit index. 65 | * @return 0 or 1, the value of the i'th bit in h 66 | */ 67 | public static int bit(byte[] h, int i) { 68 | return (h[i >> 3] >> (i & 7)) & 1; 69 | } 70 | 71 | /** 72 | * Converts a hex string to bytes. 73 | * @param s the hex string to be converted. 74 | * @return the byte[] 75 | */ 76 | public static byte[] hexToBytes(String s) { 77 | int len = s.length(); 78 | if (len % 2 != 0) { 79 | throw new IllegalArgumentException("Hex string must have an even length"); 80 | } 81 | byte[] data = new byte[len / 2]; 82 | for (int i = 0; i < len; i += 2) { 83 | data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) 84 | + Character.digit(s.charAt(i+1), 16)); 85 | } 86 | return data; 87 | } 88 | 89 | /** 90 | * Converts bytes to a hex string. 91 | * @param raw the byte[] to be converted. 92 | * @return the hex representation as a string. 93 | */ 94 | public static String bytesToHex(byte[] raw) { 95 | if ( raw == null ) { 96 | return null; 97 | } 98 | final StringBuilder hex = new StringBuilder(2 * raw.length); 99 | for (final byte b : raw) { 100 | hex.append(Character.forDigit((b & 0xF0) >> 4, 16)) 101 | .append(Character.forDigit((b & 0x0F), 16)); 102 | } 103 | return hex.toString(); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/math/Constants.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math; 13 | 14 | import net.i2p.crypto.eddsa.Utils; 15 | 16 | final class Constants { 17 | public static final byte[] ZERO = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000"); 18 | public static final byte[] ONE = Utils.hexToBytes("0100000000000000000000000000000000000000000000000000000000000000"); 19 | public static final byte[] TWO = Utils.hexToBytes("0200000000000000000000000000000000000000000000000000000000000000"); 20 | public static final byte[] FOUR = Utils.hexToBytes("0400000000000000000000000000000000000000000000000000000000000000"); 21 | public static final byte[] FIVE = Utils.hexToBytes("0500000000000000000000000000000000000000000000000000000000000000"); 22 | public static final byte[] EIGHT = Utils.hexToBytes("0800000000000000000000000000000000000000000000000000000000000000"); 23 | } 24 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/math/Curve.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math; 13 | 14 | import java.io.Serializable; 15 | 16 | /** 17 | * A twisted Edwards curve. 18 | * Points on the curve satisfy $-x^2 + y^2 = 1 + d x^2y^2$ 19 | * @author str4d 20 | * 21 | */ 22 | public class Curve implements Serializable { 23 | private static final long serialVersionUID = 4578920872509827L; 24 | private final Field f; 25 | private final FieldElement d; 26 | private final FieldElement d2; 27 | private final FieldElement I; 28 | 29 | private final GroupElement zeroP2; 30 | private final GroupElement zeroP3; 31 | private final GroupElement zeroP3PrecomputedDouble; 32 | private final GroupElement zeroPrecomp; 33 | 34 | public Curve(Field f, byte[] d, FieldElement I) { 35 | this.f = f; 36 | this.d = f.fromByteArray(d); 37 | this.d2 = this.d.add(this.d); 38 | this.I = I; 39 | 40 | FieldElement zero = f.ZERO; 41 | FieldElement one = f.ONE; 42 | zeroP2 = GroupElement.p2(this, zero, one, one); 43 | zeroP3 = GroupElement.p3(this, zero, one, one, zero, false); 44 | zeroP3PrecomputedDouble = GroupElement.p3(this, zero, one, one, zero, true); 45 | zeroPrecomp = GroupElement.precomp(this, one, one, zero); 46 | } 47 | 48 | public Field getField() { 49 | return f; 50 | } 51 | 52 | public FieldElement getD() { 53 | return d; 54 | } 55 | 56 | public FieldElement get2D() { 57 | return d2; 58 | } 59 | 60 | public FieldElement getI() { 61 | return I; 62 | } 63 | 64 | public GroupElement getZero(GroupElement.Representation repr) { 65 | switch (repr) { 66 | case P2: 67 | return zeroP2; 68 | case P3: 69 | return zeroP3; 70 | case P3PrecomputedDouble: 71 | return zeroP3PrecomputedDouble; 72 | case PRECOMP: 73 | return zeroPrecomp; 74 | default: 75 | return null; 76 | } 77 | } 78 | 79 | public GroupElement createPoint(byte[] P, boolean precompute) { 80 | GroupElement ge = new GroupElement(this, P, precompute); 81 | return ge; 82 | } 83 | 84 | @Override 85 | public int hashCode() { 86 | return f.hashCode() ^ 87 | d.hashCode() ^ 88 | I.hashCode(); 89 | } 90 | 91 | @Override 92 | public boolean equals(Object o) { 93 | if (o == this) 94 | return true; 95 | if (!(o instanceof Curve)) 96 | return false; 97 | Curve c = (Curve) o; 98 | return f.equals(c.getField()) && 99 | d.equals(c.getD()) && 100 | I.equals(c.getI()); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/math/Encoding.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math; 13 | 14 | /** 15 | * Common interface for all $(b-1)$-bit encodings of elements 16 | * of EdDSA finite fields. 17 | * @author str4d 18 | * 19 | */ 20 | public abstract class Encoding { 21 | protected Field f; 22 | 23 | public synchronized void setField(Field f) { 24 | if (this.f != null) 25 | throw new IllegalStateException("already set"); 26 | this.f = f; 27 | } 28 | 29 | /** 30 | * Encode a FieldElement in its $(b-1)$-bit encoding. 31 | * @param x the FieldElement to encode 32 | * @return the $(b-1)$-bit encoding of this FieldElement. 33 | */ 34 | public abstract byte[] encode(FieldElement x); 35 | 36 | /** 37 | * Decode a FieldElement from its $(b-1)$-bit encoding. 38 | * The highest bit is masked out. 39 | * @param in the $(b-1)$-bit encoding of a FieldElement. 40 | * @return the FieldElement represented by 'val'. 41 | */ 42 | public abstract FieldElement decode(byte[] in); 43 | 44 | /** 45 | * From the Ed25519 paper:
46 | * $x$ is negative if the $(b-1)$-bit encoding of $x$ is lexicographically larger 47 | * than the $(b-1)$-bit encoding of -x. If $q$ is an odd prime and the encoding 48 | * is the little-endian representation of $\{0, 1,\dots, q-1\}$ then the negative 49 | * elements of $F_q$ are $\{1, 3, 5,\dots, q-2\}$. 50 | * @param x the FieldElement to check 51 | * @return true if negative 52 | */ 53 | public abstract boolean isNegative(FieldElement x); 54 | } 55 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/math/Field.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math; 13 | 14 | import java.io.Serializable; 15 | 16 | /** 17 | * An EdDSA finite field. Includes several pre-computed values. 18 | * @author str4d 19 | * 20 | */ 21 | public class Field implements Serializable { 22 | private static final long serialVersionUID = 8746587465875676L; 23 | 24 | public final FieldElement ZERO; 25 | public final FieldElement ONE; 26 | public final FieldElement TWO; 27 | public final FieldElement FOUR; 28 | public final FieldElement FIVE; 29 | public final FieldElement EIGHT; 30 | 31 | private final int b; 32 | private final FieldElement q; 33 | /** 34 | * q-2 35 | */ 36 | private final FieldElement qm2; 37 | /** 38 | * (q-5) / 8 39 | */ 40 | private final FieldElement qm5d8; 41 | private final Encoding enc; 42 | 43 | public Field(int b, byte[] q, Encoding enc) { 44 | this.b = b; 45 | this.enc = enc; 46 | this.enc.setField(this); 47 | 48 | this.q = fromByteArray(q); 49 | 50 | // Set up constants 51 | ZERO = fromByteArray(Constants.ZERO); 52 | ONE = fromByteArray(Constants.ONE); 53 | TWO = fromByteArray(Constants.TWO); 54 | FOUR = fromByteArray(Constants.FOUR); 55 | FIVE = fromByteArray(Constants.FIVE); 56 | EIGHT = fromByteArray(Constants.EIGHT); 57 | 58 | // Precompute values 59 | qm2 = this.q.subtract(TWO); 60 | qm5d8 = this.q.subtract(FIVE).divide(EIGHT); 61 | } 62 | 63 | public FieldElement fromByteArray(byte[] x) { 64 | return enc.decode(x); 65 | } 66 | 67 | public int getb() { 68 | return b; 69 | } 70 | 71 | public FieldElement getQ() { 72 | return q; 73 | } 74 | 75 | public FieldElement getQm2() { 76 | return qm2; 77 | } 78 | 79 | public FieldElement getQm5d8() { 80 | return qm5d8; 81 | } 82 | 83 | public Encoding getEncoding(){ 84 | return enc; 85 | } 86 | 87 | @Override 88 | public int hashCode() { 89 | return q.hashCode(); 90 | } 91 | 92 | @Override 93 | public boolean equals(Object obj) { 94 | if (!(obj instanceof Field)) 95 | return false; 96 | Field f = (Field) obj; 97 | return b == f.b && q.equals(f.q); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/math/FieldElement.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math; 13 | 14 | import java.io.Serializable; 15 | 16 | /** 17 | * Note: concrete subclasses must implement hashCode() and equals() 18 | */ 19 | public abstract class FieldElement implements Serializable { 20 | private static final long serialVersionUID = 1239527465875676L; 21 | 22 | protected final Field f; 23 | 24 | public FieldElement(Field f) { 25 | if (null == f) { 26 | throw new IllegalArgumentException("field cannot be null"); 27 | } 28 | this.f = f; 29 | } 30 | 31 | /** 32 | * Encode a FieldElement in its $(b-1)$-bit encoding. 33 | * @return the $(b-1)$-bit encoding of this FieldElement. 34 | */ 35 | public byte[] toByteArray() { 36 | return f.getEncoding().encode(this); 37 | } 38 | 39 | public abstract boolean isNonZero(); 40 | 41 | public boolean isNegative() { 42 | return f.getEncoding().isNegative(this); 43 | } 44 | 45 | public abstract FieldElement add(FieldElement val); 46 | 47 | public FieldElement addOne() { 48 | return add(f.ONE); 49 | } 50 | 51 | public abstract FieldElement subtract(FieldElement val); 52 | 53 | public FieldElement subtractOne() { 54 | return subtract(f.ONE); 55 | } 56 | 57 | public abstract FieldElement negate(); 58 | 59 | public FieldElement divide(FieldElement val) { 60 | return multiply(val.invert()); 61 | } 62 | 63 | public abstract FieldElement multiply(FieldElement val); 64 | 65 | public abstract FieldElement square(); 66 | 67 | public abstract FieldElement squareAndDouble(); 68 | 69 | public abstract FieldElement invert(); 70 | 71 | public abstract FieldElement pow22523(); 72 | 73 | public abstract FieldElement cmov(FieldElement val, final int b); 74 | 75 | // Note: concrete subclasses must implement hashCode() and equals() 76 | } 77 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/math/ScalarOps.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math; 13 | 14 | public interface ScalarOps { 15 | /** 16 | * Reduce the given scalar mod $l$. 17 | *

18 | * From the Ed25519 paper:
19 | * Here we interpret $2b$-bit strings in little-endian form as integers in 20 | * $\{0, 1,..., 2^{(2b)}-1\}$. 21 | * @param s the scalar to reduce 22 | * @return $s \bmod l$ 23 | */ 24 | public byte[] reduce(byte[] s); 25 | 26 | /** 27 | * $r = (a * b + c) \bmod l$ 28 | * @param a a scalar 29 | * @param b a scalar 30 | * @param c a scalar 31 | * @return $(a*b + c) \bmod l$ 32 | */ 33 | public byte[] multiplyAndAdd(byte[] a, byte[] b, byte[] c); 34 | } 35 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/math/bigint/BigIntegerFieldElement.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math.bigint; 13 | 14 | import java.io.Serializable; 15 | import java.math.BigInteger; 16 | 17 | import net.i2p.crypto.eddsa.math.Field; 18 | import net.i2p.crypto.eddsa.math.FieldElement; 19 | 20 | /** 21 | * A particular element of the field \Z/(2^255-19). 22 | * @author str4d 23 | * 24 | */ 25 | public class BigIntegerFieldElement extends FieldElement implements Serializable { 26 | private static final long serialVersionUID = 4890398908392808L; 27 | /** 28 | * Variable is package private for encoding. 29 | */ 30 | final BigInteger bi; 31 | 32 | public BigIntegerFieldElement(Field f, BigInteger bi) { 33 | super(f); 34 | this.bi = bi; 35 | } 36 | 37 | public boolean isNonZero() { 38 | return !bi.equals(BigInteger.ZERO); 39 | } 40 | 41 | public FieldElement add(FieldElement val) { 42 | return new BigIntegerFieldElement(f, bi.add(((BigIntegerFieldElement)val).bi)).mod(f.getQ()); 43 | } 44 | 45 | @Override 46 | public FieldElement addOne() { 47 | return new BigIntegerFieldElement(f, bi.add(BigInteger.ONE)).mod(f.getQ()); 48 | } 49 | 50 | public FieldElement subtract(FieldElement val) { 51 | return new BigIntegerFieldElement(f, bi.subtract(((BigIntegerFieldElement)val).bi)).mod(f.getQ()); 52 | } 53 | 54 | @Override 55 | public FieldElement subtractOne() { 56 | return new BigIntegerFieldElement(f, bi.subtract(BigInteger.ONE)).mod(f.getQ()); 57 | } 58 | 59 | public FieldElement negate() { 60 | return f.getQ().subtract(this); 61 | } 62 | 63 | @Override 64 | public FieldElement divide(FieldElement val) { 65 | return divide(((BigIntegerFieldElement)val).bi); 66 | } 67 | 68 | public FieldElement divide(BigInteger val) { 69 | return new BigIntegerFieldElement(f, bi.divide(val)).mod(f.getQ()); 70 | } 71 | 72 | public FieldElement multiply(FieldElement val) { 73 | return new BigIntegerFieldElement(f, bi.multiply(((BigIntegerFieldElement)val).bi)).mod(f.getQ()); 74 | } 75 | 76 | public FieldElement square() { 77 | return multiply(this); 78 | } 79 | 80 | public FieldElement squareAndDouble() { 81 | FieldElement sq = square(); 82 | return sq.add(sq); 83 | } 84 | 85 | public FieldElement invert() { 86 | // Euler's theorem 87 | //return modPow(f.getQm2(), f.getQ()); 88 | return new BigIntegerFieldElement(f, bi.modInverse(((BigIntegerFieldElement)f.getQ()).bi)); 89 | } 90 | 91 | public FieldElement mod(FieldElement m) { 92 | return new BigIntegerFieldElement(f, bi.mod(((BigIntegerFieldElement)m).bi)); 93 | } 94 | 95 | public FieldElement modPow(FieldElement e, FieldElement m) { 96 | return new BigIntegerFieldElement(f, bi.modPow(((BigIntegerFieldElement)e).bi, ((BigIntegerFieldElement)m).bi)); 97 | } 98 | 99 | public FieldElement pow(FieldElement e){ 100 | return modPow(e, f.getQ()); 101 | } 102 | 103 | public FieldElement pow22523(){ 104 | return pow(f.getQm5d8()); 105 | } 106 | 107 | @Override 108 | public FieldElement cmov(FieldElement val, int b) { 109 | // Not constant-time, but it doesn't really matter because none of the underlying BigInteger operations 110 | // are either, so there's not much point in trying hard here ... 111 | return b == 0 ? this : val; 112 | } 113 | 114 | @Override 115 | public int hashCode() { 116 | return bi.hashCode(); 117 | } 118 | 119 | @Override 120 | public boolean equals(Object obj) { 121 | if (!(obj instanceof BigIntegerFieldElement)) 122 | return false; 123 | BigIntegerFieldElement fe = (BigIntegerFieldElement) obj; 124 | return bi.equals(fe.bi); 125 | } 126 | 127 | @Override 128 | public String toString() { 129 | return "[BigIntegerFieldElement val="+bi+"]"; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/math/bigint/BigIntegerLittleEndianEncoding.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math.bigint; 13 | 14 | import java.io.Serializable; 15 | import java.math.BigInteger; 16 | 17 | import net.i2p.crypto.eddsa.math.Encoding; 18 | import net.i2p.crypto.eddsa.math.Field; 19 | import net.i2p.crypto.eddsa.math.FieldElement; 20 | 21 | public class BigIntegerLittleEndianEncoding extends Encoding implements Serializable { 22 | private static final long serialVersionUID = 3984579843759837L; 23 | /** 24 | * Mask where only the first b-1 bits are set. 25 | */ 26 | private BigInteger mask; 27 | 28 | @Override 29 | public synchronized void setField(Field f) { 30 | super.setField(f); 31 | mask = BigInteger.ONE.shiftLeft(f.getb()-1).subtract(BigInteger.ONE); 32 | } 33 | 34 | public byte[] encode(FieldElement x) { 35 | return encode(((BigIntegerFieldElement)x).bi.and(mask)); 36 | } 37 | 38 | /** 39 | * Convert $x$ to little endian. 40 | * Constant time. 41 | * 42 | * @param x the BigInteger value to encode 43 | * @return array of length $b/8$ 44 | * @throws IllegalStateException if field not set 45 | */ 46 | public byte[] encode(BigInteger x) { 47 | if (f == null) 48 | throw new IllegalStateException("field not set"); 49 | byte[] in = x.toByteArray(); 50 | byte[] out = new byte[f.getb()/8]; 51 | for (int i = 0; i < in.length; i++) { 52 | out[i] = in[in.length-1-i]; 53 | } 54 | for (int i = in.length; i < out.length; i++) { 55 | out[i] = 0; 56 | } 57 | return out; 58 | } 59 | 60 | /** 61 | * Decode a FieldElement from its $(b-1)$-bit encoding. 62 | * The highest bit is masked out. 63 | * 64 | * @param in the $(b-1)$-bit encoding of a FieldElement. 65 | * @return the FieldElement represented by 'val'. 66 | * @throws IllegalStateException if field not set 67 | * @throws IllegalArgumentException if encoding is invalid 68 | */ 69 | public FieldElement decode(byte[] in) { 70 | if (f == null) 71 | throw new IllegalStateException("field not set"); 72 | if (in.length != f.getb()/8) 73 | throw new IllegalArgumentException("Not a valid encoding"); 74 | return new BigIntegerFieldElement(f, toBigInteger(in).and(mask)); 75 | } 76 | 77 | /** 78 | * Convert in to big endian 79 | * 80 | * @param in the $(b-1)$-bit encoding of a FieldElement. 81 | * @return the decoded value as a BigInteger 82 | */ 83 | public BigInteger toBigInteger(byte[] in) { 84 | byte[] out = new byte[in.length]; 85 | for (int i = 0; i < in.length; i++) { 86 | out[i] = in[in.length-1-i]; 87 | } 88 | return new BigInteger(1, out); 89 | } 90 | 91 | /** 92 | * From the Ed25519 paper:
93 | * $x$ is negative if the $(b-1)$-bit encoding of $x$ is lexicographically larger 94 | * than the $(b-1)$-bit encoding of $-x$. If $q$ is an odd prime and the encoding 95 | * is the little-endian representation of $\{0, 1,\dots, q-1\}$ then the negative 96 | * elements of $F_q$ are $\{1, 3, 5,\dots, q-2\}$. 97 | * @return true if negative 98 | */ 99 | public boolean isNegative(FieldElement x) { 100 | return ((BigIntegerFieldElement)x).bi.testBit(0); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/math/bigint/BigIntegerScalarOps.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math.bigint; 13 | 14 | import java.math.BigInteger; 15 | 16 | import net.i2p.crypto.eddsa.math.Field; 17 | import net.i2p.crypto.eddsa.math.ScalarOps; 18 | 19 | public class BigIntegerScalarOps implements ScalarOps { 20 | private final BigInteger l; 21 | private final BigIntegerLittleEndianEncoding enc; 22 | 23 | public BigIntegerScalarOps(Field f, BigInteger l) { 24 | this.l = l; 25 | enc = new BigIntegerLittleEndianEncoding(); 26 | enc.setField(f); 27 | } 28 | 29 | public byte[] reduce(byte[] s) { 30 | return enc.encode(enc.toBigInteger(s).mod(l)); 31 | } 32 | 33 | public byte[] multiplyAndAdd(byte[] a, byte[] b, byte[] c) { 34 | return enc.encode(enc.toBigInteger(a).multiply(enc.toBigInteger(b)).add(enc.toBigInteger(c)).mod(l)); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/math/bigint/package.html: -------------------------------------------------------------------------------- 1 | 2 |

3 | Low-level, non-optimized implementation using BigIntegers for any curve. 4 | See the ed25519 implementation for Curve 25519. 5 |

6 | 7 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/math/ed25519/Ed25519LittleEndianEncoding.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math.ed25519; 13 | 14 | import net.i2p.crypto.eddsa.math.*; 15 | 16 | /** 17 | * Helper class for encoding/decoding from/to the 32 byte representation. 18 | *

19 | * Reviewed/commented by Bloody Rookie (nemproject@gmx.de) 20 | */ 21 | public class Ed25519LittleEndianEncoding extends Encoding { 22 | /** 23 | * Encodes a given field element in its 32 byte representation. This is done in two steps: 24 | *

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

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

30 | *

Assumption:

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

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

38 | *

Proof:

39 | *

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

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

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

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

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

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

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

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

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

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

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

244 | * Preconditions: 245 | *

    246 | *
  • $|x|$ bounded by $1.1*2^{26},1.1*2^{25},1.1*2^{26},1.1*2^{25}$, etc. 247 | *
248 | * 249 | * @return true if $x$ is in $\{1,3,5,\dots,q-2\}$, false otherwise. 250 | */ 251 | public boolean isNegative(FieldElement x) { 252 | byte[] s = encode(x); 253 | return (s[0] & 1) != 0; 254 | } 255 | 256 | } 257 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/math/ed25519/package.html: -------------------------------------------------------------------------------- 1 | 2 |

3 | Low-level, optimized implementation using Radix $2^{51}$ for Curve 25519. 4 | See the bigint implementation for other curves. 5 |

6 | 7 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/math/package.html: -------------------------------------------------------------------------------- 1 | 2 |

3 | Data structures that definie curves and fields, and 4 | the mathematical operaions on them. 5 |

6 | Low-level implementation is in bigint for any curve using BigIntegers, 7 | and in ed25519 for Curve 25519 using Radix $2^{51}$. 8 |

9 | 10 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/package.html: -------------------------------------------------------------------------------- 1 | 2 |

3 | Java implementation of EdDSA, a digital signature scheme using 4 | a variant of elliptic curve cryptography based on Twisted Edwards curves. 5 |

6 | Contains a generic implementation for any curve using BigIntegers, 7 | and an optimized implementation for Curve 25519 using Radix 2^51. 8 |

9 | 10 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/spec/EdDSAGenParameterSpec.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.spec; 13 | 14 | import java.security.spec.AlgorithmParameterSpec; 15 | 16 | /** 17 | * Implementation of AlgorithmParameterSpec that holds the name of a named 18 | * EdDSA curve specification. 19 | * @author str4d 20 | * 21 | */ 22 | public class EdDSAGenParameterSpec implements AlgorithmParameterSpec { 23 | private final String name; 24 | 25 | public EdDSAGenParameterSpec(String stdName) { 26 | name = stdName; 27 | } 28 | 29 | public String getName() { 30 | return name; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/spec/EdDSANamedCurveSpec.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.spec; 13 | 14 | import net.i2p.crypto.eddsa.math.Curve; 15 | import net.i2p.crypto.eddsa.math.GroupElement; 16 | import net.i2p.crypto.eddsa.math.ScalarOps; 17 | 18 | /** 19 | * EdDSA Curve specification that can also be referred to by name. 20 | * @author str4d 21 | * 22 | */ 23 | public class EdDSANamedCurveSpec extends EdDSAParameterSpec { 24 | private final String name; 25 | 26 | public EdDSANamedCurveSpec(String name, Curve curve, 27 | String hashAlgo, ScalarOps sc, GroupElement B) { 28 | super(curve, hashAlgo, sc, B); 29 | this.name = name; 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/spec/EdDSANamedCurveTable.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.spec; 13 | 14 | import java.util.HashMap; 15 | import java.util.Locale; 16 | 17 | import net.i2p.crypto.eddsa.Utils; 18 | import net.i2p.crypto.eddsa.math.Curve; 19 | import net.i2p.crypto.eddsa.math.Field; 20 | import net.i2p.crypto.eddsa.math.ed25519.Ed25519LittleEndianEncoding; 21 | import net.i2p.crypto.eddsa.math.ed25519.Ed25519ScalarOps; 22 | 23 | /** 24 | * The named EdDSA curves. 25 | * @author str4d 26 | * 27 | */ 28 | public class EdDSANamedCurveTable { 29 | public static final String ED_25519 = "Ed25519"; 30 | 31 | private static final Field ed25519field = new Field( 32 | 256, // b 33 | Utils.hexToBytes("edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), // q 34 | new Ed25519LittleEndianEncoding()); 35 | 36 | private static final Curve ed25519curve = new Curve(ed25519field, 37 | Utils.hexToBytes("a3785913ca4deb75abd841414d0a700098e879777940c78c73fe6f2bee6c0352"), // d 38 | ed25519field.fromByteArray(Utils.hexToBytes("b0a00e4a271beec478e42fad0618432fa7d7fb3d99004d2b0bdfc14f8024832b"))); // I 39 | 40 | public static final EdDSANamedCurveSpec ED_25519_CURVE_SPEC = new EdDSANamedCurveSpec( 41 | ED_25519, 42 | ed25519curve, 43 | "SHA-512", // H 44 | new Ed25519ScalarOps(), // l 45 | ed25519curve.createPoint( // B 46 | Utils.hexToBytes("5866666666666666666666666666666666666666666666666666666666666666"), 47 | true)); // Precompute tables for B 48 | 49 | private static volatile HashMap curves = new HashMap(); 50 | 51 | private static synchronized void putCurve(String name, EdDSANamedCurveSpec curve) { 52 | HashMap newCurves = new HashMap(curves); 53 | newCurves.put(name, curve); 54 | curves = newCurves; 55 | } 56 | 57 | public static void defineCurve(EdDSANamedCurveSpec curve) { 58 | putCurve(curve.getName().toLowerCase(Locale.ENGLISH), curve); 59 | } 60 | 61 | static void defineCurveAlias(String name, String alias) { 62 | EdDSANamedCurveSpec curve = curves.get(name.toLowerCase(Locale.ENGLISH)); 63 | if (curve == null) { 64 | throw new IllegalStateException(); 65 | } 66 | putCurve(alias.toLowerCase(Locale.ENGLISH), curve); 67 | } 68 | 69 | static { 70 | // RFC 8032 71 | defineCurve(ED_25519_CURVE_SPEC); 72 | } 73 | 74 | public static EdDSANamedCurveSpec getByName(String name) { 75 | return curves.get(name.toLowerCase(Locale.ENGLISH)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/spec/EdDSAParameterSpec.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.spec; 13 | 14 | import java.security.MessageDigest; 15 | import java.security.NoSuchAlgorithmException; 16 | import java.security.spec.AlgorithmParameterSpec; 17 | 18 | import net.i2p.crypto.eddsa.math.Curve; 19 | import net.i2p.crypto.eddsa.math.GroupElement; 20 | import net.i2p.crypto.eddsa.math.ScalarOps; 21 | 22 | import java.io.Serializable; 23 | 24 | /** 25 | * Parameter specification for an EdDSA algorithm. 26 | * @author str4d 27 | * 28 | */ 29 | public class EdDSAParameterSpec implements AlgorithmParameterSpec, Serializable { 30 | private static final long serialVersionUID = 8274987108472012L; 31 | private final Curve curve; 32 | private final String hashAlgo; 33 | private final ScalarOps sc; 34 | private final GroupElement B; 35 | 36 | /** 37 | * @param curve the curve 38 | * @param hashAlgo the JCA string for the hash algorithm 39 | * @param sc the parameter L represented as ScalarOps 40 | * @param B the parameter B 41 | * @throws IllegalArgumentException if hash algorithm is unsupported or length is wrong 42 | */ 43 | public EdDSAParameterSpec(Curve curve, String hashAlgo, 44 | ScalarOps sc, GroupElement B) { 45 | try { 46 | MessageDigest hash = MessageDigest.getInstance(hashAlgo); 47 | // EdDSA hash function must produce 2b-bit output 48 | if (curve.getField().getb()/4 != hash.getDigestLength()) 49 | throw new IllegalArgumentException("Hash output is not 2b-bit"); 50 | } catch (NoSuchAlgorithmException e) { 51 | throw new IllegalArgumentException("Unsupported hash algorithm"); 52 | } 53 | 54 | this.curve = curve; 55 | this.hashAlgo = hashAlgo; 56 | this.sc = sc; 57 | this.B = B; 58 | } 59 | 60 | public Curve getCurve() { 61 | return curve; 62 | } 63 | 64 | public String getHashAlgorithm() { 65 | return hashAlgo; 66 | } 67 | 68 | public ScalarOps getScalarOps() { 69 | return sc; 70 | } 71 | 72 | /** 73 | * @return the base (generator) 74 | */ 75 | public GroupElement getB() { 76 | return B; 77 | } 78 | 79 | @Override 80 | public int hashCode() { 81 | return hashAlgo.hashCode() ^ 82 | curve.hashCode() ^ 83 | B.hashCode(); 84 | } 85 | 86 | @Override 87 | public boolean equals(Object o) { 88 | if (o == this) 89 | return true; 90 | if (!(o instanceof EdDSAParameterSpec)) 91 | return false; 92 | EdDSAParameterSpec s = (EdDSAParameterSpec) o; 93 | return hashAlgo.equals(s.getHashAlgorithm()) && 94 | curve.equals(s.getCurve()) && 95 | B.equals(s.getB()); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/spec/EdDSAPrivateKeySpec.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.spec; 13 | 14 | import java.security.MessageDigest; 15 | import java.security.NoSuchAlgorithmException; 16 | import java.security.spec.KeySpec; 17 | import java.util.Arrays; 18 | 19 | import net.i2p.crypto.eddsa.math.GroupElement; 20 | 21 | /** 22 | * @author str4d 23 | * 24 | */ 25 | public class EdDSAPrivateKeySpec implements KeySpec { 26 | private final byte[] seed; 27 | private final byte[] h; 28 | private final byte[] a; 29 | private final GroupElement A; 30 | private final EdDSAParameterSpec spec; 31 | 32 | /** 33 | * @param seed the private key 34 | * @param spec the parameter specification for this key 35 | * @throws IllegalArgumentException if seed length is wrong or hash algorithm is unsupported 36 | */ 37 | public EdDSAPrivateKeySpec(byte[] seed, EdDSAParameterSpec spec) { 38 | if (seed.length != spec.getCurve().getField().getb()/8) 39 | throw new IllegalArgumentException("seed length is wrong"); 40 | 41 | this.spec = spec; 42 | this.seed = seed; 43 | 44 | try { 45 | MessageDigest hash = MessageDigest.getInstance(spec.getHashAlgorithm()); 46 | int b = spec.getCurve().getField().getb(); 47 | 48 | // H(k) 49 | h = hash.digest(seed); 50 | 51 | /*a = BigInteger.valueOf(2).pow(b-2); 52 | for (int i=3;i<(b-2);i++) { 53 | a = a.add(BigInteger.valueOf(2).pow(i).multiply(BigInteger.valueOf(Utils.bit(h,i)))); 54 | }*/ 55 | // Saves ~0.4ms per key when running signing tests. 56 | // TODO: are these bitflips the same for any hash function? 57 | h[0] &= 248; 58 | h[(b/8)-1] &= 63; 59 | h[(b/8)-1] |= 64; 60 | a = Arrays.copyOfRange(h, 0, b/8); 61 | 62 | A = spec.getB().scalarMultiply(a); 63 | } catch (NoSuchAlgorithmException e) { 64 | throw new IllegalArgumentException("Unsupported hash algorithm"); 65 | } 66 | } 67 | 68 | /** 69 | * Initialize directly from the hash. 70 | * getSeed() will return null if this constructor is used. 71 | * 72 | * @param spec the parameter specification for this key 73 | * @param h the private key 74 | * @throws IllegalArgumentException if hash length is wrong 75 | * @since 0.1.1 76 | */ 77 | public EdDSAPrivateKeySpec(EdDSAParameterSpec spec, byte[] h) { 78 | if (h.length != spec.getCurve().getField().getb()/4) 79 | throw new IllegalArgumentException("hash length is wrong"); 80 | 81 | this.seed = null; 82 | this.h = h; 83 | this.spec = spec; 84 | int b = spec.getCurve().getField().getb(); 85 | 86 | h[0] &= 248; 87 | h[(b/8)-1] &= 63; 88 | h[(b/8)-1] |= 64; 89 | a = Arrays.copyOfRange(h, 0, b/8); 90 | 91 | A = spec.getB().scalarMultiply(a); 92 | } 93 | 94 | public EdDSAPrivateKeySpec(byte[] seed, byte[] h, byte[] a, GroupElement A, EdDSAParameterSpec spec) { 95 | this.seed = seed; 96 | this.h = h; 97 | this.a = a; 98 | this.A = A; 99 | this.spec = spec; 100 | } 101 | 102 | /** 103 | * @return will be null if constructed directly from the private key 104 | */ 105 | public byte[] getSeed() { 106 | return seed; 107 | } 108 | 109 | /** 110 | * @return the hash 111 | */ 112 | public byte[] getH() { 113 | return h; 114 | } 115 | 116 | /** 117 | * @return the private key 118 | */ 119 | public byte[] geta() { 120 | return a; 121 | } 122 | 123 | /** 124 | * @return the public key 125 | */ 126 | public GroupElement getA() { 127 | return A; 128 | } 129 | 130 | public EdDSAParameterSpec getParams() { 131 | return spec; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/spec/EdDSAPublicKeySpec.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.spec; 13 | 14 | import java.security.spec.KeySpec; 15 | 16 | import net.i2p.crypto.eddsa.math.GroupElement; 17 | 18 | /** 19 | * @author str4d 20 | * 21 | */ 22 | public class EdDSAPublicKeySpec implements KeySpec { 23 | private final GroupElement A; 24 | private GroupElement Aneg = null; 25 | private final EdDSAParameterSpec spec; 26 | 27 | /** 28 | * @param pk the public key 29 | * @param spec the parameter specification for this key 30 | * @throws IllegalArgumentException if key length is wrong 31 | */ 32 | public EdDSAPublicKeySpec(byte[] pk, EdDSAParameterSpec spec) { 33 | if (pk.length != spec.getCurve().getField().getb()/8) 34 | throw new IllegalArgumentException("public-key length is wrong"); 35 | 36 | this.A = new GroupElement(spec.getCurve(), pk); 37 | this.spec = spec; 38 | } 39 | 40 | public EdDSAPublicKeySpec(GroupElement A, EdDSAParameterSpec spec) { 41 | this.A = A; 42 | this.spec = spec; 43 | } 44 | 45 | public GroupElement getA() { 46 | return A; 47 | } 48 | 49 | public GroupElement getNegativeA() { 50 | // Only read Aneg once, otherwise read re-ordering might occur between here and return. Requires all GroupElement's fields to be final. 51 | GroupElement ourAneg = Aneg; 52 | if(ourAneg == null) { 53 | ourAneg = A.negate(); 54 | Aneg = ourAneg; 55 | } 56 | return ourAneg; 57 | } 58 | 59 | public EdDSAParameterSpec getParams() { 60 | return spec; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/net/i2p/crypto/eddsa/spec/package.html: -------------------------------------------------------------------------------- 1 | 2 |

3 | Specifications for curves and keys, and a table for named curves. 4 | Contains the following curves: 5 |

6 |
    7 |
  • "Ed25519"
  • 8 |
9 | 10 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/Ed25519TestVectors.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa; 13 | 14 | import java.io.BufferedReader; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.InputStreamReader; 18 | import java.util.ArrayList; 19 | import java.util.Collection; 20 | import java.util.List; 21 | 22 | public class Ed25519TestVectors { 23 | public static class TestTuple { 24 | public static int numCases; 25 | public int caseNum; 26 | public byte[] seed; 27 | public byte[] pk; 28 | public byte[] message; 29 | public byte[] sig; 30 | 31 | public TestTuple(String line) { 32 | caseNum = ++numCases; 33 | String[] x = line.split(":"); 34 | seed = Utils.hexToBytes(x[0].substring(0, 64)); 35 | pk = Utils.hexToBytes(x[1]); 36 | message = Utils.hexToBytes(x[2]); 37 | sig = Utils.hexToBytes(x[3].substring(0, 128)); 38 | } 39 | } 40 | 41 | public static Collection testCases = getTestData("test.data"); 42 | 43 | public static Collection getTestData(String fileName) { 44 | List testCases = new ArrayList(); 45 | BufferedReader file = null; 46 | try { 47 | InputStream is = Ed25519TestVectors.class.getResourceAsStream(fileName); 48 | if (is == null) 49 | throw new IOException("Resource not found: " + fileName); 50 | file = new BufferedReader(new InputStreamReader(is)); 51 | String line; 52 | while ((line = file.readLine()) != null) { 53 | testCases.add(new TestTuple(line)); 54 | } 55 | } catch (IOException e) { 56 | e.printStackTrace(); 57 | } finally { 58 | if (file != null) try { file.close(); } catch (IOException e) {} 59 | } 60 | return testCases; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/EdDSAEngineTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa; 13 | 14 | import static org.hamcrest.Matchers.equalTo; 15 | import static org.hamcrest.Matchers.is; 16 | import static org.junit.Assert.assertThat; 17 | 18 | import java.nio.charset.Charset; 19 | import java.security.MessageDigest; 20 | import java.security.PrivateKey; 21 | import java.security.PublicKey; 22 | import java.security.Signature; 23 | import java.security.SignatureException; 24 | 25 | import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; 26 | import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; 27 | import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; 28 | import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; 29 | 30 | import org.junit.Rule; 31 | import org.junit.Test; 32 | import org.junit.rules.ExpectedException; 33 | import sun.security.util.DerValue; 34 | import sun.security.x509.X509Key; 35 | 36 | /** 37 | * @author str4d 38 | * 39 | */ 40 | public class EdDSAEngineTest { 41 | static final byte[] TEST_SEED = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000"); 42 | static final byte[] TEST_PK = Utils.hexToBytes("3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29"); 43 | static final byte[] TEST_MSG = "This is a secret message".getBytes(Charset.forName("UTF-8")); 44 | static final byte[] TEST_MSG_SIG = Utils.hexToBytes("94825896c7075c31bcb81f06dba2bdcd9dcf16e79288d4b9f87c248215c8468d475f429f3de3b4a2cf67fe17077ae19686020364d6d4fa7a0174bab4a123ba0f"); 45 | 46 | @Rule 47 | public ExpectedException exception = ExpectedException.none(); 48 | 49 | @Test 50 | public void testSign() throws Exception { 51 | EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 52 | //Signature sgr = Signature.getInstance("EdDSA", "I2P"); 53 | Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm())); 54 | 55 | for (Ed25519TestVectors.TestTuple testCase : Ed25519TestVectors.testCases) { 56 | EdDSAPrivateKeySpec privKey = new EdDSAPrivateKeySpec(testCase.seed, spec); 57 | PrivateKey sKey = new EdDSAPrivateKey(privKey); 58 | sgr.initSign(sKey); 59 | 60 | sgr.update(testCase.message); 61 | 62 | assertThat("Test case " + testCase.caseNum + " failed", 63 | sgr.sign(), is(equalTo(testCase.sig))); 64 | } 65 | } 66 | 67 | @Test 68 | public void testVerify() throws Exception { 69 | EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 70 | //Signature sgr = Signature.getInstance("EdDSA", "I2P"); 71 | Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm())); 72 | for (Ed25519TestVectors.TestTuple testCase : Ed25519TestVectors.testCases) { 73 | EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(testCase.pk, spec); 74 | PublicKey vKey = new EdDSAPublicKey(pubKey); 75 | sgr.initVerify(vKey); 76 | 77 | sgr.update(testCase.message); 78 | 79 | assertThat("Test case " + testCase.caseNum + " failed", 80 | sgr.verify(testCase.sig), is(true)); 81 | } 82 | } 83 | 84 | /** 85 | * Checks that a wrong-length signature throws an IAE. 86 | */ 87 | @Test 88 | public void testVerifyWrongSigLength() throws Exception { 89 | EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 90 | //Signature sgr = Signature.getInstance("EdDSA", "I2P"); 91 | Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm())); 92 | EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(TEST_PK, spec); 93 | PublicKey vKey = new EdDSAPublicKey(pubKey); 94 | sgr.initVerify(vKey); 95 | 96 | sgr.update(TEST_MSG); 97 | 98 | exception.expect(SignatureException.class); 99 | exception.expectMessage("signature length is wrong"); 100 | sgr.verify(new byte[] {0}); 101 | } 102 | 103 | @Test 104 | public void testSignResetsForReuse() throws Exception { 105 | EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 106 | Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm())); 107 | EdDSAPrivateKeySpec privKey = new EdDSAPrivateKeySpec(TEST_SEED, spec); 108 | PrivateKey sKey = new EdDSAPrivateKey(privKey); 109 | sgr.initSign(sKey); 110 | 111 | // First usage 112 | sgr.update(new byte[] {0}); 113 | sgr.sign(); 114 | 115 | // Second usage 116 | sgr.update(TEST_MSG); 117 | assertThat("Second sign failed", sgr.sign(), is(equalTo(TEST_MSG_SIG))); 118 | } 119 | 120 | @Test 121 | public void testVerifyResetsForReuse() throws Exception { 122 | EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 123 | Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm())); 124 | EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(TEST_PK, spec); 125 | PublicKey vKey = new EdDSAPublicKey(pubKey); 126 | sgr.initVerify(vKey); 127 | 128 | // First usage 129 | sgr.update(new byte[] {0}); 130 | sgr.verify(TEST_MSG_SIG); 131 | 132 | // Second usage 133 | sgr.update(TEST_MSG); 134 | assertThat("Second verify failed", sgr.verify(TEST_MSG_SIG), is(true)); 135 | } 136 | 137 | @Test 138 | public void testSignOneShotMode() throws Exception { 139 | EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 140 | Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm())); 141 | EdDSAPrivateKeySpec privKey = new EdDSAPrivateKeySpec(TEST_SEED, spec); 142 | PrivateKey sKey = new EdDSAPrivateKey(privKey); 143 | sgr.initSign(sKey); 144 | sgr.setParameter(EdDSAEngine.ONE_SHOT_MODE); 145 | 146 | sgr.update(TEST_MSG); 147 | 148 | assertThat("One-shot mode sign failed", sgr.sign(), is(equalTo(TEST_MSG_SIG))); 149 | } 150 | 151 | @Test 152 | public void testVerifyOneShotMode() throws Exception { 153 | EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 154 | Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm())); 155 | EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(TEST_PK, spec); 156 | PublicKey vKey = new EdDSAPublicKey(pubKey); 157 | sgr.initVerify(vKey); 158 | sgr.setParameter(EdDSAEngine.ONE_SHOT_MODE); 159 | 160 | sgr.update(TEST_MSG); 161 | 162 | assertThat("One-shot mode verify failed", sgr.verify(TEST_MSG_SIG), is(true)); 163 | } 164 | 165 | @Test 166 | public void testSignOneShotModeMultipleUpdates() throws Exception { 167 | EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 168 | Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm())); 169 | EdDSAPrivateKeySpec privKey = new EdDSAPrivateKeySpec(TEST_SEED, spec); 170 | PrivateKey sKey = new EdDSAPrivateKey(privKey); 171 | sgr.initSign(sKey); 172 | sgr.setParameter(EdDSAEngine.ONE_SHOT_MODE); 173 | 174 | sgr.update(TEST_MSG); 175 | 176 | exception.expect(SignatureException.class); 177 | exception.expectMessage("update() already called"); 178 | sgr.update(TEST_MSG); 179 | } 180 | 181 | @Test 182 | public void testVerifyOneShotModeMultipleUpdates() throws Exception { 183 | EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 184 | EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(TEST_PK, spec); 185 | Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm())); 186 | PublicKey vKey = new EdDSAPublicKey(pubKey); 187 | sgr.initVerify(vKey); 188 | sgr.setParameter(EdDSAEngine.ONE_SHOT_MODE); 189 | 190 | sgr.update(TEST_MSG); 191 | 192 | exception.expect(SignatureException.class); 193 | exception.expectMessage("update() already called"); 194 | sgr.update(TEST_MSG); 195 | } 196 | 197 | @Test 198 | public void testSignOneShot() throws Exception { 199 | EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 200 | EdDSAPrivateKeySpec privKey = new EdDSAPrivateKeySpec(TEST_SEED, spec); 201 | EdDSAEngine sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm())); 202 | PrivateKey sKey = new EdDSAPrivateKey(privKey); 203 | sgr.initSign(sKey); 204 | 205 | assertThat("signOneShot() failed", sgr.signOneShot(TEST_MSG), is(equalTo(TEST_MSG_SIG))); 206 | } 207 | 208 | @Test 209 | public void testVerifyOneShot() throws Exception { 210 | EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 211 | EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(TEST_PK, spec); 212 | EdDSAEngine sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm())); 213 | PublicKey vKey = new EdDSAPublicKey(pubKey); 214 | sgr.initVerify(vKey); 215 | 216 | assertThat("verifyOneShot() failed", sgr.verifyOneShot(TEST_MSG, TEST_MSG_SIG), is(true)); 217 | } 218 | 219 | @Test 220 | public void testVerifyX509PublicKeyInfo() throws Exception { 221 | EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519"); 222 | Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm())); 223 | for (Ed25519TestVectors.TestTuple testCase : Ed25519TestVectors.testCases) { 224 | EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(testCase.pk, spec); 225 | PublicKey vKey = new EdDSAPublicKey(pubKey); 226 | PublicKey x509Key = X509Key.parse(new DerValue(vKey.getEncoded())); 227 | sgr.initVerify(x509Key); 228 | 229 | sgr.update(testCase.message); 230 | 231 | assertThat("Test case " + testCase.caseNum + " failed", 232 | sgr.verify(testCase.sig), is(true)); 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/EdDSAPrivateKeyTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa; 13 | 14 | import static org.hamcrest.Matchers.*; 15 | import static org.junit.Assert.*; 16 | 17 | import java.security.spec.PKCS8EncodedKeySpec; 18 | 19 | import net.i2p.crypto.eddsa.Utils; 20 | import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; 21 | 22 | import org.junit.Test; 23 | 24 | /** 25 | * @author str4d 26 | * 27 | */ 28 | public class EdDSAPrivateKeyTest { 29 | /** 30 | * The example private key MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC 31 | * from https://tools.ietf.org/html/rfc8410#section-10.3 32 | */ 33 | static final byte[] TEST_PRIVKEY = Utils.hexToBytes("302e020100300506032b657004220420d4ee72dbf913584ad5b6d8f1f769f8ad3afe7c28cbf1d4fbe097a88f44755842"); 34 | 35 | static final byte[] TEST_PRIVKEY_NULL_PARAMS = Utils.hexToBytes("3030020100300706032b6570050004220420d4ee72dbf913584ad5b6d8f1f769f8ad3afe7c28cbf1d4fbe097a88f44755842"); 36 | static final byte[] TEST_PRIVKEY_OLD = Utils.hexToBytes("302f020100300806032b65640a01010420d4ee72dbf913584ad5b6d8f1f769f8ad3afe7c28cbf1d4fbe097a88f44755842"); 37 | 38 | @Test 39 | public void testDecodeAndEncode() throws Exception { 40 | // Decode 41 | PKCS8EncodedKeySpec encoded = new PKCS8EncodedKeySpec(TEST_PRIVKEY); 42 | EdDSAPrivateKey keyIn = new EdDSAPrivateKey(encoded); 43 | 44 | // Encode 45 | EdDSAPrivateKeySpec decoded = new EdDSAPrivateKeySpec( 46 | keyIn.getSeed(), 47 | keyIn.getH(), 48 | keyIn.geta(), 49 | keyIn.getA(), 50 | keyIn.getParams()); 51 | EdDSAPrivateKey keyOut = new EdDSAPrivateKey(decoded); 52 | 53 | // Check 54 | assertThat(keyOut.getEncoded(), is(equalTo(TEST_PRIVKEY))); 55 | } 56 | 57 | @Test 58 | public void testDecodeWithNullAndEncode() throws Exception { 59 | // Decode 60 | PKCS8EncodedKeySpec encoded = new PKCS8EncodedKeySpec(TEST_PRIVKEY_NULL_PARAMS); 61 | EdDSAPrivateKey keyIn = new EdDSAPrivateKey(encoded); 62 | 63 | // Encode 64 | EdDSAPrivateKeySpec decoded = new EdDSAPrivateKeySpec( 65 | keyIn.getSeed(), 66 | keyIn.getH(), 67 | keyIn.geta(), 68 | keyIn.getA(), 69 | keyIn.getParams()); 70 | EdDSAPrivateKey keyOut = new EdDSAPrivateKey(decoded); 71 | 72 | // Check 73 | assertThat(keyOut.getEncoded(), is(equalTo(TEST_PRIVKEY))); 74 | } 75 | 76 | @Test 77 | public void testReEncodeOldEncoding() throws Exception { 78 | // Decode 79 | PKCS8EncodedKeySpec encoded = new PKCS8EncodedKeySpec(TEST_PRIVKEY_OLD); 80 | EdDSAPrivateKey keyIn = new EdDSAPrivateKey(encoded); 81 | 82 | // Encode 83 | EdDSAPrivateKeySpec decoded = new EdDSAPrivateKeySpec( 84 | keyIn.getSeed(), 85 | keyIn.getH(), 86 | keyIn.geta(), 87 | keyIn.getA(), 88 | keyIn.getParams()); 89 | EdDSAPrivateKey keyOut = new EdDSAPrivateKey(decoded); 90 | 91 | // Check 92 | assertThat(keyOut.getEncoded(), is(equalTo(TEST_PRIVKEY))); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/EdDSAPublicKeyTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa; 13 | 14 | import static org.hamcrest.Matchers.*; 15 | import static org.junit.Assert.*; 16 | 17 | import java.security.spec.X509EncodedKeySpec; 18 | 19 | import net.i2p.crypto.eddsa.Utils; 20 | import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; 21 | 22 | import org.junit.Test; 23 | 24 | /** 25 | * @author str4d 26 | * 27 | */ 28 | public class EdDSAPublicKeyTest { 29 | /** 30 | * The example public key MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= 31 | * from https://tools.ietf.org/html/rfc8410#section-10.1 32 | */ 33 | static final byte[] TEST_PUBKEY = Utils.hexToBytes("302a300506032b657003210019bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1"); 34 | 35 | static final byte[] TEST_PUBKEY_NULL_PARAMS = Utils.hexToBytes("302c300706032b6570050003210019bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1"); 36 | static final byte[] TEST_PUBKEY_OLD = Utils.hexToBytes("302d300806032b65640a010103210019bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1"); 37 | 38 | @Test 39 | public void testDecodeAndEncode() throws Exception { 40 | // Decode 41 | X509EncodedKeySpec encoded = new X509EncodedKeySpec(TEST_PUBKEY); 42 | EdDSAPublicKey keyIn = new EdDSAPublicKey(encoded); 43 | 44 | // Encode 45 | EdDSAPublicKeySpec decoded = new EdDSAPublicKeySpec( 46 | keyIn.getA(), 47 | keyIn.getParams()); 48 | EdDSAPublicKey keyOut = new EdDSAPublicKey(decoded); 49 | 50 | // Check 51 | assertThat(keyOut.getEncoded(), is(equalTo(TEST_PUBKEY))); 52 | } 53 | 54 | @Test 55 | public void testDecodeWithNullAndEncode() throws Exception { 56 | // Decode 57 | X509EncodedKeySpec encoded = new X509EncodedKeySpec(TEST_PUBKEY_NULL_PARAMS); 58 | EdDSAPublicKey keyIn = new EdDSAPublicKey(encoded); 59 | 60 | // Encode 61 | EdDSAPublicKeySpec decoded = new EdDSAPublicKeySpec( 62 | keyIn.getA(), 63 | keyIn.getParams()); 64 | EdDSAPublicKey keyOut = new EdDSAPublicKey(decoded); 65 | 66 | // Check 67 | assertThat(keyOut.getEncoded(), is(equalTo(TEST_PUBKEY))); 68 | } 69 | 70 | @Test 71 | public void testReEncodeOldEncoding() throws Exception { 72 | // Decode 73 | X509EncodedKeySpec encoded = new X509EncodedKeySpec(TEST_PUBKEY_OLD); 74 | EdDSAPublicKey keyIn = new EdDSAPublicKey(encoded); 75 | 76 | // Encode 77 | EdDSAPublicKeySpec decoded = new EdDSAPublicKeySpec( 78 | keyIn.getA(), 79 | keyIn.getParams()); 80 | EdDSAPublicKey keyOut = new EdDSAPublicKey(decoded); 81 | 82 | // Check 83 | assertThat(keyOut.getEncoded(), is(equalTo(TEST_PUBKEY))); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/EdDSASecurityProviderTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa; 13 | 14 | import java.security.KeyFactory; 15 | import java.security.KeyPairGenerator; 16 | import java.security.NoSuchProviderException; 17 | import java.security.Security; 18 | import java.security.Signature; 19 | 20 | import net.i2p.crypto.eddsa.EdDSASecurityProvider; 21 | 22 | import org.junit.Rule; 23 | import org.junit.Test; 24 | import org.junit.rules.ExpectedException; 25 | 26 | /** 27 | * @author str4d 28 | * 29 | */ 30 | public class EdDSASecurityProviderTest { 31 | 32 | @Rule 33 | public ExpectedException exception = ExpectedException.none(); 34 | 35 | @Test 36 | public void canGetInstancesWhenProviderIsPresent() throws Exception { 37 | Security.addProvider(new EdDSASecurityProvider()); 38 | 39 | KeyPairGenerator.getInstance("EdDSA", "EdDSA"); 40 | KeyFactory.getInstance("EdDSA", "EdDSA"); 41 | Signature.getInstance("NONEwithEdDSA", "EdDSA"); 42 | 43 | Security.removeProvider("EdDSA"); 44 | } 45 | 46 | @Test 47 | public void cannotGetInstancesWhenProviderIsNotPresent() throws Exception { 48 | exception.expect(NoSuchProviderException.class); 49 | KeyPairGenerator.getInstance("EdDSA", "EdDSA"); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/UtilsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa; 13 | 14 | import org.hamcrest.core.IsEqual; 15 | import org.junit.*; 16 | 17 | import java.security.SecureRandom; 18 | 19 | import static org.hamcrest.Matchers.is; 20 | import static org.junit.Assert.assertThat; 21 | 22 | /** 23 | * @author str4d 24 | * additional test by the NEM project team. 25 | * 26 | */ 27 | public class UtilsTest { 28 | private static final String hex1 = "3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29"; 29 | private static final String hex2 = "47a3f5b71494bcd961f3a4e859a238d6eaf8e648746d2f56a89b5e236f98d45f"; 30 | private static final String hex3 = "5fd396e4a2b5dc9078f57e3ab5a87c28fd128e5f78cc4a97f4122dc45f6e4bb9"; 31 | private static final byte[] bytes1 = { 59, 106, 39, -68, -50, -74, -92, 45, 98, -93, -88, -48, 42, 111, 13, 115, 32 | 101, 50, 21, 119, 29, -30, 67, -90, 58, -64, 72, -95, -117, 89, -38, 41 }; 33 | private static final byte[] bytes2 = { 71, -93, -11, -73, 20, -108, -68, -39, 97, -13, -92, -24, 89, -94, 56, -42, 34 | -22, -8, -26, 72, 116, 109, 47, 86, -88, -101, 94, 35, 111, -104, -44, 95 }; 35 | private static final byte[] bytes3 = { 95, -45, -106, -28, -94, -75, -36, -112, 120, -11, 126, 58, -75, -88, 124, 40, 36 | -3, 18, -114, 95, 120, -52, 74, -105, -12, 18, 45, -60, 95, 110, 75, -71 }; 37 | 38 | /** 39 | * Test method for {@link net.i2p.crypto.eddsa.Utils#equal(int, int)}. 40 | */ 41 | @Test 42 | public void testIntEqual() { 43 | assertThat(Utils.equal(0, 0), is(1)); 44 | assertThat(Utils.equal(1, 1), is(1)); 45 | assertThat(Utils.equal(1, 0), is(0)); 46 | assertThat(Utils.equal(1, 127), is(0)); 47 | assertThat(Utils.equal(-127, 127), is(0)); 48 | assertThat(Utils.equal(-42, -42), is(1)); 49 | assertThat(Utils.equal(255, 255), is(1)); 50 | assertThat(Utils.equal(-255, -256), is(0)); 51 | } 52 | 53 | @Test 54 | public void equalsReturnsOneForEqualByteArrays() { 55 | final SecureRandom random = new SecureRandom(); 56 | final byte[] bytes1 = new byte[32]; 57 | final byte[] bytes2 = new byte[32]; 58 | for (int i=0; i<100; i++) { 59 | random.nextBytes(bytes1); 60 | System.arraycopy(bytes1, 0, bytes2, 0, 32); 61 | Assert.assertThat(Utils.equal(bytes1, bytes2), IsEqual.equalTo(1)); 62 | } 63 | } 64 | 65 | @Test 66 | public void equalsReturnsZeroForUnequalByteArrays() { 67 | final SecureRandom random = new SecureRandom(); 68 | final byte[] bytes1 = new byte[32]; 69 | final byte[] bytes2 = new byte[32]; 70 | random.nextBytes(bytes1); 71 | for (int i=0; i<32; i++) { 72 | System.arraycopy(bytes1, 0, bytes2, 0, 32); 73 | bytes2[i] = (byte)(bytes2[i] ^ 0xff); 74 | Assert.assertThat(Utils.equal(bytes1, bytes2), IsEqual.equalTo(0)); 75 | } 76 | } 77 | 78 | /** 79 | * Test method for {@link net.i2p.crypto.eddsa.Utils#equal(byte[], byte[])}. 80 | */ 81 | @Test 82 | public void testByteArrayEqual() { 83 | byte[] zero = new byte[32]; 84 | byte[] one = new byte[32]; 85 | one[0] = 1; 86 | 87 | assertThat(Utils.equal(zero, zero), is(1)); 88 | assertThat(Utils.equal(one, one), is(1)); 89 | assertThat(Utils.equal(one, zero), is(0)); 90 | assertThat(Utils.equal(zero, one), is(0)); 91 | } 92 | 93 | /** 94 | * Test method for {@link net.i2p.crypto.eddsa.Utils#negative(int)}. 95 | */ 96 | @Test 97 | public void testNegative() { 98 | assertThat(Utils.negative(0), is(0)); 99 | assertThat(Utils.negative(1), is(0)); 100 | assertThat(Utils.negative(-1), is(1)); 101 | assertThat(Utils.negative(32), is(0)); 102 | assertThat(Utils.negative(-100), is(1)); 103 | assertThat(Utils.negative(127), is(0)); 104 | assertThat(Utils.negative(-255), is(1)); 105 | } 106 | 107 | /** 108 | * Test method for {@link net.i2p.crypto.eddsa.Utils#bit(byte[], int)}. 109 | */ 110 | @Test 111 | public void testBit() { 112 | assertThat(Utils.bit(new byte[] {0}, 0), is(0)); 113 | assertThat(Utils.bit(new byte[] {8}, 3), is(1)); 114 | assertThat(Utils.bit(new byte[] {1, 2, 3}, 9), is(1)); 115 | assertThat(Utils.bit(new byte[] {1, 2, 3}, 15), is(0)); 116 | assertThat(Utils.bit(new byte[] {1, 2, 3}, 16), is(1)); 117 | } 118 | 119 | @Test(expected = IllegalArgumentException.class) 120 | public void hexToBytesThrowsOnInvalidLengthHexString() { 121 | Utils.hexToBytes("bad"); 122 | } 123 | 124 | @Test 125 | public void hexToBytesReturnsCorrectByteArray() { 126 | Assert.assertThat(Utils.hexToBytes(hex1), IsEqual.equalTo(bytes1)); 127 | Assert.assertThat(Utils.hexToBytes(hex2), IsEqual.equalTo(bytes2)); 128 | Assert.assertThat(Utils.hexToBytes(hex3), IsEqual.equalTo(bytes3)); 129 | } 130 | 131 | @Test 132 | public void bytesToHexReturnsCorrectHexString() { 133 | Assert.assertThat(Utils.bytesToHex(bytes1), IsEqual.equalTo(hex1)); 134 | Assert.assertThat(Utils.bytesToHex(bytes2), IsEqual.equalTo(hex2)); 135 | Assert.assertThat(Utils.bytesToHex(bytes3), IsEqual.equalTo(hex3)); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/math/AbstractFieldElementTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math; 13 | 14 | import org.hamcrest.core.*; 15 | import org.junit.*; 16 | 17 | import java.math.BigInteger; 18 | 19 | /** 20 | * Tests rely on the BigInteger class. 21 | */ 22 | public abstract class AbstractFieldElementTest { 23 | 24 | protected abstract FieldElement getRandomFieldElement(); 25 | protected abstract BigInteger toBigInteger(FieldElement f); 26 | protected abstract BigInteger getQ(); 27 | protected abstract Field getField(); 28 | 29 | // region isNonZero 30 | 31 | protected abstract FieldElement getZeroFieldElement(); 32 | protected abstract FieldElement getNonZeroFieldElement(); 33 | 34 | @Test 35 | public void isNonZeroReturnsFalseIfFieldElementIsZero() { 36 | // Act: 37 | final FieldElement f = getZeroFieldElement(); 38 | 39 | // Assert: 40 | Assert.assertThat(f.isNonZero(), IsEqual.equalTo(false)); 41 | } 42 | 43 | @Test 44 | public void isNonZeroReturnsTrueIfFieldElementIsNonZero() { 45 | // Act: 46 | final FieldElement f = getNonZeroFieldElement(); 47 | 48 | // Assert: 49 | Assert.assertThat(f.isNonZero(), IsEqual.equalTo(true)); 50 | } 51 | 52 | // endregion 53 | 54 | // region mod q arithmetic 55 | 56 | @Test 57 | public void addReturnsCorrectResult() { 58 | for (int i=0; i<1000; i++) { 59 | // Arrange: 60 | final FieldElement f1 = getRandomFieldElement(); 61 | final FieldElement f2 = getRandomFieldElement(); 62 | final BigInteger b1 = toBigInteger(f1); 63 | final BigInteger b2 = toBigInteger(f2); 64 | 65 | // Act: 66 | final FieldElement f3 = f1.add(f2); 67 | final BigInteger b3 = toBigInteger(f3).mod(getQ()); 68 | 69 | // Assert: 70 | Assert.assertThat(b3, IsEqual.equalTo(b1.add(b2).mod(getQ()))); 71 | } 72 | } 73 | 74 | @Test 75 | public void subtractReturnsCorrectResult() { 76 | for (int i=0; i<1000; i++) { 77 | // Arrange: 78 | final FieldElement f1 = getRandomFieldElement(); 79 | final FieldElement f2 = getRandomFieldElement(); 80 | final BigInteger b1 = toBigInteger(f1); 81 | final BigInteger b2 = toBigInteger(f2); 82 | 83 | // Act: 84 | final FieldElement f3 = f1.subtract(f2); 85 | final BigInteger b3 = toBigInteger(f3).mod(getQ()); 86 | 87 | // Assert: 88 | Assert.assertThat(b3, IsEqual.equalTo(b1.subtract(b2).mod(getQ()))); 89 | } 90 | } 91 | 92 | @Test 93 | public void negateReturnsCorrectResult() { 94 | for (int i=0; i<1000; i++) { 95 | // Arrange: 96 | final FieldElement f1 = getRandomFieldElement(); 97 | final BigInteger b1 = toBigInteger(f1); 98 | 99 | // Act: 100 | final FieldElement f2 = f1.negate(); 101 | final BigInteger b2 = toBigInteger(f2).mod(getQ()); 102 | 103 | // Assert: 104 | Assert.assertThat(b2, IsEqual.equalTo(b1.negate().mod(getQ()))); 105 | } 106 | } 107 | 108 | @Test 109 | public void multiplyReturnsCorrectResult() { 110 | for (int i=0; i<1000; i++) { 111 | // Arrange: 112 | final FieldElement f1 = getRandomFieldElement(); 113 | final FieldElement f2 = getRandomFieldElement(); 114 | final BigInteger b1 = toBigInteger(f1); 115 | final BigInteger b2 = toBigInteger(f2); 116 | 117 | // Act: 118 | final FieldElement f3 = f1.multiply(f2); 119 | final BigInteger b3 = toBigInteger(f3).mod(getQ()); 120 | 121 | // Assert: 122 | Assert.assertThat(b3, IsEqual.equalTo(b1.multiply(b2).mod(getQ()))); 123 | } 124 | } 125 | 126 | @Test 127 | public void squareReturnsCorrectResult() { 128 | for (int i=0; i<1000; i++) { 129 | // Arrange: 130 | final FieldElement f1 = getRandomFieldElement(); 131 | final BigInteger b1 = toBigInteger(f1); 132 | 133 | // Act: 134 | final FieldElement f2 = f1.square(); 135 | final BigInteger b2 = toBigInteger(f2).mod(getQ()); 136 | 137 | // Assert: 138 | Assert.assertThat(b2, IsEqual.equalTo(b1.multiply(b1).mod(getQ()))); 139 | } 140 | } 141 | 142 | @Test 143 | public void squareAndDoubleReturnsCorrectResult() { 144 | for (int i=0; i<1000; i++) { 145 | // Arrange: 146 | final FieldElement f1 = getRandomFieldElement(); 147 | final BigInteger b1 = toBigInteger(f1); 148 | 149 | // Act: 150 | final FieldElement f2 = f1.squareAndDouble(); 151 | final BigInteger b2 = toBigInteger(f2).mod(getQ()); 152 | 153 | // Assert: 154 | Assert.assertThat(b2, IsEqual.equalTo(b1.multiply(b1).multiply(new BigInteger("2")).mod(getQ()))); 155 | } 156 | } 157 | 158 | @Test 159 | public void invertReturnsCorrectResult() { 160 | for (int i=0; i<1000; i++) { 161 | // Arrange: 162 | final FieldElement f1 = getRandomFieldElement(); 163 | final BigInteger b1 = toBigInteger(f1); 164 | 165 | // Act: 166 | final FieldElement f2 = f1.invert(); 167 | final BigInteger b2 = toBigInteger(f2).mod(getQ()); 168 | 169 | // Assert: 170 | Assert.assertThat(b2, IsEqual.equalTo(b1.modInverse(getQ()))); 171 | } 172 | } 173 | 174 | @Test 175 | public void pow22523ReturnsCorrectResult() { 176 | for (int i=0; i<1000; i++) { 177 | // Arrange: 178 | final FieldElement f1 = getRandomFieldElement(); 179 | final BigInteger b1 = toBigInteger(f1); 180 | 181 | // Act: 182 | final FieldElement f2 = f1.pow22523(); 183 | final BigInteger b2 = toBigInteger(f2).mod(getQ()); 184 | 185 | // Assert: 186 | Assert.assertThat(b2, IsEqual.equalTo(b1.modPow(BigInteger.ONE.shiftLeft(252).subtract(new BigInteger("3")), getQ()))); 187 | } 188 | } 189 | 190 | // endregion 191 | 192 | // region cmov 193 | 194 | @Test 195 | public void cmovReturnsCorrectResult() { 196 | final FieldElement zero = getZeroFieldElement(); 197 | final FieldElement nz = getNonZeroFieldElement(); 198 | final FieldElement f = getRandomFieldElement(); 199 | 200 | Assert.assertThat(zero.cmov(nz, 0), IsEqual.equalTo(zero)); 201 | Assert.assertThat(zero.cmov(nz, 1), IsEqual.equalTo(nz)); 202 | 203 | Assert.assertThat(f.cmov(nz, 0), IsEqual.equalTo(f)); 204 | Assert.assertThat(f.cmov(nz, 1), IsEqual.equalTo(nz)); 205 | } 206 | 207 | // endregion 208 | 209 | // region hashCode / equals 210 | 211 | @Test 212 | public void equalsOnlyReturnsTrueForEquivalentObjects() { 213 | // Arrange: 214 | final FieldElement f1 = getRandomFieldElement(); 215 | final FieldElement f2 = getField().getEncoding().decode(f1.toByteArray()); 216 | final FieldElement f3 = getRandomFieldElement(); 217 | final FieldElement f4 = getRandomFieldElement(); 218 | 219 | // Assert: 220 | Assert.assertThat(f1, IsEqual.equalTo(f2)); 221 | Assert.assertThat(f1, IsNot.not(IsEqual.equalTo(f3))); 222 | Assert.assertThat(f1, IsNot.not(IsEqual.equalTo(f4))); 223 | Assert.assertThat(f3, IsNot.not(IsEqual.equalTo(f4))); 224 | } 225 | 226 | @Test 227 | public void hashCodesAreEqualForEquivalentObjects() { 228 | // Arrange: 229 | final FieldElement f1 = getRandomFieldElement(); 230 | final FieldElement f2 = getField().getEncoding().decode(f1.toByteArray()); 231 | final FieldElement f3 = getRandomFieldElement(); 232 | final FieldElement f4 = getRandomFieldElement(); 233 | 234 | // Assert: 235 | Assert.assertThat(f1.hashCode(), IsEqual.equalTo(f2.hashCode())); 236 | Assert.assertThat(f1.hashCode(), IsNot.not(IsEqual.equalTo(f3.hashCode()))); 237 | Assert.assertThat(f1.hashCode(), IsNot.not(IsEqual.equalTo(f4.hashCode()))); 238 | Assert.assertThat(f3.hashCode(), IsNot.not(IsEqual.equalTo(f4.hashCode()))); 239 | } 240 | 241 | // endregion 242 | } 243 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/math/ConstantsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math; 13 | 14 | import static org.hamcrest.Matchers.equalTo; 15 | import static org.hamcrest.Matchers.greaterThanOrEqualTo; 16 | import static org.hamcrest.Matchers.is; 17 | import static org.junit.Assert.assertThat; 18 | import static org.junit.Assert.fail; 19 | 20 | import java.security.MessageDigest; 21 | import java.security.NoSuchAlgorithmException; 22 | 23 | import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec; 24 | import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; 25 | 26 | import org.junit.Test; 27 | 28 | /** 29 | * Based on the tests in checkparams.py from the Python Ed25519 implementation. 30 | * @author str4d 31 | * 32 | */ 33 | public class ConstantsTest { 34 | static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 35 | static final Curve curve = ed25519.getCurve(); 36 | 37 | static final FieldElement ZERO = curve.getField().ZERO; 38 | static final FieldElement ONE = curve.getField().ONE; 39 | static final FieldElement TWO = curve.getField().TWO; 40 | 41 | static final GroupElement P3_ZERO = GroupElement.p3(curve, ZERO, ONE, ONE, ZERO); 42 | 43 | @Test 44 | public void testb() { 45 | int b = curve.getField().getb(); 46 | assertThat(b, is(greaterThanOrEqualTo(10))); 47 | try { 48 | MessageDigest h = MessageDigest.getInstance(ed25519.getHashAlgorithm()); 49 | assertThat(8 * h.getDigestLength(), is(equalTo(2 * b))); 50 | } catch (NoSuchAlgorithmException e) { 51 | fail(e.getMessage()); 52 | } 53 | } 54 | 55 | /*@Test 56 | public void testq() { 57 | FieldElement q = curve.getField().getQ(); 58 | assertThat(TWO.modPow(q.subtractOne(), q), is(equalTo(ONE))); 59 | assertThat(q.mod(curve.getField().FOUR), is(equalTo(ONE))); 60 | } 61 | 62 | @Test 63 | public void testl() { 64 | int b = curve.getField().getb(); 65 | BigInteger l = ed25519.getL(); 66 | assertThat(TWO.modPow(l.subtract(BigInteger.ONE), l), is(equalTo(ONE))); 67 | assertThat(l, is(greaterThanOrEqualTo(BigInteger.valueOf(2).pow(b-4)))); 68 | assertThat(l, is(lessThanOrEqualTo(BigInteger.valueOf(2).pow(b-3)))); 69 | } 70 | 71 | @Test 72 | public void testd() { 73 | FieldElement q = curve.getField().getQ(); 74 | FieldElement qm1 = q.subtractOne(); 75 | assertThat(curve.getD().modPow(qm1.divide(curve.getField().TWO), q), is(equalTo(qm1))); 76 | } 77 | 78 | @Test 79 | public void testI() { 80 | FieldElement q = curve.getField().getQ(); 81 | assertThat(curve.getI().modPow(curve.getField().TWO, q), is(equalTo(q.subtractOne()))); 82 | }*/ 83 | 84 | @Test 85 | public void testB() { 86 | GroupElement B = ed25519.getB(); 87 | assertThat(B.isOnCurve(curve), is(true)); 88 | //assertThat(B.scalarMultiply(new BigIntegerLittleEndianEncoding().encode(ed25519.getL(), curve.getField().getb()/8)), is(equalTo(P3_ZERO))); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/math/MathUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math; 13 | 14 | import net.i2p.crypto.eddsa.Utils; 15 | import net.i2p.crypto.eddsa.math.ed25519.*; 16 | import net.i2p.crypto.eddsa.spec.*; 17 | import org.hamcrest.core.IsEqual; 18 | import org.junit.*; 19 | 20 | import java.math.BigInteger; 21 | import java.security.SecureRandom; 22 | 23 | /** 24 | * Utility class to help with calculations. 25 | */ 26 | public class MathUtils { 27 | private static final int[] exponents = {0, 26, 26 + 25, 2*26 + 25, 2*26 + 2*25, 3*26 + 2*25, 3*26 + 3*25, 4*26 + 3*25, 4*26 + 4*25, 5*26 + 4*25}; 28 | private static final SecureRandom random = new SecureRandom(); 29 | private static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 30 | private static final Curve curve = ed25519.getCurve(); 31 | private static final BigInteger d = new BigInteger("-121665").multiply(new BigInteger("121666").modInverse(getQ())); 32 | private static final BigInteger groupOrder = BigInteger.ONE.shiftLeft(252).add(new BigInteger("27742317777372353535851937790883648493")); 33 | 34 | /** 35 | * Gets q = 2^255 - 19 as BigInteger. 36 | */ 37 | public static BigInteger getQ() { 38 | return new BigInteger("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed", 16); 39 | } 40 | 41 | /** 42 | * Gets group order = 2^252 + 27742317777372353535851937790883648493 as BigInteger. 43 | */ 44 | public static BigInteger getGroupOrder() { 45 | return groupOrder; 46 | } 47 | 48 | /** 49 | * Gets the underlying finite field with q=2^255 - 19 elements. 50 | * 51 | * @return The finite field. 52 | */ 53 | public static Field getField() { 54 | return new Field( 55 | 256, // b 56 | Utils.hexToBytes("edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), // q 57 | new Ed25519LittleEndianEncoding()); 58 | } 59 | 60 | // region field element 61 | 62 | /** 63 | * Converts a 2^25.5 bit representation to a BigInteger. 64 | *

65 | * Value: 2^exponents[0] * t[0] + 2^exponents[1] * t[1] + ... + 2^exponents[9] * t[9] 66 | * 67 | * @param t The 2^25.5 bit representation. 68 | * @return The BigInteger. 69 | */ 70 | public static BigInteger toBigInteger(final int[] t) { 71 | BigInteger b = BigInteger.ZERO; 72 | for (int i=0; i<10; i++) { 73 | b = b.add(BigInteger.ONE.multiply(BigInteger.valueOf(t[i])).shiftLeft(exponents[i])); 74 | } 75 | 76 | return b; 77 | } 78 | 79 | /** 80 | * Converts a 2^8 bit representation to a BigInteger. 81 | *

82 | * Value: bytes[0] + 2^8 * bytes[1] + ... 83 | * 84 | * @param bytes The 2^8 bit representation. 85 | * @return The BigInteger. 86 | */ 87 | public static BigInteger toBigInteger(final byte[] bytes) { 88 | BigInteger b = BigInteger.ZERO; 89 | for (int i=0; i= 0) { 124 | throw new RuntimeException("only numbers < 2^256 are allowed"); 125 | } 126 | final byte[] bytes = new byte[32]; 127 | final byte[] original = b.toByteArray(); 128 | 129 | // Although b < 2^256, original can have length > 32 with some bytes set to 0. 130 | final int offset = original.length > 32? original.length - 32 : 0; 131 | for (int i=0; i 152 | * a, b and c are given in 2^8 bit representation. 153 | * 154 | * @param a The first integer. 155 | * @param b The second integer. 156 | * @param c The third integer. 157 | * @return The mod group order reduced result. 158 | */ 159 | public static byte[] multiplyAndAddModGroupOrder(final byte[] a, final byte[] b, final byte[] c) { 160 | final BigInteger result = toBigInteger(a).multiply(toBigInteger(b)).add(toBigInteger(c)).mod(groupOrder); 161 | return toByteArray(result); 162 | } 163 | 164 | public static byte[] getRandomByteArray(final int length) { 165 | final byte[] bytes = new byte[length]; 166 | random.nextBytes(bytes); 167 | return bytes; 168 | } 169 | 170 | /** 171 | * Gets a random field element where |t[i]| <= 2^24 for 0 <= i <= 9. 172 | * 173 | * @return The field element. 174 | */ 175 | public static FieldElement getRandomFieldElement() { 176 | final int[] t = new int[10]; 177 | for (int j=0; j<10; j++) { 178 | t[j] = random.nextInt(1 << 25) - (1 << 24); 179 | } 180 | return new Ed25519FieldElement(getField(), t); 181 | } 182 | 183 | // endregion 184 | 185 | // region group element 186 | 187 | /** 188 | * Gets a random group element in P3 representation. 189 | * 190 | * @return The group element. 191 | */ 192 | public static GroupElement getRandomGroupElement() { return getRandomGroupElement(false); } 193 | 194 | /** 195 | * Gets a random group element in P3 representation, with precmp and dblPrecmp populated. 196 | * 197 | * @return The group element. 198 | */ 199 | public static GroupElement getRandomGroupElement(boolean precompute) { 200 | final byte[] bytes = new byte[32]; 201 | while (true) { 202 | try { 203 | random.nextBytes(bytes); 204 | return new GroupElement(curve, bytes, precompute); 205 | } catch (IllegalArgumentException e) { 206 | // Will fail in about 87.5%, so try again. 207 | } 208 | } 209 | } 210 | 211 | /** 212 | * Creates a group element from a byte array. 213 | *

214 | * Bit 0 to 254 are the affine y-coordinate, bit 255 is the sign of the affine x-coordinate. 215 | * 216 | * @param bytes the byte array. 217 | * @return The group element. 218 | */ 219 | public static GroupElement toGroupElement(final byte[] bytes) { 220 | final boolean shouldBeNegative = (bytes[31] >> 7) != 0; 221 | bytes[31] &= 0x7f; 222 | final BigInteger y = MathUtils.toBigInteger(bytes); 223 | 224 | // x = sign(x) * sqrt((y^2 - 1) / (d * y^2 + 1)) 225 | final BigInteger u = y.multiply(y).subtract(BigInteger.ONE).mod(getQ()); 226 | final BigInteger v = d.multiply(y).multiply(y).add(BigInteger.ONE).mod(getQ()); 227 | final BigInteger tmp = u.multiply(v.pow(7)).modPow(BigInteger.ONE.shiftLeft(252).subtract(new BigInteger("3")), getQ()).mod(getQ()); 228 | BigInteger x = tmp.multiply(u).multiply(v.pow(3)).mod(getQ()); 229 | if (!v.multiply(x).multiply(x).subtract(u).mod(getQ()).equals(BigInteger.ZERO)) { 230 | if (!v.multiply(x).multiply(x).add(u).mod(getQ()).equals(BigInteger.ZERO)) { 231 | throw new IllegalArgumentException("not a valid GroupElement"); 232 | } 233 | x = x.multiply(toBigInteger(curve.getI())).mod(getQ()); 234 | } 235 | final boolean isNegative = x.mod(new BigInteger("2")).equals(BigInteger.ONE); 236 | if ((shouldBeNegative && !isNegative) || (!shouldBeNegative && isNegative)) { 237 | x = x.negate().mod(getQ()); 238 | } 239 | 240 | return GroupElement.p3(curve, toFieldElement(x), toFieldElement(y), getField().ONE, toFieldElement(x.multiply(y).mod(getQ()))); 241 | } 242 | 243 | /** 244 | * Converts a group element from one representation to another. 245 | * This method is a helper used to test various methods in GroupElement. 246 | * 247 | * @param g The group element. 248 | * @param repr The desired representation. 249 | * @return The same group element in the new representation. 250 | */ 251 | public static GroupElement toRepresentation(final GroupElement g, final GroupElement.Representation repr) { 252 | BigInteger x; 253 | BigInteger y; 254 | final BigInteger gX = toBigInteger(g.getX().toByteArray()); 255 | final BigInteger gY = toBigInteger(g.getY().toByteArray()); 256 | final BigInteger gZ = toBigInteger(g.getZ().toByteArray()); 257 | final BigInteger gT = null == g.getT()? null : toBigInteger(g.getT().toByteArray()); 258 | 259 | // Switch to affine coordinates. 260 | switch (g.getRepresentation()) { 261 | case P2: 262 | case P3: 263 | case P3PrecomputedDouble: 264 | x = gX.multiply(gZ.modInverse(getQ())).mod(getQ()); 265 | y = gY.multiply(gZ.modInverse(getQ())).mod(getQ()); 266 | break; 267 | case P1P1: 268 | x = gX.multiply(gZ.modInverse(getQ())).mod(getQ()); 269 | y = gY.multiply(gT.modInverse(getQ())).mod(getQ()); 270 | break; 271 | case CACHED: 272 | x = gX.subtract(gY).multiply(gZ.multiply(new BigInteger("2")).modInverse(getQ())).mod(getQ()); 273 | y = gX.add(gY).multiply(gZ.multiply(new BigInteger("2")).modInverse(getQ())).mod(getQ()); 274 | break; 275 | case PRECOMP: 276 | x = gX.subtract(gY).multiply(new BigInteger("2").modInverse(getQ())).mod(getQ()); 277 | y = gX.add(gY).multiply(new BigInteger("2").modInverse(getQ())).mod(getQ()); 278 | break; 279 | default: 280 | throw new UnsupportedOperationException(); 281 | } 282 | 283 | // Now back to the desired representation. 284 | switch (repr) { 285 | case P2: 286 | return GroupElement.p2( 287 | curve, 288 | toFieldElement(x), 289 | toFieldElement(y), 290 | getField().ONE); 291 | case P3: 292 | return GroupElement.p3( 293 | curve, 294 | toFieldElement(x), 295 | toFieldElement(y), 296 | getField().ONE, 297 | toFieldElement(x.multiply(y).mod(getQ())), false); 298 | case P3PrecomputedDouble: 299 | return GroupElement.p3( 300 | curve, 301 | toFieldElement(x), 302 | toFieldElement(y), 303 | getField().ONE, 304 | toFieldElement(x.multiply(y).mod(getQ())), true); 305 | case P1P1: 306 | return GroupElement.p1p1( 307 | curve, 308 | toFieldElement(x), 309 | toFieldElement(y), 310 | getField().ONE, 311 | getField().ONE); 312 | case CACHED: 313 | return GroupElement.cached( 314 | curve, 315 | toFieldElement(y.add(x).mod(getQ())), 316 | toFieldElement(y.subtract(x).mod(getQ())), 317 | getField().ONE, 318 | toFieldElement(d.multiply(new BigInteger("2")).multiply(x).multiply(y).mod(getQ()))); 319 | case PRECOMP: 320 | return GroupElement.precomp( 321 | curve, 322 | toFieldElement(y.add(x).mod(getQ())), 323 | toFieldElement(y.subtract(x).mod(getQ())), 324 | toFieldElement(d.multiply(new BigInteger("2")).multiply(x).multiply(y).mod(getQ()))); 325 | default: 326 | throw new UnsupportedOperationException(); 327 | } 328 | } 329 | 330 | /** 331 | * Adds two group elements and returns the result in P3 representation. 332 | * It uses BigInteger arithmetic and the affine representation. 333 | * This method is a helper used to test the projective group addition formulas in GroupElement. 334 | * 335 | * @param g1 The first group element. 336 | * @param g2 The second group element. 337 | * @return The result of the addition. 338 | */ 339 | public static GroupElement addGroupElements(final GroupElement g1, final GroupElement g2) { 340 | // Relying on a special representation of the group elements. 341 | if ((g1.getRepresentation() != GroupElement.Representation.P2 && g1.getRepresentation() != GroupElement.Representation.P3) || 342 | (g2.getRepresentation() != GroupElement.Representation.P2 && g2.getRepresentation() != GroupElement.Representation.P3)) { 343 | throw new IllegalArgumentException("g1 and g2 must have representation P2 or P3"); 344 | } 345 | 346 | // Projective coordinates 347 | final BigInteger g1X = toBigInteger(g1.getX().toByteArray()); 348 | final BigInteger g1Y = toBigInteger(g1.getY().toByteArray()); 349 | final BigInteger g1Z = toBigInteger(g1.getZ().toByteArray()); 350 | final BigInteger g2X = toBigInteger(g2.getX().toByteArray()); 351 | final BigInteger g2Y = toBigInteger(g2.getY().toByteArray()); 352 | final BigInteger g2Z = toBigInteger(g2.getZ().toByteArray()); 353 | 354 | // Affine coordinates 355 | final BigInteger g1x = g1X.multiply(g1Z.modInverse(getQ())).mod(getQ()); 356 | final BigInteger g1y = g1Y.multiply(g1Z.modInverse(getQ())).mod(getQ()); 357 | final BigInteger g2x = g2X.multiply(g2Z.modInverse(getQ())).mod(getQ()); 358 | final BigInteger g2y = g2Y.multiply(g2Z.modInverse(getQ())).mod(getQ()); 359 | 360 | // Addition formula for affine coordinates. The formula is complete in our case. 361 | // 362 | // (x3, y3) = (x1, y1) + (x2, y2) where 363 | // 364 | // x3 = (x1 * y2 + x2 * y1) / (1 + d * x1 * x2 * y1 * y2) and 365 | // y3 = (x1 * x2 + y1 * y2) / (1 - d * x1 * x2 * y1 * y2) and 366 | // d = -121665/121666 367 | BigInteger dx1x2y1y2 = d.multiply(g1x).multiply(g2x).multiply(g1y).multiply(g2y).mod(getQ()); 368 | BigInteger x3 = g1x.multiply(g2y).add(g2x.multiply(g1y)) 369 | .multiply(BigInteger.ONE.add(dx1x2y1y2).modInverse(getQ())).mod(getQ()); 370 | BigInteger y3 = g1x.multiply(g2x).add(g1y.multiply(g2y)) 371 | .multiply(BigInteger.ONE.subtract(dx1x2y1y2).modInverse(getQ())).mod(getQ()); 372 | BigInteger t3 = x3.multiply(y3).mod(getQ()); 373 | 374 | return GroupElement.p3(g1.getCurve(), toFieldElement(x3), toFieldElement(y3), getField().ONE, toFieldElement(t3)); 375 | } 376 | 377 | /** 378 | * Doubles a group element and returns the result in P3 representation. 379 | * It uses BigInteger arithmetic and the affine representation. 380 | * This method is a helper used to test the projective group doubling formula in GroupElement. 381 | * 382 | * @param g The group element. 383 | * @return g+g. 384 | */ 385 | public static GroupElement doubleGroupElement(final GroupElement g) { 386 | return addGroupElements(g, g); 387 | } 388 | 389 | /** 390 | * Scalar multiply the group element by the field element. 391 | * 392 | * @param g The group element. 393 | * @param f The field element. 394 | * @return The resulting group element. 395 | */ 396 | public static GroupElement scalarMultiplyGroupElement(final GroupElement g, final FieldElement f) { 397 | final byte[] bytes = f.toByteArray(); 398 | GroupElement h = curve.getZero(GroupElement.Representation.P3); 399 | for (int i=254; i>=0; i--) { 400 | h = doubleGroupElement(h); 401 | if (Utils.bit(bytes, i) == 1) { 402 | h = addGroupElements(h, g); 403 | } 404 | } 405 | 406 | return h; 407 | } 408 | 409 | /** 410 | * Calculates f1 * g1 + f2 * g2. 411 | * 412 | * @param g1 The first group element. 413 | * @param f1 The first multiplier. 414 | * @param g2 The second group element. 415 | * @param f2 The second multiplier. 416 | * @return The resulting group element. 417 | */ 418 | public static GroupElement doubleScalarMultiplyGroupElements( 419 | final GroupElement g1, 420 | final FieldElement f1, 421 | final GroupElement g2, 422 | final FieldElement f2) { 423 | final GroupElement h1 = scalarMultiplyGroupElement(g1, f1); 424 | final GroupElement h2 = scalarMultiplyGroupElement(g2, f2); 425 | return addGroupElements(h1, h2); 426 | } 427 | 428 | /** 429 | * Negates a group element. 430 | * 431 | * @param g The group element. 432 | * @return The negated group element. 433 | */ 434 | public static GroupElement negateGroupElement(final GroupElement g) { 435 | if (g.getRepresentation() != GroupElement.Representation.P3) { 436 | throw new IllegalArgumentException("g must have representation P3"); 437 | } 438 | 439 | return GroupElement.p3(g.getCurve(), g.getX().negate(), g.getY(), g.getZ(), g.getT().negate()); 440 | } 441 | 442 | // Start TODO BR: Remove when finished! 443 | @Test 444 | public void mathUtilsWorkAsExpected() { 445 | final GroupElement neutral = GroupElement.p3(curve, curve.getField().ZERO, curve.getField().ONE, curve.getField().ONE, curve.getField().ZERO); 446 | for (int i=0; i<1000; i++) { 447 | final GroupElement g = getRandomGroupElement(); 448 | 449 | // Act: 450 | final GroupElement h1 = addGroupElements(g, neutral); 451 | final GroupElement h2 = addGroupElements(neutral, g); 452 | 453 | // Assert: 454 | Assert.assertThat(g, IsEqual.equalTo(h1)); 455 | Assert.assertThat(g, IsEqual.equalTo(h2)); 456 | } 457 | 458 | for (int i=0; i<1000; i++) { 459 | GroupElement g = getRandomGroupElement(); 460 | 461 | // P3 -> P2. 462 | GroupElement h = toRepresentation(g, GroupElement.Representation.P2); 463 | Assert.assertThat(h, IsEqual.equalTo(g)); 464 | // P3 -> P1P1. 465 | h = toRepresentation(g, GroupElement.Representation.P1P1); 466 | Assert.assertThat(g, IsEqual.equalTo(h)); 467 | 468 | // P3 -> CACHED. 469 | h = toRepresentation(g, GroupElement.Representation.CACHED); 470 | Assert.assertThat(h, IsEqual.equalTo(g)); 471 | 472 | // P3 -> P2 -> P3. 473 | g = toRepresentation(g, GroupElement.Representation.P2); 474 | h = toRepresentation(g, GroupElement.Representation.P3); 475 | Assert.assertThat(g, IsEqual.equalTo(h)); 476 | 477 | // P3 -> P2 -> P1P1. 478 | g = toRepresentation(g, GroupElement.Representation.P2); 479 | h = toRepresentation(g, GroupElement.Representation.P1P1); 480 | Assert.assertThat(g, IsEqual.equalTo(h)); 481 | } 482 | 483 | for (int i=0; i<10; i++) { 484 | // Arrange: 485 | final GroupElement g = MathUtils.getRandomGroupElement(); 486 | 487 | // Act: 488 | final GroupElement h = MathUtils.scalarMultiplyGroupElement(g, curve.getField().ZERO); 489 | 490 | // Assert: 491 | Assert.assertThat(curve.getZero(GroupElement.Representation.P3), IsEqual.equalTo(h)); 492 | } 493 | } 494 | // End TODO BR: Remove when finished! 495 | } 496 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/math/PrecomputationTestVectors.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math; 13 | 14 | import java.io.BufferedReader; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.InputStreamReader; 18 | 19 | import net.i2p.crypto.eddsa.Utils; 20 | import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec; 21 | import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; 22 | 23 | public class PrecomputationTestVectors { 24 | // Test files were generated using base.py and base2.py from ref10 25 | // (by printing hex(x%q) instead of the radix-255 representation). 26 | static GroupElement[][] testPrecmp = getPrecomputation("basePrecmp"); 27 | static GroupElement[] testDblPrecmp = getDoublePrecomputation("baseDblPrecmp"); 28 | 29 | public static GroupElement[][] getPrecomputation(String fileName) { 30 | EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 31 | Curve curve = ed25519.getCurve(); 32 | Field field = curve.getField(); 33 | GroupElement[][] precmp = new GroupElement[32][8]; 34 | BufferedReader file = null; 35 | int row = 0, col = 0; 36 | try { 37 | InputStream is = PrecomputationTestVectors.class.getResourceAsStream(fileName); 38 | if (is == null) 39 | throw new IOException("Resource not found: " + fileName); 40 | file = new BufferedReader(new InputStreamReader(is)); 41 | String line; 42 | while ((line = file.readLine()) != null) { 43 | if (line.equals(" },")) 44 | col += 1; 45 | else if (line.equals("},")) { 46 | col = 0; 47 | row += 1; 48 | } else if (line.startsWith(" { ")) { 49 | String ypxStr = line.substring(4, line.lastIndexOf(' ')); 50 | FieldElement ypx = field.fromByteArray( 51 | Utils.hexToBytes(ypxStr)); 52 | line = file.readLine(); 53 | String ymxStr = line.substring(4, line.lastIndexOf(' ')); 54 | FieldElement ymx = field.fromByteArray( 55 | Utils.hexToBytes(ymxStr)); 56 | line = file.readLine(); 57 | String xy2dStr = line.substring(4, line.lastIndexOf(' ')); 58 | FieldElement xy2d = field.fromByteArray( 59 | Utils.hexToBytes(xy2dStr)); 60 | precmp[row][col] = GroupElement.precomp(curve, 61 | ypx, ymx, xy2d); 62 | } 63 | } 64 | } catch (IOException e) { 65 | e.printStackTrace(); 66 | } finally { 67 | if (file != null) try { file.close(); } catch (IOException e) {} 68 | } 69 | return precmp; 70 | } 71 | 72 | public static GroupElement[] getDoublePrecomputation(String fileName) { 73 | EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 74 | Curve curve = ed25519.getCurve(); 75 | Field field = curve.getField(); 76 | GroupElement[] dblPrecmp = new GroupElement[8]; 77 | BufferedReader file = null; 78 | int row = 0; 79 | try { 80 | InputStream is = PrecomputationTestVectors.class.getResourceAsStream(fileName); 81 | if (is == null) 82 | throw new IOException("Resource not found: " + fileName); 83 | file = new BufferedReader(new InputStreamReader(is)); 84 | String line; 85 | while ((line = file.readLine()) != null) { 86 | if (line.equals(" },")) { 87 | row += 1; 88 | } else if (line.startsWith(" { ")) { 89 | String ypxStr = line.substring(4, line.lastIndexOf(' ')); 90 | FieldElement ypx = field.fromByteArray( 91 | Utils.hexToBytes(ypxStr)); 92 | line = file.readLine(); 93 | String ymxStr = line.substring(4, line.lastIndexOf(' ')); 94 | FieldElement ymx = field.fromByteArray( 95 | Utils.hexToBytes(ymxStr)); 96 | line = file.readLine(); 97 | String xy2dStr = line.substring(4, line.lastIndexOf(' ')); 98 | FieldElement xy2d = field.fromByteArray( 99 | Utils.hexToBytes(xy2dStr)); 100 | dblPrecmp[row] = GroupElement.precomp(curve, 101 | ypx, ymx, xy2d); 102 | } 103 | } 104 | } catch (IOException e) { 105 | e.printStackTrace(); 106 | } finally { 107 | if (file != null) try { file.close(); } catch (IOException e) {} 108 | } 109 | return dblPrecmp; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/math/baseDblPrecmp: -------------------------------------------------------------------------------- 1 | { 2 | { 853b8cf5c693bc2f190e8cfbc62d93cfc2423d6498480b2765bad4333a9dcf07 }, 3 | { 3e9140d70539109db3be40d1059f39fd098a8f683484c1a56712f898922ffd44 }, 4 | { 68aa7a870512c9ab9ec4aacc23e8d9268c5943ddcb7d1b5aa8650c9f687b116f }, 5 | }, 6 | { 7 | { 3097ee4ca8b025af8a4b86e830845a023267019f02501bc1f4f8809a1b4e167a }, 8 | { 65d2fca4e81f61567dbac1e5fd53d33bbdd64b211af3318162da5b558715b92a }, 9 | { 89d8d00d3f93ae1462da351c222394584cdbf28c45e570d1c6b4b912af26285a }, 10 | }, 11 | { 12 | { 33bba50844bc12a202ed5ec7c348508d44ecbf5a0ceb1bddeb06e246f1cc4529 }, 13 | { bad647a4c382917fb729274bd11400d587a064b81cf13ce3f3551beb737e4a15 }, 14 | { 85822a81f1dbbbbcfcd1bdd007080e272da7bd1b0b671bb49ab63b6b69beaa43 }, 15 | }, 16 | { 17 | { bfa34e94d05c1a6bd2c09db33a357074492e54288252b2717e923c2869ea1b46 }, 18 | { b12132aa9a2c6fbaa723ba3b5321a06c3a2c19924f76ea9de017532e5ddd6e1d }, 19 | { a2b3b801c86d83f19aa43e05475f03b3f3ad7758ba419c52a7900f6a1cbb9f7a }, 20 | }, 21 | { 22 | { 2f63a8a68a672e9bc546bc516f9e50a6b5f586c6c933b2ce597fdd8a33edb934 }, 23 | { 64809d037e216ef39b4120f5b681a09844b05ee708c6cb968f9cdcfa515ac049 }, 24 | { 1baf4590bfe8b4062fd219a7e883ffe216cfd49329fcf6aa068b001b0272c173 }, 25 | }, 26 | { 27 | { de2a808a8400bf2f272e3002cffed9e50634701771843e11af8f6d54e2aa7542 }, 28 | { 48438649025b5f318183087769b3d63e95eb8d6a5575a0a37fc7d5298059ab18 }, 29 | { e98960fdc52c2bd8a4e48232a1b41e0322861ab59911314448f93db52255c63d }, 30 | }, 31 | { 32 | { 6d7f00a222c270bfdbdebcb59ab384bf07ba07fb120e7a5341f246c3eed74f23 }, 33 | { 93bf7f323b016f506b6f779bc9ebfcae6859adaa32b2129da72460172d886702 }, 34 | { 78a32e7319a1605371d48ddfb1e6372433e5a791f837efa2637809aafda67b49 }, 35 | }, 36 | { 37 | { a0eacf1303ccce246d249c188dc24886d0d4f2c1fabdbd2d2be72df11729e261 }, 38 | { 0bcf8c4686cd0b04d610992aa49b82d39251b20708300875bf5ed01842cdb543 }, 39 | { 16b5d09b2f769a5deede3f374eaf38eb7042d6937d5a2e0342d8e40a21611d51 }, 40 | }, 41 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/math/bigint/BigIntegerFieldElementTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math.bigint; 13 | 14 | import static org.hamcrest.Matchers.*; 15 | import static org.junit.Assert.*; 16 | 17 | import java.math.BigInteger; 18 | import java.util.Random; 19 | 20 | import net.i2p.crypto.eddsa.Utils; 21 | import net.i2p.crypto.eddsa.math.Field; 22 | import net.i2p.crypto.eddsa.math.FieldElement; 23 | import net.i2p.crypto.eddsa.math.MathUtils; 24 | import net.i2p.crypto.eddsa.math.AbstractFieldElementTest; 25 | import org.junit.Test; 26 | 27 | /** 28 | * @author str4d 29 | * 30 | */ 31 | public class BigIntegerFieldElementTest extends AbstractFieldElementTest { 32 | static final byte[] BYTES_ZERO = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000"); 33 | static final byte[] BYTES_ONE = Utils.hexToBytes("0100000000000000000000000000000000000000000000000000000000000000"); 34 | static final byte[] BYTES_TEN = Utils.hexToBytes("0a00000000000000000000000000000000000000000000000000000000000000"); 35 | 36 | static final Field ed25519Field = new Field( 37 | 256, // b 38 | Utils.hexToBytes("edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), // q 39 | new BigIntegerLittleEndianEncoding()); 40 | 41 | static final FieldElement ZERO = new BigIntegerFieldElement(ed25519Field, BigInteger.ZERO); 42 | static final FieldElement ONE = new BigIntegerFieldElement(ed25519Field, BigInteger.ONE); 43 | static final FieldElement TWO = new BigIntegerFieldElement(ed25519Field, BigInteger.valueOf(2)); 44 | 45 | protected FieldElement getRandomFieldElement() { 46 | BigInteger r; 47 | Random rnd = new Random(); 48 | do { 49 | r = new BigInteger(255, rnd); 50 | } while (r.compareTo(getQ()) >= 0); 51 | return new BigIntegerFieldElement(ed25519Field, r); 52 | } 53 | 54 | protected BigInteger toBigInteger(FieldElement f) { 55 | return ((BigIntegerFieldElement)f).bi; 56 | } 57 | 58 | protected BigInteger getQ() { 59 | return MathUtils.getQ(); 60 | } 61 | 62 | protected Field getField() { 63 | return ed25519Field; 64 | } 65 | 66 | /** 67 | * Test method for {@link BigIntegerFieldElement#BigIntegerFieldElement(Field, BigInteger)}. 68 | */ 69 | @Test 70 | public void testFieldElementBigInteger() { 71 | assertThat(new BigIntegerFieldElement(ed25519Field, BigInteger.ZERO).bi, is(BigInteger.ZERO)); 72 | assertThat(new BigIntegerFieldElement(ed25519Field, BigInteger.ONE).bi, is(BigInteger.ONE)); 73 | assertThat(new BigIntegerFieldElement(ed25519Field, BigInteger.valueOf(2)).bi, is(BigInteger.valueOf(2))); 74 | } 75 | 76 | /** 77 | * Test method for {@link FieldElement#toByteArray()}. 78 | */ 79 | @Test 80 | public void testToByteArray() { 81 | byte[] zero = ZERO.toByteArray(); 82 | assertThat(zero.length, is(equalTo(BYTES_ZERO.length))); 83 | assertThat(zero, is(equalTo(BYTES_ZERO))); 84 | 85 | byte[] one = ONE.toByteArray(); 86 | assertThat(one.length, is(equalTo(BYTES_ONE.length))); 87 | assertThat(one, is(equalTo(BYTES_ONE))); 88 | 89 | byte[] ten = new BigIntegerFieldElement(ed25519Field, BigInteger.TEN).toByteArray(); 90 | assertThat(ten.length, is(equalTo(BYTES_TEN.length))); 91 | assertThat(ten, is(equalTo(BYTES_TEN))); 92 | } 93 | 94 | // region isNonZero 95 | 96 | protected FieldElement getZeroFieldElement() { 97 | return ZERO; 98 | } 99 | 100 | protected FieldElement getNonZeroFieldElement() { 101 | return TWO; 102 | } 103 | 104 | // endregion 105 | 106 | /** 107 | * Test method for {@link FieldElement#equals(java.lang.Object)}. 108 | */ 109 | @Test 110 | public void testEqualsObject() { 111 | assertThat(new BigIntegerFieldElement(ed25519Field, BigInteger.ZERO), is(equalTo(ZERO))); 112 | assertThat(new BigIntegerFieldElement(ed25519Field, BigInteger.valueOf(1000)), is(equalTo(new BigIntegerFieldElement(ed25519Field, BigInteger.valueOf(1000))))); 113 | assertThat(ONE, is(not(equalTo(TWO)))); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/math/bigint/BigIntegerScalarOpsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math.bigint; 13 | 14 | import static org.hamcrest.Matchers.*; 15 | import static org.junit.Assert.*; 16 | 17 | import java.math.BigInteger; 18 | 19 | import net.i2p.crypto.eddsa.Utils; 20 | import net.i2p.crypto.eddsa.math.Field; 21 | import net.i2p.crypto.eddsa.math.ScalarOps; 22 | import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec; 23 | import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; 24 | 25 | import org.junit.Test; 26 | 27 | /** 28 | * @author str4d 29 | * 30 | */ 31 | public class BigIntegerScalarOpsTest { 32 | 33 | static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 34 | static final Field ed25519Field = ed25519.getCurve().getField(); 35 | 36 | /** 37 | * Test method for {@link net.i2p.crypto.eddsa.math.bigint.BigIntegerScalarOps#reduce(byte[])}. 38 | */ 39 | @Test 40 | public void testReduce() { 41 | ScalarOps sc = new BigIntegerScalarOps(ed25519Field, 42 | new BigInteger("5")); 43 | assertThat(sc.reduce(new byte[] {7}), 44 | is(equalTo(Utils.hexToBytes("0200000000000000000000000000000000000000000000000000000000000000")))); 45 | 46 | ScalarOps sc2 = new BigIntegerScalarOps(ed25519Field, 47 | new BigInteger("7237005577332262213973186563042994240857116359379907606001950938285454250989")); 48 | // Example from test case 1 49 | byte[] r = Utils.hexToBytes("b6b19cd8e0426f5983fa112d89a143aa97dab8bc5deb8d5b6253c928b65272f4044098c2a990039cde5b6a4818df0bfb6e40dc5dee54248032962323e701352d"); 50 | assertThat(sc2.reduce(r), is(equalTo(Utils.hexToBytes("f38907308c893deaf244787db4af53682249107418afc2edc58f75ac58a07404")))); 51 | } 52 | 53 | /** 54 | * Test method for {@link net.i2p.crypto.eddsa.math.bigint.BigIntegerScalarOps#multiplyAndAdd(byte[], byte[], byte[])}. 55 | */ 56 | @Test 57 | public void testMultiplyAndAdd() { 58 | ScalarOps sc = new BigIntegerScalarOps(ed25519Field, 59 | new BigInteger("5")); 60 | assertThat(sc.multiplyAndAdd(new byte[] {7}, new byte[] {2}, new byte[] {5}), 61 | is(equalTo(Utils.hexToBytes("0400000000000000000000000000000000000000000000000000000000000000")))); 62 | 63 | ScalarOps sc2 = new BigIntegerScalarOps(ed25519Field, 64 | new BigInteger("7237005577332262213973186563042994240857116359379907606001950938285454250989")); 65 | // Example from test case 1 66 | byte[] h = Utils.hexToBytes("86eabc8e4c96193d290504e7c600df6cf8d8256131ec2c138a3e7e162e525404"); 67 | byte[] a = Utils.hexToBytes("307c83864f2833cb427a2ef1c00a013cfdff2768d980c0a3a520f006904de94f"); 68 | byte[] r = Utils.hexToBytes("f38907308c893deaf244787db4af53682249107418afc2edc58f75ac58a07404"); 69 | byte[] S = Utils.hexToBytes("5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"); 70 | assertThat(sc2.multiplyAndAdd(h, a, r), is(equalTo(S))); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/math/ed25519/Ed25519FieldElementTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math.ed25519; 13 | 14 | import net.i2p.crypto.eddsa.math.*; 15 | import org.hamcrest.core.*; 16 | import org.junit.*; 17 | 18 | import java.math.BigInteger; 19 | 20 | /** 21 | * Tests rely on the BigInteger class. 22 | */ 23 | public class Ed25519FieldElementTest extends AbstractFieldElementTest { 24 | 25 | protected FieldElement getRandomFieldElement() { 26 | return MathUtils.getRandomFieldElement(); 27 | } 28 | 29 | protected BigInteger toBigInteger(FieldElement f) { 30 | return MathUtils.toBigInteger(f); 31 | } 32 | 33 | protected BigInteger getQ() { 34 | return MathUtils.getQ(); 35 | } 36 | 37 | protected Field getField() { 38 | return MathUtils.getField(); 39 | } 40 | 41 | // region constructor 42 | 43 | @Test 44 | public void canConstructFieldElementFromArrayWithCorrectLength() { 45 | // Assert: 46 | new Ed25519FieldElement(MathUtils.getField(), new int[10]); 47 | } 48 | 49 | @Test (expected = IllegalArgumentException.class) 50 | public void cannotConstructFieldElementFromArrayWithIncorrectLength() { 51 | // Assert: 52 | new Ed25519FieldElement(MathUtils.getField(), new int[9]); 53 | } 54 | 55 | @Test (expected = IllegalArgumentException.class) 56 | public void cannotConstructFieldElementWithoutField() { 57 | // Assert: 58 | new Ed25519FieldElement(null, new int[9]); 59 | } 60 | 61 | // endregion 62 | 63 | // region isNonZero 64 | 65 | protected FieldElement getZeroFieldElement() { 66 | return new Ed25519FieldElement(MathUtils.getField(), new int[10]); 67 | } 68 | 69 | protected FieldElement getNonZeroFieldElement() { 70 | final int[] t = new int[10]; 71 | t[0] = 5; 72 | return new Ed25519FieldElement(MathUtils.getField(), t); 73 | } 74 | 75 | // endregion 76 | 77 | // region toString 78 | 79 | @Test 80 | public void toStringReturnsCorrectRepresentation() { 81 | // Arrange: 82 | final byte[] bytes = new byte[32]; 83 | for (int i=0; i<32; i++) { 84 | bytes[i] = (byte)(i+1); 85 | } 86 | final FieldElement f = MathUtils.getField().getEncoding().decode(bytes); 87 | 88 | // Act: 89 | final String fAsString = f.toString(); 90 | final StringBuilder builder = new StringBuilder(); 91 | builder.append("[Ed25519FieldElement val="); 92 | for (byte b : bytes) { 93 | builder.append(String.format("%02x", b)); 94 | } 95 | builder.append("]"); 96 | 97 | // Assert: 98 | Assert.assertThat(fAsString, IsEqual.equalTo(builder.toString())); 99 | } 100 | 101 | // endregion 102 | } 103 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/math/ed25519/Ed25519LittleEndianEncodingTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math.ed25519; 13 | 14 | import net.i2p.crypto.eddsa.math.*; 15 | import org.hamcrest.core.IsEqual; 16 | import org.junit.*; 17 | 18 | import java.math.BigInteger; 19 | import java.security.SecureRandom; 20 | 21 | /** 22 | * Tests rely on the BigInteger class. 23 | */ 24 | public class Ed25519LittleEndianEncodingTest { 25 | 26 | private static final SecureRandom random = new SecureRandom(); 27 | 28 | @Test 29 | public void encodeReturnsCorrectByteArrayForSimpleFieldElements() { 30 | // Arrange: 31 | final int[] t1 = new int[10]; 32 | final int[] t2 = new int[10]; 33 | t2[0] = 1; 34 | final FieldElement fieldElement1 = new Ed25519FieldElement(MathUtils.getField(), t1); 35 | final FieldElement fieldElement2 = new Ed25519FieldElement(MathUtils.getField(), t2); 36 | 37 | // Act: 38 | final byte[] bytes1 = MathUtils.getField().getEncoding().encode(fieldElement1); 39 | final byte[] bytes2 = MathUtils.getField().getEncoding().encode(fieldElement2); 40 | 41 | // Assert: 42 | Assert.assertThat(bytes1, IsEqual.equalTo(MathUtils.toByteArray(BigInteger.ZERO))); 43 | Assert.assertThat(bytes2, IsEqual.equalTo(MathUtils.toByteArray(BigInteger.ONE))); 44 | } 45 | 46 | @Test 47 | public void encodeReturnsCorrectByteArray() { 48 | for (int i=0; i<10000; i++){ 49 | // Arrange: 50 | final int[] t = new int[10]; 51 | for (int j=0; j<10; j++) { 52 | t[j] = random.nextInt(1 << 28) - (1 << 27); 53 | } 54 | final FieldElement fieldElement1 = new Ed25519FieldElement(MathUtils.getField(), t); 55 | final BigInteger b = MathUtils.toBigInteger(t); 56 | 57 | // Act: 58 | final byte[] bytes = MathUtils.getField().getEncoding().encode(fieldElement1); 59 | 60 | // Assert: 61 | Assert.assertThat(bytes, IsEqual.equalTo(MathUtils.toByteArray(b.mod(MathUtils.getQ())))); 62 | } 63 | } 64 | 65 | @Test 66 | public void decodeReturnsCorrectFieldElementForSimpleByteArrays() { 67 | // Arrange: 68 | final byte[] bytes1 = new byte[32]; 69 | final byte[] bytes2 = new byte[32]; 70 | bytes2[0] = 1; 71 | 72 | // Act: 73 | final Ed25519FieldElement f1 = (Ed25519FieldElement)MathUtils.getField().getEncoding().decode(bytes1); 74 | final Ed25519FieldElement f2 = (Ed25519FieldElement)MathUtils.getField().getEncoding().decode(bytes2); 75 | final BigInteger b1 = MathUtils.toBigInteger(f1.t); 76 | final BigInteger b2 = MathUtils.toBigInteger(f2.t); 77 | 78 | // Assert: 79 | Assert.assertThat(b1, IsEqual.equalTo(BigInteger.ZERO)); 80 | Assert.assertThat(b2, IsEqual.equalTo(BigInteger.ONE)); 81 | } 82 | 83 | @Test 84 | public void decodeReturnsCorrectFieldElement() { 85 | for (int i=0; i<10000; i++) { 86 | // Arrange: 87 | final byte[] bytes = new byte[32]; 88 | random.nextBytes(bytes); 89 | bytes[31] = (byte)(bytes[31] & 0x7f); 90 | final BigInteger b1 = MathUtils.toBigInteger(bytes); 91 | 92 | // Act: 93 | final Ed25519FieldElement f = (Ed25519FieldElement)MathUtils.getField().getEncoding().decode(bytes); 94 | final BigInteger b2 = MathUtils.toBigInteger(f.t).mod(MathUtils.getQ()); 95 | 96 | // Assert: 97 | Assert.assertThat(b2, IsEqual.equalTo(b1)); 98 | } 99 | } 100 | 101 | @Test 102 | public void isNegativeReturnsCorrectResult() { 103 | for (int i=0; i<10000; i++) { 104 | // Arrange: 105 | final int[] t = new int[10]; 106 | for (int j=0; j<10; j++) { 107 | t[j] = random.nextInt(1 << 28) - (1 << 27); 108 | } 109 | final boolean isNegative = MathUtils.toBigInteger(t).mod(MathUtils.getQ()).mod(new BigInteger("2")).equals(BigInteger.ONE); 110 | final FieldElement f = new Ed25519FieldElement(MathUtils.getField(), t); 111 | 112 | // Assert: 113 | Assert.assertThat(MathUtils.getField().getEncoding().isNegative(f), IsEqual.equalTo(isNegative)); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/math/ed25519/Ed25519ScalarOpsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.math.ed25519; 13 | 14 | import net.i2p.crypto.eddsa.Utils; 15 | import net.i2p.crypto.eddsa.math.*; 16 | import org.hamcrest.core.IsEqual; 17 | import org.junit.*; 18 | 19 | import java.math.BigInteger; 20 | 21 | import static org.hamcrest.Matchers.equalTo; 22 | import static org.hamcrest.Matchers.is; 23 | import static org.junit.Assert.assertThat; 24 | 25 | /** 26 | * @author str4d 27 | * Additional tests by the NEM project team. 28 | * 29 | */ 30 | public class Ed25519ScalarOpsTest { 31 | 32 | private static final Ed25519ScalarOps scalarOps = new Ed25519ScalarOps(); 33 | 34 | /** 35 | * Test method for {@link net.i2p.crypto.eddsa.math.bigint.BigIntegerScalarOps#reduce(byte[])}. 36 | */ 37 | @Test 38 | public void testReduce() { 39 | // Example from test case 1 40 | byte[] r = Utils.hexToBytes("b6b19cd8e0426f5983fa112d89a143aa97dab8bc5deb8d5b6253c928b65272f4044098c2a990039cde5b6a4818df0bfb6e40dc5dee54248032962323e701352d"); 41 | assertThat(scalarOps.reduce(r), is(equalTo(Utils.hexToBytes("f38907308c893deaf244787db4af53682249107418afc2edc58f75ac58a07404")))); 42 | } 43 | 44 | @Test 45 | public void reduceReturnsExpectedResult() { 46 | for (int i=0; i<1000; i++) { 47 | // Arrange: 48 | final byte[] bytes = MathUtils.getRandomByteArray(64); 49 | 50 | // Act: 51 | final byte[] reduced1 = scalarOps.reduce(bytes); 52 | final byte[] reduced2 = MathUtils.reduceModGroupOrder(bytes); 53 | 54 | // Assert: 55 | Assert.assertThat(MathUtils.toBigInteger(reduced1).compareTo(MathUtils.getGroupOrder()), IsEqual.equalTo(-1)); 56 | Assert.assertThat(MathUtils.toBigInteger(reduced1).compareTo(new BigInteger("-1")), IsEqual.equalTo(1)); 57 | Assert.assertThat(reduced1, IsEqual.equalTo(reduced2)); 58 | } 59 | } 60 | 61 | /** 62 | * Test method for {@link net.i2p.crypto.eddsa.math.bigint.BigIntegerScalarOps#multiplyAndAdd(byte[], byte[], byte[])}. 63 | */ 64 | @Test 65 | public void testMultiplyAndAdd() { 66 | // Example from test case 1 67 | byte[] h = Utils.hexToBytes("86eabc8e4c96193d290504e7c600df6cf8d8256131ec2c138a3e7e162e525404"); 68 | byte[] a = Utils.hexToBytes("307c83864f2833cb427a2ef1c00a013cfdff2768d980c0a3a520f006904de94f"); 69 | byte[] r = Utils.hexToBytes("f38907308c893deaf244787db4af53682249107418afc2edc58f75ac58a07404"); 70 | byte[] S = Utils.hexToBytes("5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"); 71 | assertThat(scalarOps.multiplyAndAdd(h, a, r), is(equalTo(S))); 72 | } 73 | 74 | @Test 75 | public void multiplyAndAddReturnsExpectedResult() { 76 | for (int i=0; i<1000; i++) { 77 | // Arrange: 78 | final byte[] bytes1 = MathUtils.getRandomByteArray(32); 79 | final byte[] bytes2 = MathUtils.getRandomByteArray(32); 80 | final byte[] bytes3 = MathUtils.getRandomByteArray(32); 81 | 82 | // Act: 83 | final byte[] result1 = scalarOps.multiplyAndAdd(bytes1, bytes2, bytes3); 84 | final byte[] result2 = MathUtils.multiplyAndAddModGroupOrder(bytes1, bytes2, bytes3); 85 | 86 | // Assert: 87 | Assert.assertThat(MathUtils.toBigInteger(result1).compareTo(MathUtils.getGroupOrder()), IsEqual.equalTo(-1)); 88 | Assert.assertThat(MathUtils.toBigInteger(result1).compareTo(new BigInteger("-1")), IsEqual.equalTo(1)); 89 | Assert.assertThat(result1, IsEqual.equalTo(result2)); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/spec/EdDSANamedCurveTableTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.spec; 13 | 14 | import static net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable.ED_25519; 15 | import static net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable.ED_25519_CURVE_SPEC; 16 | import static org.hamcrest.Matchers.*; 17 | import static org.junit.Assert.*; 18 | 19 | import org.junit.Test; 20 | 21 | /** 22 | * @author str4d 23 | * 24 | */ 25 | public class EdDSANamedCurveTableTest { 26 | /** 27 | * Ensure curve names are case-inspecific 28 | */ 29 | @Test 30 | public void curveNamesAreCaseInspecific() { 31 | EdDSANamedCurveSpec mixed = EdDSANamedCurveTable.getByName("Ed25519"); 32 | EdDSANamedCurveSpec lower = EdDSANamedCurveTable.getByName("ed25519"); 33 | EdDSANamedCurveSpec upper = EdDSANamedCurveTable.getByName("ED25519"); 34 | 35 | assertThat(lower, is(equalTo(mixed))); 36 | assertThat(upper, is(equalTo(mixed))); 37 | } 38 | 39 | @Test 40 | public void testConstants() { 41 | EdDSANamedCurveSpec spec = EdDSANamedCurveTable.getByName(ED_25519); 42 | assertThat("Named curve and constant should match", spec, is(equalTo(ED_25519_CURVE_SPEC))); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/net/i2p/crypto/eddsa/spec/EdDSAPrivateKeySpecTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EdDSA-Java by str4d 3 | * 4 | * To the extent possible under law, the person who associated CC0 with 5 | * EdDSA-Java has waived all copyright and related or neighboring rights 6 | * to EdDSA-Java. 7 | * 8 | * You should have received a copy of the CC0 legalcode along with this 9 | * work. If not, see . 10 | * 11 | */ 12 | package net.i2p.crypto.eddsa.spec; 13 | 14 | import static org.hamcrest.Matchers.*; 15 | import static org.junit.Assert.*; 16 | import net.i2p.crypto.eddsa.Utils; 17 | import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; 18 | 19 | import org.junit.Rule; 20 | import org.junit.Test; 21 | import org.junit.rules.ExpectedException; 22 | 23 | /** 24 | * @author str4d 25 | * 26 | */ 27 | public class EdDSAPrivateKeySpecTest { 28 | static final byte[] ZERO_SEED = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000"); 29 | static final byte[] ZERO_H = Utils.hexToBytes("5046adc1dba838867b2bbbfdd0c3423e58b57970b5267a90f57960924a87f1960a6a85eaa642dac835424b5d7c8d637c00408c7a73da672b7f498521420b6dd3"); 30 | static final byte[] ZERO_PK = Utils.hexToBytes("3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29"); 31 | 32 | static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); 33 | 34 | @Rule 35 | public ExpectedException exception = ExpectedException.none(); 36 | 37 | /** 38 | * Test method for {@link net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec#EdDSAPrivateKeySpec(byte[], net.i2p.crypto.eddsa.spec.EdDSAParameterSpec)}. 39 | */ 40 | @Test 41 | public void testEdDSAPrivateKeySpecFromSeed() { 42 | EdDSAPrivateKeySpec key = new EdDSAPrivateKeySpec(ZERO_SEED, ed25519); 43 | assertThat(key.getSeed(), is(equalTo(ZERO_SEED))); 44 | assertThat(key.getH(), is(equalTo(ZERO_H))); 45 | assertThat(key.getA().toByteArray(), is(equalTo(ZERO_PK))); 46 | } 47 | 48 | @Test 49 | public void incorrectSeedLengthThrows() { 50 | exception.expect(IllegalArgumentException.class); 51 | exception.expectMessage("seed length is wrong"); 52 | new EdDSAPrivateKeySpec(new byte[2], ed25519); 53 | } 54 | 55 | /** 56 | * Test method for {@link net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec#EdDSAPrivateKeySpec(net.i2p.crypto.eddsa.spec.EdDSAParameterSpec, byte[])}. 57 | */ 58 | @Test 59 | public void testEdDSAPrivateKeySpecFromH() { 60 | EdDSAPrivateKeySpec key = new EdDSAPrivateKeySpec(ed25519, ZERO_H); 61 | assertThat(key.getSeed(), is(nullValue())); 62 | assertThat(key.getH(), is(equalTo(ZERO_H))); 63 | assertThat(key.getA().toByteArray(), is(equalTo(ZERO_PK))); 64 | } 65 | 66 | @Test 67 | public void incorrectHashLengthThrows() { 68 | exception.expect(IllegalArgumentException.class); 69 | exception.expectMessage("hash length is wrong"); 70 | new EdDSAPrivateKeySpec(ed25519, new byte[2]); 71 | } 72 | } 73 | --------------------------------------------------------------------------------