Skip to content

Commit 7f54968

Browse files
committed
Merge pull request #4 from str4d/ref10
Radix-2^51 support from ref10
2 parents daac2c5 + 5ce3fe8 commit 7f54968

35 files changed

+3204
-1206
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
ed25519-java
22
============
33

4-
This is an implementation of Ed25519 in Java. Structurally, it is based on the ref10 implementation in SUPERCOP (see http://ed25519.cr.yp.to/software.html). Internally, it uses BigIntegers for calculation.
4+
This is an implementation of EdDSA in Java. Structurally, it is based on the ref10 implementation in SUPERCOP (see http://ed25519.cr.yp.to/software.html).
55

6-
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).
6+
There are two internal implementations:
7+
* A port of the radix-2^51 operations in ref10 - fast and constant-time, but only useful for Ed25519.
8+
* A generic version using BigIntegers for calculation - a bit slower and not constant-time, but compatible with any EdDSA parameter specification.
9+
10+
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, the constant-time properties of ref10 may not have been completely retained (although this is the eventual goal for the Ed25519-specific implementation).
711

812
The code requires Java 6 (for e.g. the `Arrays.copyOfRange()` calls in `EdDSAEngine.engineVerify()`).
913

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

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package net.i2p.crypto.eddsa;
22

33
import java.io.ByteArrayOutputStream;
4-
import java.math.BigInteger;
54
import java.nio.ByteBuffer;
65
import java.security.InvalidKeyException;
76
import java.security.MessageDigest;
@@ -13,9 +12,8 @@
1312
import java.util.Arrays;
1413

1514
import net.i2p.crypto.eddsa.math.Curve;
16-
import net.i2p.crypto.eddsa.math.FieldElement;
1715
import net.i2p.crypto.eddsa.math.GroupElement;
18-
import net.i2p.crypto.eddsa.math.LittleEndianEncoding;
16+
import net.i2p.crypto.eddsa.math.ScalarOps;
1917

2018
/**
2119
* @author str4d
@@ -111,21 +109,16 @@ protected void engineUpdate(byte[] b, int off, int len)
111109
@Override
112110
protected byte[] engineSign() throws SignatureException {
113111
Curve curve = key.getParams().getCurve();
114-
BigInteger l = key.getParams().getL();
115-
BigInteger a = ((EdDSAPrivateKey) key).geta();
116-
LittleEndianEncoding leEnc = new LittleEndianEncoding();
112+
ScalarOps sc = key.getParams().getScalarOps();
113+
byte[] a = ((EdDSAPrivateKey) key).geta();
117114

118115
byte[] message = baos.toByteArray();
119116
// r = H(h_b,...,h_2b-1,M)
120117
byte[] r = digest.digest(message);
121-
// From the Ed25519 paper:
122-
// Here we interpret 2b-bit strings in little-endian form as integers in
123-
// {0, 1,..., 2^(2b)-1}.
124-
BigInteger rBI = leEnc.decode(r);
125118

126119
// r mod l
127120
// Reduces r from 64 bytes to 32 bytes
128-
r = leEnc.encode(rBI.mod(l), curve.getField().getb()/8);
121+
r = sc.reduce(r);
129122

130123
// R = rB
131124
GroupElement R = key.getParams().getB().scalarMultiply(r);
@@ -134,12 +127,14 @@ protected byte[] engineSign() throws SignatureException {
134127
// S = (r + H(Rbar,Abar,M)*a) mod l
135128
digest.update(Rbyte);
136129
digest.update(((EdDSAPrivateKey) key).getAbyte());
137-
FieldElement S = curve.fromBigInteger(leEnc.decode(digest.digest(message)).multiply(a).add(rBI).mod(l));
130+
byte[] h = digest.digest(message);
131+
h = sc.reduce(h);
132+
byte[] S = sc.multiplyAndAdd(h, a, r);
138133

139134
// R+S
140135
int b = curve.getField().getb();
141136
ByteBuffer out = ByteBuffer.allocate(b/4);
142-
out.put(Rbyte).put(S.toByteArray());
137+
out.put(Rbyte).put(S);
143138
return out.array();
144139
}
145140

@@ -158,20 +153,21 @@ protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
158153
byte[] h = digest.digest(message);
159154

160155
// h mod l
161-
LittleEndianEncoding leEnc = new LittleEndianEncoding();
162-
h = leEnc.encode(leEnc.decode(h).mod(key.getParams().getL()), b/8);
156+
h = key.getParams().getScalarOps().reduce(h);
163157

164158
byte[] Sbyte = Arrays.copyOfRange(sigBytes, b/8, b/4);
165159
// R = SB - H(Rbar,Abar,M)A
166160
GroupElement R = key.getParams().getB().doubleScalarMultiplyVariableTime(
167161
((EdDSAPublicKey) key).getNegativeA(), h, Sbyte);
168162

163+
// Variable time. This should be okay, because there are no secret
164+
// values used anywhere in verification.
169165
byte[] Rcalc = R.toByteArray();
170-
int result = 1;
171166
for (int i = 0; i < Rcalc.length; i++) {
172-
result &= Utils.equal(Rcalc[i], sigBytes[i]);
167+
if (Rcalc[i] != sigBytes[i])
168+
return false;
173169
}
174-
return result == 1;
170+
return true;
175171
}
176172

177173
/**

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package net.i2p.crypto.eddsa;
22

3-
import java.math.BigInteger;
43
import java.security.PrivateKey;
54

65
import net.i2p.crypto.eddsa.math.GroupElement;
@@ -16,7 +15,7 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
1615
private static final long serialVersionUID = 23495873459878957L;
1716
private transient final byte[] seed;
1817
private transient final byte[] h;
19-
private transient final BigInteger a;
18+
private transient final byte[] a;
2019
private transient final GroupElement A;
2120
private transient final byte[] Abyte;
2221
private transient final EdDSAParameterSpec edDsaSpec;
@@ -55,7 +54,7 @@ public byte[] getH() {
5554
return h;
5655
}
5756

58-
public BigInteger geta() {
57+
public byte[] geta() {
5958
return a;
6059
}
6160

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,19 @@ public static int negative(int b) {
3636
public static int bit(byte[] h, int i) {
3737
return (h[i/8] >> (i%8)) & 1;
3838
}
39+
40+
/**
41+
* Converts a hex string to bytes.
42+
* @param s the hex string to be converted.
43+
* @return the byte[]
44+
*/
45+
public static byte[] hexToBytes(String s) {
46+
int len = s.length();
47+
byte[] data = new byte[len / 2];
48+
for (int i = 0; i < len; i += 2) {
49+
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
50+
+ Character.digit(s.charAt(i+1), 16));
51+
}
52+
return data;
53+
}
3954
}
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package net.i2p.crypto.eddsa.math;
22

3-
import java.math.BigInteger;
3+
import net.i2p.crypto.eddsa.Utils;
44

55
public class Constants {
6-
public static final BigInteger ZERO = BigInteger.ZERO;
7-
public static final BigInteger ONE = BigInteger.ONE;
8-
public static final BigInteger TWO = BigInteger.valueOf(2);
9-
public static final BigInteger FOUR = BigInteger.valueOf(4);
10-
public static final BigInteger FIVE = BigInteger.valueOf(5);
11-
public static final BigInteger EIGHT = BigInteger.valueOf(8);
6+
public static final byte[] ZERO = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000");
7+
public static final byte[] ONE = Utils.hexToBytes("0100000000000000000000000000000000000000000000000000000000000000");
8+
public static final byte[] TWO = Utils.hexToBytes("0200000000000000000000000000000000000000000000000000000000000000");
9+
public static final byte[] FOUR = Utils.hexToBytes("0400000000000000000000000000000000000000000000000000000000000000");
10+
public static final byte[] FIVE = Utils.hexToBytes("0500000000000000000000000000000000000000000000000000000000000000");
11+
public static final byte[] EIGHT = Utils.hexToBytes("0800000000000000000000000000000000000000000000000000000000000000");
1212
}

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

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package net.i2p.crypto.eddsa.math;
22

33
import java.io.Serializable;
4-
import java.math.BigInteger;
54

65
/**
76
* A twisted Edwards curve.
@@ -20,16 +19,16 @@ public class Curve implements Serializable {
2019
private final GroupElement zeroP3;
2120
private final GroupElement zeroPrecomp;
2221

23-
public Curve(Field f, BigInteger d) {
22+
public Curve(Field f, byte[] d, FieldElement I) {
2423
this.f = f;
25-
this.d = fromBigInteger(d);
26-
this.d2 = this.d.multiply(fromBigInteger(Constants.TWO));
27-
this.I = fromBigInteger(Constants.TWO).modPow(f.getQ().subtract(Constants.ONE).divide(Constants.FOUR), f.getQ());
24+
this.d = f.fromByteArray(d);
25+
this.d2 = this.d.add(this.d);
26+
this.I = I;
2827

29-
FieldElement zero = fromBigInteger(Constants.ZERO);
30-
FieldElement one = fromBigInteger(Constants.ONE);
28+
FieldElement zero = f.zero;
29+
FieldElement one = f.one;
3130
zeroP2 = GroupElement.p2(this, zero, one, one);
32-
zeroP3 = createPoint(Constants.ZERO, Constants.ONE);
31+
zeroP3 = GroupElement.p3(this, zero, one, one, zero);
3332
zeroPrecomp = GroupElement.precomp(this, one, one, zero);
3433
}
3534

@@ -62,22 +61,8 @@ public GroupElement getZero(GroupElement.Representation repr) {
6261
}
6362
}
6463

65-
public FieldElement fromBigInteger(BigInteger x) {
66-
return new FieldElement(f, x);
67-
}
68-
69-
public FieldElement fromByteArray(byte[] x) {
70-
return new FieldElement(f, x);
71-
}
72-
73-
public GroupElement createPoint(BigInteger x, BigInteger y) {
74-
return createPoint(x, y, false);
75-
}
76-
77-
public GroupElement createPoint(BigInteger x, BigInteger y, boolean precompute) {
78-
FieldElement X = fromBigInteger(x);
79-
FieldElement Y = fromBigInteger(y);
80-
GroupElement ge = GroupElement.p3(this, X, Y, fromBigInteger(Constants.ONE), X.multiply(Y));
64+
public GroupElement createPoint(byte[] P, boolean precompute) {
65+
GroupElement ge = new GroupElement(this, P);
8166
if (precompute)
8267
ge.precompute(true);
8368
return ge;
Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
11
package net.i2p.crypto.eddsa.math;
22

3-
import java.math.BigInteger;
4-
53
/**
64
* Common interface for all (b-1)-bit encodings of elements
75
* of EdDSA finite fields.
86
* @author str4d
97
*
108
*/
11-
public interface Encoding {
12-
public byte[] encode(BigInteger x, int len);
13-
public BigInteger decode(byte[] in);
9+
public abstract class Encoding {
10+
protected Field f;
11+
12+
public void setField(Field f) {
13+
this.f = f;
14+
}
15+
16+
/**
17+
* Encode a FieldElement in its (b-1)-bit encoding.
18+
* @return the (b-1)-bit encoding of this FieldElement.
19+
*/
20+
public abstract byte[] encode(FieldElement x);
21+
22+
/**
23+
* Decode a FieldElement from its (b-1)-bit encoding.
24+
* The highest bit is masked out.
25+
* @param val the (b-1)-bit encoding of a FieldElement.
26+
* @return the FieldElement represented by 'val'.
27+
*/
28+
public abstract FieldElement decode(byte[] in);
1429

1530
/**
1631
* From the Ed25519 paper:
@@ -20,5 +35,5 @@ public interface Encoding {
2035
* elements of F_q are {1, 3, 5,..., q-2}.
2136
* @return
2237
*/
23-
public boolean isNegative(BigInteger x);
38+
public abstract boolean isNegative(FieldElement x);
2439
}

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

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package net.i2p.crypto.eddsa.math;
22

33
import java.io.Serializable;
4-
import java.math.BigInteger;
54

65
/**
76
* An EdDSA finite field. Includes several pre-computed values.
@@ -10,51 +9,66 @@
109
*/
1110
public class Field implements Serializable {
1211
private static final long serialVersionUID = 8746587465875676L;
12+
13+
public final FieldElement zero;
14+
public final FieldElement one;
15+
public final FieldElement two;
16+
public final FieldElement four;
17+
public final FieldElement five;
18+
public final FieldElement eight;
19+
1320
private final int b;
14-
private final BigInteger q;
21+
private final FieldElement q;
1522
/**
1623
* q-2
1724
*/
18-
private final BigInteger qm2;
25+
private final FieldElement qm2;
1926
/**
2027
* (q-5) / 8
2128
*/
22-
private final BigInteger qm5d8;
23-
/**
24-
* Mask where only the first b-1 bits are set.
25-
*/
26-
private final BigInteger mask;
29+
private final FieldElement qm5d8;
2730
private final Encoding enc;
2831

29-
public Field(int b, BigInteger q, Encoding enc) {
32+
public Field(int b, byte[] q, Encoding enc) {
3033
this.b = b;
31-
this.q = q;
32-
this.qm2 = q.subtract(Constants.TWO);
33-
this.qm5d8 = q.subtract(Constants.FIVE).divide(Constants.EIGHT);
34-
this.mask = Constants.ONE.shiftLeft(b-1).subtract(Constants.ONE);
3534
this.enc = enc;
35+
this.enc.setField(this);
36+
37+
this.q = fromByteArray(q);
38+
39+
// Set up constants
40+
zero = fromByteArray(Constants.ZERO);
41+
one = fromByteArray(Constants.ONE);
42+
two = fromByteArray(Constants.TWO);
43+
four = fromByteArray(Constants.FOUR);
44+
five = fromByteArray(Constants.FIVE);
45+
eight = fromByteArray(Constants.EIGHT);
46+
47+
// Precompute values
48+
qm2 = this.q.subtract(two);
49+
qm5d8 = this.q.subtract(five).divide(eight);
50+
}
51+
52+
public FieldElement fromByteArray(byte[] x) {
53+
return enc.decode(x);
3654
}
3755

3856
public int getb() {
3957
return b;
4058
}
4159

42-
public BigInteger getQ() {
60+
public FieldElement getQ() {
4361
return q;
4462
}
4563

46-
public BigInteger getQm2() {
64+
public FieldElement getQm2() {
4765
return qm2;
4866
}
4967

50-
public BigInteger getQm5d8() {
68+
public FieldElement getQm5d8() {
5169
return qm5d8;
5270
}
5371

54-
public BigInteger getMask() {
55-
return mask;
56-
}
57-
5872
public Encoding getEncoding(){
5973
return enc;
6074
}

0 commit comments

Comments
 (0)