Skip to content

Commit daac2c5

Browse files
committed
Merge pull request #3 from str4d/ref10
Cleanups and speedups
2 parents 9dd0ef1 + 84fb85b commit daac2c5

27 files changed

+568
-415
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,31 @@ This is an implementation of Ed25519 in Java. Structurally, it is based on the r
55

66
There are no guarantees that this is secure for use. Tests against [the data from the Python implementation](http://ed25519.cr.yp.to/python/sign.input) are passing, but this has not yet been audited by a professional cryptographer. In particular, this implementation is unlikely to have the constant-time properties of ref10 (for now).
77

8+
The code requires Java 6 (for e.g. the `Arrays.copyOfRange()` calls in `EdDSAEngine.engineVerify()`).
9+
810
The JUnit4 tests require the Hamcrest library `hamcrest-all.jar`.
911

1012
This code is released to the public domain and can be used for any purpose.
1113

14+
Code comparison
15+
---------------
16+
17+
For ease of following, here are the main methods in ref10 and their equivalents in this codebase:
18+
19+
| EdDSA Operation | ref10 function | Java function |
20+
| --------------- | -------------- | ------------- |
21+
| Generate keypair | `crypto_sign_keypair` | `EdDSAPrivateKeySpec` constructor |
22+
| Sign message | `crypto_sign` | `EdDSAEngine.engineSign` |
23+
| Verify signature | `crypto_sign_open` | `EdDSAEngine.engineVerify` |
24+
25+
| EdDSA point arithmetic | ref10 function | Java function |
26+
| ---------------------- | -------------- | ------------- |
27+
| `R = b * B` | `ge_scalarmult_base` | `GroupElement.scalarMultiply` |
28+
| `R = a*A + b*B` | `ge_double_scalarmult_vartime` | `GroupElement.doubleScalarMultiplyVariableTime` |
29+
| `R = 2 * P` | `ge_p2_dbl` | `GroupElement.dbl` |
30+
| `R = P + Q` | `ge_madd`, `ge_add` | `GroupElement.madd`, `GroupElement.add` |
31+
| `R = P - Q` | `ge_msub`, `ge_sub` | `GroupElement.msub`, `GroupElement.sub` |
32+
1233
Credits
1334
-------
1435

src/net/i2p/crypto/eddsa/EdDSAEngine.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ protected byte[] engineSign() throws SignatureException {
137137
FieldElement S = curve.fromBigInteger(leEnc.decode(digest.digest(message)).multiply(a).add(rBI).mod(l));
138138

139139
// R+S
140-
ByteBuffer out = ByteBuffer.allocate(64);
140+
int b = curve.getField().getb();
141+
ByteBuffer out = ByteBuffer.allocate(b/4);
141142
out.put(Rbyte).put(S.toByteArray());
142143
return out.array();
143144
}
@@ -147,13 +148,10 @@ protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
147148
Curve curve = key.getParams().getCurve();
148149
int b = curve.getField().getb();
149150
if (sigBytes.length != b/4)
150-
throw new IllegalArgumentException("signature length is wrong");
151-
152-
byte[] Rbyte = Arrays.copyOfRange(sigBytes, 0, b/8);
153-
byte[] Sbyte = Arrays.copyOfRange(sigBytes, b/8, b/4);
151+
throw new SignatureException("signature length is wrong");
154152

155-
// If we get to here, Rbyte is valid
156-
digest.update(Rbyte);
153+
// R is first b/8 bytes of sigBytes, S is second b/8 bytes
154+
digest.update(sigBytes, 0, b/8);
157155
digest.update(((EdDSAPublicKey) key).getAbyte());
158156
// h = H(Rbar,Abar,M)
159157
byte[] message = baos.toByteArray();
@@ -163,14 +161,15 @@ protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
163161
LittleEndianEncoding leEnc = new LittleEndianEncoding();
164162
h = leEnc.encode(leEnc.decode(h).mod(key.getParams().getL()), b/8);
165163

164+
byte[] Sbyte = Arrays.copyOfRange(sigBytes, b/8, b/4);
166165
// R = SB - H(Rbar,Abar,M)A
167166
GroupElement R = key.getParams().getB().doubleScalarMultiplyVariableTime(
168-
((EdDSAPublicKey) key).getA().negate(), h, Sbyte);
167+
((EdDSAPublicKey) key).getNegativeA(), h, Sbyte);
169168

170169
byte[] Rcalc = R.toByteArray();
171170
int result = 1;
172171
for (int i = 0; i < Rcalc.length; i++) {
173-
result &= Utils.equal(Rcalc[i], Rbyte[i]);
172+
result &= Utils.equal(Rcalc[i], sigBytes[i]);
174173
}
175174
return result == 1;
176175
}

src/net/i2p/crypto/eddsa/EdDSAPrivateKey.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*
1414
*/
1515
public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
16+
private static final long serialVersionUID = 23495873459878957L;
1617
private transient final byte[] seed;
1718
private transient final byte[] h;
1819
private transient final BigInteger a;
@@ -29,23 +30,19 @@ public EdDSAPrivateKey(EdDSAPrivateKeySpec spec) {
2930
this.edDsaSpec = spec.getParams();
3031
}
3132

32-
@Override
3333
public String getAlgorithm() {
3434
return "EdDSA";
3535
}
3636

37-
@Override
3837
public String getFormat() {
3938
return "PKCS#8";
4039
}
4140

42-
@Override
4341
public byte[] getEncoded() {
4442
// TODO Auto-generated method stub
4543
return null;
4644
}
4745

48-
@Override
4946
public EdDSAParameterSpec getParams() {
5047
return edDsaSpec;
5148
}

src/net/i2p/crypto/eddsa/EdDSAPublicKey.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,32 @@
1212
*
1313
*/
1414
public class EdDSAPublicKey implements EdDSAKey, PublicKey {
15-
private transient final GroupElement A;
16-
private transient final byte[] Abyte;
17-
private transient final EdDSAParameterSpec edDsaSpec;
15+
private static final long serialVersionUID = 9837459837498475L;
16+
private final GroupElement A;
17+
private final GroupElement Aneg;
18+
private final byte[] Abyte;
19+
private final EdDSAParameterSpec edDsaSpec;
1820

1921
public EdDSAPublicKey(EdDSAPublicKeySpec spec) {
2022
this.A = spec.getA();
23+
this.Aneg = spec.getNegativeA();
2124
this.Abyte = this.A.toByteArray();
2225
this.edDsaSpec = spec.getParams();
2326
}
2427

25-
@Override
2628
public String getAlgorithm() {
2729
return "EdDSA";
2830
}
2931

30-
@Override
3132
public String getFormat() {
3233
return "X.509";
3334
}
3435

35-
@Override
3636
public byte[] getEncoded() {
3737
// TODO Auto-generated method stub
3838
return null;
3939
}
4040

41-
@Override
4241
public EdDSAParameterSpec getParams() {
4342
return edDsaSpec;
4443
}
@@ -47,6 +46,10 @@ public GroupElement getA() {
4746
return A;
4847
}
4948

49+
public GroupElement getNegativeA() {
50+
return Aneg;
51+
}
52+
5053
public byte[] getAbyte() {
5154
return Abyte;
5255
}

src/net/i2p/crypto/eddsa/KeyFactory.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*
1717
*/
1818
public class KeyFactory extends KeyFactorySpi {
19-
@Override
19+
2020
protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
2121
throws InvalidKeySpecException {
2222
if (keySpec instanceof EdDSAPrivateKeySpec) {
@@ -25,7 +25,6 @@ protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
2525
throw new InvalidKeySpecException("key spec not recognised");
2626
}
2727

28-
@Override
2928
protected PublicKey engineGeneratePublic(KeySpec keySpec)
3029
throws InvalidKeySpecException {
3130
if (keySpec instanceof EdDSAPublicKeySpec) {
@@ -35,7 +34,6 @@ protected PublicKey engineGeneratePublic(KeySpec keySpec)
3534
}
3635

3736
@SuppressWarnings("unchecked")
38-
@Override
3937
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
4038
throws InvalidKeySpecException {
4139
if (keySpec.isAssignableFrom(EdDSAPublicKeySpec.class) && key instanceof EdDSAPublicKey) {
@@ -52,7 +50,6 @@ protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
5250
throw new InvalidKeySpecException("not implemented yet " + key + " " + keySpec);
5351
}
5452

55-
@Override
5653
protected Key engineTranslateKey(Key key) throws InvalidKeyException {
5754
throw new InvalidKeyException("No other EdDSA key providers known");
5855
}

src/net/i2p/crypto/eddsa/KeyPairGenerator.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
1616
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
1717

18+
/**
19+
* Default strength is 256
20+
*/
1821
public class KeyPairGenerator extends KeyPairGeneratorSpi {
19-
private int strength = 256;
22+
private static final int DEFAULT_STRENGTH = 256;
2023
private EdDSAParameterSpec edParams;
2124
private SecureRandom random;
2225
private boolean initialized;
@@ -26,10 +29,9 @@ public class KeyPairGenerator extends KeyPairGeneratorSpi {
2629
static {
2730
edParameters = new Hashtable<Integer, AlgorithmParameterSpec>();
2831

29-
edParameters.put(Integer.valueOf(256), new EdDSAGenParameterSpec("ed25519-sha-512"));
32+
edParameters.put(Integer.valueOf(DEFAULT_STRENGTH), new EdDSAGenParameterSpec(EdDSANamedCurveTable.CURVE_ED25519_SHA512));
3033
}
3134

32-
@Override
3335
public void initialize(int strength, SecureRandom random) {
3436
AlgorithmParameterSpec edParams = edParameters.get(Integer.valueOf(strength));
3537
if (edParams == null)
@@ -54,10 +56,9 @@ public void initialize(AlgorithmParameterSpec params, SecureRandom random) throw
5456
initialized = true;
5557
}
5658

57-
@Override
5859
public KeyPair generateKeyPair() {
5960
if (!initialized)
60-
initialize(strength, new SecureRandom());
61+
initialize(DEFAULT_STRENGTH, new SecureRandom());
6162

6263
byte[] seed = new byte[edParams.getCurve().getField().getb()/8];
6364
random.nextBytes(seed);

src/net/i2p/crypto/eddsa/Utils.java

Lines changed: 8 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ public class Utils {
1010
* @return 1 if b and c are equal, 0 otherwise.
1111
*/
1212
public static int equal(int b, int c) {
13-
return b == c ? 1 : 0;
13+
int result = 0;
14+
int xor = b ^ c;
15+
for (int i = 0; i < 8; i++) {
16+
result |= xor >> i;
17+
}
18+
return (result ^ 0x01) & 0x01;
1419
}
1520

1621
/**
@@ -22,112 +27,13 @@ public static int negative(int b) {
2227
return (b >> 8) & 1;
2328
}
2429

25-
/**
26-
* Convert a to radix 16.
27-
* @param a = a[0]+256*a[1]+...+256^31 a[31]
28-
* @return
29-
*/
30-
public static byte[] toRadix16(byte[] a) {
31-
byte[] e = new byte[64];
32-
int i;
33-
// Radix 16 notation
34-
for (i = 0; i < 32; i++) {
35-
e[2*i+0] = (byte) ((a[i] >> 0) & 15);
36-
e[2*i+1] = (byte) ((a[i] >> 4) & 15);
37-
}
38-
/* each e[i] is between 0 and 15 */
39-
/* e[63] is between 0 and 7 */
40-
int carry = 0;
41-
for (i = 0; i < 63; i++) {
42-
e[i] += carry;
43-
carry = e[i] + 8;
44-
carry >>= 4;
45-
e[i] -= carry << 4;
46-
}
47-
e[63] += carry;
48-
/* each e[i] is between -8 and 8 */
49-
return e;
50-
}
51-
52-
/**
53-
* I don't really know what this method does.
54-
* @param a
55-
* @return
56-
*/
57-
public static byte[] slide(byte[] a) {
58-
byte[] r = new byte[256];
59-
int i;
60-
int b;
61-
int k;
62-
63-
for (i = 0;i < 256;++i) {
64-
r[i] = (byte) (1 & (a[i >> 3] >> (i & 7)));
65-
}
66-
67-
for (i = 0;i < 256;++i) {
68-
if (r[i] != 0) {
69-
for (b = 1; b <= 6 && i + b < 256; ++b) {
70-
if (r[i + b] != 0) {
71-
if (r[i] + (r[i + b] << b) <= 15) {
72-
r[i] += r[i + b] << b; r[i + b] = 0;
73-
} else if (r[i] - (r[i + b] << b) >= -15) {
74-
r[i] -= r[i + b] << b;
75-
for (k = i + b; k < 256; ++k) {
76-
if (r[k] == 0) {
77-
r[k] = 1;
78-
break;
79-
}
80-
r[k] = 0;
81-
}
82-
} else
83-
break;
84-
}
85-
}
86-
}
87-
}
88-
89-
return r;
90-
}
91-
9230
/**
9331
* Get the i'th bit of a byte array.
9432
* @param h the byte array.
9533
* @param i the bit index.
96-
* @return the value of the i'th bit in h.
34+
* @return 0 or 1, the value of the i'th bit in h
9735
*/
9836
public static int bit(byte[] h, int i) {
99-
return h[i/8] >> (i%8) & 1;
100-
}
101-
102-
/**
103-
* Converts bytes to a hex string.
104-
* @param raw the byte[] to be converted.
105-
* @return the hex representation as a string.
106-
*/
107-
public static String getHex(byte[] raw) {
108-
if ( raw == null ) {
109-
return null;
110-
}
111-
final StringBuilder hex = new StringBuilder(2 * raw.length);
112-
for (final byte b : raw) {
113-
hex.append(Character.forDigit((b & 0xF0) >> 4, 16))
114-
.append(Character.forDigit((b & 0x0F), 16));
115-
}
116-
return hex.toString();
117-
}
118-
119-
/**
120-
* Converts a hex string to bytes.
121-
* @param s the hex string to be converted.
122-
* @return the byte[]
123-
*/
124-
public static byte[] hexToBytes(String s) {
125-
int len = s.length();
126-
byte[] data = new byte[len / 2];
127-
for (int i = 0; i < len; i += 2) {
128-
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
129-
+ Character.digit(s.charAt(i+1), 16));
130-
}
131-
return data;
37+
return (h[i/8] >> (i%8)) & 1;
13238
}
13339
}

src/net/i2p/crypto/eddsa/math/Constants.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
import java.math.BigInteger;
44

55
public class Constants {
6-
public static final BigInteger ZERO = BigInteger.valueOf(0);
7-
public static final BigInteger ONE = BigInteger.valueOf(1);
6+
public static final BigInteger ZERO = BigInteger.ZERO;
7+
public static final BigInteger ONE = BigInteger.ONE;
88
public static final BigInteger TWO = BigInteger.valueOf(2);
9-
public static final BigInteger THREE = BigInteger.valueOf(3);
109
public static final BigInteger FOUR = BigInteger.valueOf(4);
1110
public static final BigInteger FIVE = BigInteger.valueOf(5);
1211
public static final BigInteger EIGHT = BigInteger.valueOf(8);

0 commit comments

Comments
 (0)