EdDSA: Backport versions 0.2/0.3 from github:

- Change key encoding to match curdle draft
- Support key decoding based on curdle draft
- Implement true constant-time cmov()
- Add handling of X509Key-wrapped EdDSA keys (GitHub PR #47)
- Clarify that KeyPairGenerator takes a key size, not strength
- Javadocs
GitHub PR #58:
- Make GroupElement immutable by moving the pre-computed logic to the constructors,
  allowing the synchronized checking of whether the pre-computed logic had executed or not
  to be removed since it always has when it is used because those code paths
  are modified to request it at construction time.
- This allows getNegativeA() to be lazy, and doesn't need volatile due to the immutability
  (and final fields - this is important part of the contract with the JVM memory model).
- Remove synchronized contention from the named curve table get method.
- Generally remove use of the named curve table get method with a constant curve name
  in hot code paths in favour of using a new static constant for the curve spec.
Overall performance changes:
- Keygen 46% faster
- Signing 39% slower (due to cmov)
- Verify 2% faster
This commit is contained in:
zzz
2018-07-01 11:10:06 +00:00
parent c65ce1d3f9
commit eff0cac30b
19 changed files with 624 additions and 226 deletions

View File

@ -11,6 +11,8 @@ import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import net.i2p.crypto.eddsa.math.Curve;
@ -54,6 +56,8 @@ import net.i2p.crypto.eddsa.math.ScalarOps;
*
*/
public final class EdDSAEngine extends Signature {
public static final String SIGNATURE_ALGORITHM = "NONEwithEdDSA";
private MessageDigest digest;
private ByteArrayOutputStream baos;
private EdDSAKey key;
@ -76,14 +80,14 @@ public final class EdDSAEngine extends Signature {
private static class OneShotSpec implements AlgorithmParameterSpec {}
/**
* No specific hash requested, allows any EdDSA key.
* No specific EdDSA-internal hash requested, allows any EdDSA key.
*/
public EdDSAEngine() {
super("EdDSA");
super(SIGNATURE_ALGORITHM);
}
/**
* Specific hash requested, only matching keys will be allowed.
* Specific EdDSA-internal hash requested, only matching keys will be allowed.
* @param digest the hash algorithm that keys must have to sign or verify.
*/
public EdDSAEngine(MessageDigest digest) {
@ -147,6 +151,16 @@ public final class EdDSAEngine extends Signature {
}
} else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm()))
throw new InvalidKeyException("Key hash algorithm does not match chosen digest");
} else if (publicKey.getClass().getName().equals("sun.security.x509.X509Key")) {
// X509Certificate will sometimes contain an X509Key rather than the EdDSAPublicKey itself; the contained
// key is valid but needs to be instanced as an EdDSAPublicKey before it can be used.
EdDSAPublicKey parsedPublicKey;
try {
parsedPublicKey = new EdDSAPublicKey(new X509EncodedKeySpec(publicKey.getEncoded()));
} catch (InvalidKeySpecException ex) {
throw new InvalidKeyException("cannot handle X.509 EdDSA public key: " + publicKey.getAlgorithm());
}
engineInitVerify(parsedPublicKey);
} else {
throw new InvalidKeyException("cannot identify EdDSA public key: " + publicKey.getClass());
}
@ -311,6 +325,8 @@ public final class EdDSAEngine extends Signature {
* sig = sign()
*</pre>
*
* @param data the message to be signed
* @return the signature
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
@ -330,6 +346,10 @@ public final class EdDSAEngine extends Signature {
* sig = sign()
*</pre>
*
* @param data byte array containing the message to be signed
* @param off the start of the message inside data
* @param len the length of the message
* @return the signature
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
@ -351,6 +371,9 @@ public final class EdDSAEngine extends Signature {
* ok = verify(signature)
*</pre>
*
* @param data the message that was signed
* @param signature of the message
* @return true if the signature is valid, false otherwise
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
@ -370,6 +393,11 @@ public final class EdDSAEngine extends Signature {
* ok = verify(signature)
*</pre>
*
* @param data byte array containing the message that was signed
* @param off the start of the message inside data
* @param len the length of the message
* @param signature of the message
* @return true if the signature is valid, false otherwise
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
@ -389,6 +417,11 @@ public final class EdDSAEngine extends Signature {
* ok = verify(signature, sigoff, siglen)
*</pre>
*
* @param data the message that was signed
* @param signature byte array containing the signature
* @param sigoff the start of the signature
* @param siglen the length of the signature
* @return true if the signature is valid, false otherwise
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25
@ -408,6 +441,13 @@ public final class EdDSAEngine extends Signature {
* ok = verify(signature, sigoff, siglen)
*</pre>
*
* @param data byte array containing the message that was signed
* @param off the start of the message inside data
* @param len the length of the message
* @param signature byte array containing the signature
* @param sigoff the start of the signature
* @param siglen the length of the signature
* @return true if the signature is valid, false otherwise
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
* @since 0.9.25

View File

@ -11,8 +11,14 @@ import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
*/
public interface EdDSAKey {
/**
* return a parameter specification representing the EdDSA domain
* parameters for the key.
* The reported key algorithm for all EdDSA keys
* @since 0.9.36
*/
public String KEY_ALGORITHM = "EdDSA";
/**
* @return a parameter specification representing the EdDSA domain
* parameters for the key.
*/
public EdDSAParameterSpec getParams();
}

View File

@ -13,11 +13,15 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
/**
* An EdDSA private key.
*<p>
* Warning: Private key encoding is not fully specified in the
* current IETF draft. This implementation uses PKCS#8 encoding,
* Warning: Private key encoding is based on the current curdle WG draft,
* and is subject to change. See getEncoded().
*</p><p>
* Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
* For compatibility with older releases, decoding supports both the old and new
* draft specifications. See decode().
*</p><p>
* Ref: https://tools.ietf.org/html/draft-ietf-curdle-pkix-04
*</p><p>
* Old Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
*</p>
*
* @since 0.9.15
@ -33,6 +37,12 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
private final byte[] Abyte;
private final EdDSAParameterSpec edDsaSpec;
// OID 1.3.101.xxx
private static final int OID_OLD = 100;
private static final int OID_ED25519 = 112;
private static final int OID_BYTE = 11;
private static final int IDLEN_BYTE = 6;
public EdDSAPrivateKey(EdDSAPrivateKeySpec spec) {
this.seed = spec.getSeed();
this.h = spec.getH();
@ -47,121 +57,213 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
*/
public EdDSAPrivateKey(PKCS8EncodedKeySpec spec) throws InvalidKeySpecException {
this(new EdDSAPrivateKeySpec(decode(spec.getEncoded()),
EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)));
EdDSANamedCurveTable.ED_25519_CURVE_SPEC));
}
@Override
public String getAlgorithm() {
return "EdDSA";
return KEY_ALGORITHM;
}
@Override
public String getFormat() {
return "PKCS#8";
}
/**
* This follows the docs from
* java.security.spec.PKCS8EncodedKeySpec
* quote:
* Returns the public key in its canonical encoding.
*<p>
* This implements the following specs:
*<ul><li>
* General encoding: https://tools.ietf.org/html/draft-ietf-curdle-pkix-04
*</li><li>
* Key encoding: https://tools.ietf.org/html/rfc8032
*</li></ul>
*<p>
* This encodes the seed. It will return null if constructed from
* a spec which was directly constructed from H, in which case seed is null.
*</p><p>
* For keys in older formats, decoding and then re-encoding is sufficient to
* migrate them to the canonical encoding.
*</p>
* Relevant spec quotes:
*<pre>
* The PrivateKeyInfo syntax is defined in the PKCS#8 standard as follows:
* PrivateKeyInfo ::= SEQUENCE {
* OneAsymmetricKey ::= SEQUENCE {
* version Version,
* privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
* privateKey PrivateKey,
* attributes [0] IMPLICIT Attributes OPTIONAL }
* attributes [0] Attributes OPTIONAL,
* ...,
* [[2: publicKey [1] PublicKey OPTIONAL ]],
* ...
* }
*
* Version ::= INTEGER
* PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
* PrivateKey ::= OCTET STRING
* PublicKey ::= OCTET STRING
* Attributes ::= SET OF Attribute
*</pre>
*
*<pre>
* AlgorithmIdentifier ::= SEQUENCE
* {
* algorithm OBJECT IDENTIFIER,
* parameters ANY OPTIONAL
* }
* ... when encoding a OneAsymmetricKey object, the private key is wrapped
* in a CurvePrivateKey object and wrapped by the OCTET STRING of the
* 'privateKey' field.
*
* CurvePrivateKey ::= OCTET STRING
*</pre>
*
* Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
*<pre>
* AlgorithmIdentifier ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER,
* parameters ANY DEFINED BY algorithm OPTIONAL
* }
*
* Note that the private key encoding is not fully specified in the Josefsson draft version 04,
* and the example could be wrong, as it's lacking Version and AlgorithmIdentifier.
* This will hopefully be clarified in the next draft.
* But sun.security.pkcs.PKCS8Key expects them so we must include them for keytool to work.
* For all of the OIDs, the parameters MUST be absent.
*</pre>
*
* This encodes the seed. It will return null if constructed from
* a spec which was directly constructed from H, in which case seed is null.
*<pre>
* id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 }
*</pre>
*
* @return 49 bytes for Ed25519, null for other curves
* @return 48 bytes for Ed25519, null for other curves
* @since implemented in 0.9.25
*/
@Override
public byte[] getEncoded() {
if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)))
if (!edDsaSpec.equals(EdDSANamedCurveTable.ED_25519_CURVE_SPEC))
return null;
int totlen = 17 + seed.length;
if (seed == null)
return null;
int totlen = 16 + seed.length;
byte[] rv = new byte[totlen];
int idx = 0;
// sequence
rv[idx++] = 0x30;
rv[idx++] = (byte) (15 + seed.length);
rv[idx++] = (byte) (totlen - 2);
// version
// not in the Josefsson example
rv[idx++] = 0x02;
rv[idx++] = 1;
// v1 - no public key included
rv[idx++] = 0;
// Algorithm Identifier
// sequence
// not in the Josefsson example
rv[idx++] = 0x30;
rv[idx++] = 8;
// OID 1.3.101.100
rv[idx++] = 5;
// OID
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb540809%28v=vs.85%29.aspx
// not in the Josefsson example
rv[idx++] = 0x06;
rv[idx++] = 3;
rv[idx++] = (1 * 40) + 3;
rv[idx++] = 101;
rv[idx++] = 100;
// params
rv[idx++] = 0x0a;
rv[idx++] = 1;
rv[idx++] = 1; // Ed25519
// the key
rv[idx++] = (byte) OID_ED25519;
// params - absent
// PrivateKey
rv[idx++] = 0x04; // octet string
rv[idx++] = (byte) (2 + seed.length);
// CurvePrivateKey
rv[idx++] = 0x04; // octet string
rv[idx++] = (byte) seed.length;
// the key
System.arraycopy(seed, 0, rv, idx, seed.length);
return rv;
}
/**
* This is really dumb for now.
* See getEncoded().
* Extracts the private key bytes from the provided encoding.
*<p>
* This will decode data conforming to the current spec at
* https://tools.ietf.org/html/draft-ietf-curdle-pkix-04
* or as inferred from the old spec at
* https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04.
*</p><p>
* Contrary to draft-ietf-curdle-pkix-04, it WILL accept a parameter value
* of NULL, as it is required for interoperability with the default Java
* keystore. Other implementations MUST NOT copy this behaviour from here
* unless they also need to read keys from the default Java keystore.
*</p><p>
* This is really dumb for now. It does not use a general-purpose ASN.1 decoder.
* See also getEncoded().
*
* @return 32 bytes for Ed25519, throws for other curves
* @since 0.9.25
*/
private static byte[] decode(byte[] d) throws InvalidKeySpecException {
try {
//
// Setup and OID check
//
int totlen = 48;
int idlen = 5;
int doid = d[OID_BYTE];
if (doid == OID_OLD) {
totlen = 49;
idlen = 8;
} else if (doid == OID_ED25519) {
// Detect parameter value of NULL
if (d[IDLEN_BYTE] == 7) {
totlen = 50;
idlen = 7;
}
} else {
throw new InvalidKeySpecException("unsupported key spec");
}
//
// Pre-decoding check
//
if (d.length != totlen) {
throw new InvalidKeySpecException("invalid key spec length");
}
//
// Decoding
//
int idx = 0;
if (d[idx++] != 0x30 ||
d[idx++] != 47 ||
d[idx++] != (totlen - 2) ||
d[idx++] != 0x02 ||
d[idx++] != 1 ||
d[idx++] != 0 ||
d[idx++] != 0x30 ||
d[idx++] != 8 ||
d[idx++] != idlen ||
d[idx++] != 0x06 ||
d[idx++] != 3 ||
d[idx++] != (1 * 40) + 3 ||
d[idx++] != 101 ||
d[idx++] != 100 ||
d[idx++] != 0x0a ||
d[idx++] != 1 ||
d[idx++] != 1 ||
d[idx++] != 0x04 ||
d[idx++] != 101) {
throw new InvalidKeySpecException("unsupported key spec");
}
idx++; // OID, checked above
// parameters only with old OID
if (doid == OID_OLD) {
if (d[idx++] != 0x0a ||
d[idx++] != 1 ||
d[idx++] != 1) {
throw new InvalidKeySpecException("unsupported key spec");
}
} else {
// Handle parameter value of NULL
//
// Quote https://tools.ietf.org/html/draft-ietf-curdle-pkix-04 :
// For all of the OIDs, the parameters MUST be absent.
// Regardless of the defect in the original 1997 syntax,
// implementations MUST NOT accept a parameters value of NULL.
//
// But Java's default keystore puts it in (when decoding as
// PKCS8 and then re-encoding to pass on), so we must accept it.
if (idlen == 7) {
if (d[idx++] != 0x05 ||
d[idx++] != 0) {
throw new InvalidKeySpecException("unsupported key spec");
}
}
// PrivateKey wrapping the CurvePrivateKey
if (d[idx++] != 0x04 ||
d[idx++] != 34) {
throw new InvalidKeySpecException("unsupported key spec");
}
}
if (d[idx++] != 0x04 ||
d[idx++] != 32) {
throw new InvalidKeySpecException("unsupported key spec");
}
@ -173,6 +275,7 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
}
}
@Override
public EdDSAParameterSpec getParams() {
return edDsaSpec;
}

View File

@ -13,10 +13,15 @@ import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
/**
* An EdDSA public key.
*<p>
* Warning: Public key encoding is is based on the
* current IETF draft, and is subject to change. See getEncoded().
* Warning: Public key encoding is is based on the current curdle WG draft,
* and is subject to change. See getEncoded().
*</p><p>
* Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
* For compatibility with older releases, decoding supports both the old and new
* draft specifications. See decode().
*</p><p>
* Ref: https://tools.ietf.org/html/draft-ietf-curdle-pkix-04
*</p><p>
* Old Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
*</p>
*
* @since 0.9.15
@ -26,13 +31,18 @@ import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
public class EdDSAPublicKey implements EdDSAKey, PublicKey {
private static final long serialVersionUID = 9837459837498475L;
private final GroupElement A;
private final GroupElement Aneg;
private GroupElement Aneg;
private final byte[] Abyte;
private final EdDSAParameterSpec edDsaSpec;
// OID 1.3.101.xxx
private static final int OID_OLD = 100;
private static final int OID_ED25519 = 112;
private static final int OID_BYTE = 8;
private static final int IDLEN_BYTE = 3;
public EdDSAPublicKey(EdDSAPublicKeySpec spec) {
this.A = spec.getA();
this.Aneg = spec.getNegativeA();
this.Abyte = this.A.toByteArray();
this.edDsaSpec = spec.getParams();
}
@ -42,65 +52,81 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey {
*/
public EdDSAPublicKey(X509EncodedKeySpec spec) throws InvalidKeySpecException {
this(new EdDSAPublicKeySpec(decode(spec.getEncoded()),
EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)));
EdDSANamedCurveTable.ED_25519_CURVE_SPEC));
}
@Override
public String getAlgorithm() {
return "EdDSA";
return KEY_ALGORITHM;
}
@Override
public String getFormat() {
return "X.509";
}
/**
* This follows the spec at
* ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
* which matches the docs from
* java.security.spec.X509EncodedKeySpec
* quote:
* Returns the public key in its canonical encoding.
*<p>
* This implements the following specs:
*<ul><li>
* General encoding: https://tools.ietf.org/html/draft-ietf-curdle-pkix-04
*</li><li>
* Key encoding: https://tools.ietf.org/html/rfc8032
*</li></ul>
*<p>
* For keys in older formats, decoding and then re-encoding is sufficient to
* migrate them to the canonical encoding.
*</p>
* Relevant spec quotes:
*<pre>
* The SubjectPublicKeyInfo syntax is defined in the X.509 standard as follows:
* SubjectPublicKeyInfo ::= SEQUENCE {
* algorithm AlgorithmIdentifier,
* subjectPublicKey BIT STRING }
*</pre>
* In the X.509 certificate, the subjectPublicKeyInfo field has the
* SubjectPublicKeyInfo type, which has the following ASN.1 syntax:
*
*<pre>
* AlgorithmIdentifier ::= SEQUENCE
* {
* algorithm OBJECT IDENTIFIER,
* parameters ANY OPTIONAL
* SubjectPublicKeyInfo ::= SEQUENCE {
* algorithm AlgorithmIdentifier,
* subjectPublicKey BIT STRING
* }
*</pre>
*
* @return 47 bytes for Ed25519, null for other curves
*<pre>
* AlgorithmIdentifier ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER,
* parameters ANY DEFINED BY algorithm OPTIONAL
* }
*
* For all of the OIDs, the parameters MUST be absent.
*</pre>
*
*<pre>
* id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 }
*</pre>
*
* @return 44 bytes for Ed25519, null for other curves
* @since implemented in 0.9.25
*/
@Override
public byte[] getEncoded() {
if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)))
if (!edDsaSpec.equals(EdDSANamedCurveTable.ED_25519_CURVE_SPEC))
return null;
int totlen = 15 + Abyte.length;
int totlen = 12 + Abyte.length;
byte[] rv = new byte[totlen];
int idx = 0;
// sequence
rv[idx++] = 0x30;
rv[idx++] = (byte) (13 + Abyte.length);
rv[idx++] = (byte) (totlen - 2);
// Algorithm Identifier
// sequence
rv[idx++] = 0x30;
rv[idx++] = 8;
// OID 1.3.101.100
rv[idx++] = 5;
// OID
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb540809%28v=vs.85%29.aspx
rv[idx++] = 0x06;
rv[idx++] = 3;
rv[idx++] = (1 * 40) + 3;
rv[idx++] = 101;
rv[idx++] = 100;
// params
rv[idx++] = 0x0a;
rv[idx++] = 1;
rv[idx++] = 1; // Ed25519
rv[idx++] = (byte) OID_ED25519;
// params - absent
// the key
rv[idx++] = 0x03; // bit string
rv[idx++] = (byte) (1 + Abyte.length);
@ -110,28 +136,93 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey {
}
/**
* This is really dumb for now.
* See getEncoded().
* Extracts the public key bytes from the provided encoding.
*<p>
* This will decode data conforming to the current spec at
* https://tools.ietf.org/html/draft-ietf-curdle-pkix-04
* or the old spec at
* https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04.
*</p><p>
* Contrary to draft-ietf-curdle-pkix-04, it WILL accept a parameter value
* of NULL, as it is required for interoperability with the default Java
* keystore. Other implementations MUST NOT copy this behaviour from here
* unless they also need to read keys from the default Java keystore.
*</p><p>
* This is really dumb for now. It does not use a general-purpose ASN.1 decoder.
* See also getEncoded().
*</p>
*
* @return 32 bytes for Ed25519, throws for other curves
* @since 0.9.25
*/
private static byte[] decode(byte[] d) throws InvalidKeySpecException {
try {
//
// Setup and OID check
//
int totlen = 44;
int idlen = 5;
int doid = d[OID_BYTE];
if (doid == OID_OLD) {
totlen = 47;
idlen = 8;
} else if (doid == OID_ED25519) {
// Detect parameter value of NULL
if (d[IDLEN_BYTE] == 7) {
totlen = 46;
idlen = 7;
}
} else {
throw new InvalidKeySpecException("unsupported key spec");
}
//
// Pre-decoding check
//
if (d.length != totlen) {
throw new InvalidKeySpecException("invalid key spec length");
}
//
// Decoding
//
int idx = 0;
if (d[idx++] != 0x30 ||
d[idx++] != 45 ||
d[idx++] != (totlen - 2) ||
d[idx++] != 0x30 ||
d[idx++] != 8 ||
d[idx++] != idlen ||
d[idx++] != 0x06 ||
d[idx++] != 3 ||
d[idx++] != (1 * 40) + 3 ||
d[idx++] != 101 ||
d[idx++] != 100 ||
d[idx++] != 0x0a ||
d[idx++] != 1 ||
d[idx++] != 1 ||
d[idx++] != 0x03 ||
d[idx++] != 101) {
throw new InvalidKeySpecException("unsupported key spec");
}
idx++; // OID, checked above
// parameters only with old OID
if (doid == OID_OLD) {
if (d[idx++] != 0x0a ||
d[idx++] != 1 ||
d[idx++] != 1) {
throw new InvalidKeySpecException("unsupported key spec");
}
} else {
// Handle parameter value of NULL
//
// Quote https://tools.ietf.org/html/draft-ietf-curdle-pkix-04 :
// For all of the OIDs, the parameters MUST be absent.
// Regardless of the defect in the original 1997 syntax,
// implementations MUST NOT accept a parameters value of NULL.
//
// But Java's default keystore puts it in (when decoding as
// PKCS8 and then re-encoding to pass on), so we must accept it.
if (idlen == 7) {
if (d[idx++] != 0x05 ||
d[idx++] != 0) {
throw new InvalidKeySpecException("unsupported key spec");
}
}
}
if (d[idx++] != 0x03 ||
d[idx++] != 33 ||
d[idx++] != 0) {
throw new InvalidKeySpecException("unsupported key spec");
@ -144,6 +235,7 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey {
}
}
@Override
public EdDSAParameterSpec getParams() {
return edDsaSpec;
}
@ -153,7 +245,13 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey {
}
public GroupElement getNegativeA() {
return Aneg;
// Only read Aneg once, otherwise read re-ordering might occur between here and return. Requires all GroupElement's fields to be final.
GroupElement ourAneg = Aneg;
if(ourAneg == null) {
ourAneg = A.negate();
Aneg = ourAneg;
}
return ourAneg;
}
public byte[] getAbyte() {

View File

@ -17,12 +17,12 @@ import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import net.i2p.util.RandomSource;
/**
* Default strength is 256
* Default keysize is 256 (Ed25519)
*
* @since 0.9.15
*/
public final class KeyPairGenerator extends KeyPairGeneratorSpi {
private static final int DEFAULT_STRENGTH = 256;
private static final int DEFAULT_KEYSIZE = 256;
private EdDSAParameterSpec edParams;
private SecureRandom random;
private boolean initialized;
@ -32,11 +32,11 @@ public final class KeyPairGenerator extends KeyPairGeneratorSpi {
static {
edParameters = new Hashtable<Integer, AlgorithmParameterSpec>();
edParameters.put(Integer.valueOf(DEFAULT_STRENGTH), new EdDSAGenParameterSpec(EdDSANamedCurveTable.CURVE_ED25519_SHA512));
edParameters.put(Integer.valueOf(DEFAULT_KEYSIZE), new EdDSAGenParameterSpec(EdDSANamedCurveTable.ED_25519));
}
public void initialize(int strength, SecureRandom random) {
AlgorithmParameterSpec edParams = edParameters.get(Integer.valueOf(strength));
public void initialize(int keysize, SecureRandom random) {
AlgorithmParameterSpec edParams = edParameters.get(Integer.valueOf(keysize));
if (edParams == null)
throw new InvalidParameterException("unknown key type.");
try {
@ -61,7 +61,7 @@ public final class KeyPairGenerator extends KeyPairGeneratorSpi {
public KeyPair generateKeyPair() {
if (!initialized)
initialize(DEFAULT_STRENGTH, RandomSource.getInstance());
initialize(DEFAULT_KEYSIZE, RandomSource.getInstance());
byte[] seed = new byte[edParams.getCurve().getField().getb()/8];
random.nextBytes(seed);

View File

@ -1,7 +1,7 @@
package net.i2p.crypto.eddsa;
/**
* Basic utilities for eddsa.
* Basic utilities for EdDSA.
* Not for external use, not maintained as a public API.
*
* @since 0.9.15
@ -11,6 +11,8 @@ package net.i2p.crypto.eddsa;
public class Utils {
/**
* Constant-time byte comparison.
* @param b a byte
* @param c a byte
* @return 1 if b and c are equal, 0 otherwise.
*/
public static int equal(int b, int c) {
@ -24,6 +26,8 @@ public class Utils {
/**
* Constant-time byte[] comparison.
* @param b a byte[]
* @param c a byte[]
* @return 1 if b and c are equal, 0 otherwise.
*/
public static int equal(byte[] b, byte[] c) {

View File

@ -19,6 +19,7 @@ public class Curve implements Serializable {
private final GroupElement zeroP2;
private final GroupElement zeroP3;
private final GroupElement zeroP3PrecomputedDouble;
private final GroupElement zeroPrecomp;
public Curve(Field f, byte[] d, FieldElement I) {
@ -30,7 +31,8 @@ public class Curve implements Serializable {
FieldElement zero = f.ZERO;
FieldElement one = f.ONE;
zeroP2 = GroupElement.p2(this, zero, one, one);
zeroP3 = GroupElement.p3(this, zero, one, one, zero);
zeroP3 = GroupElement.p3(this, zero, one, one, zero, false);
zeroP3PrecomputedDouble = GroupElement.p3(this, zero, one, one, zero, true);
zeroPrecomp = GroupElement.precomp(this, one, one, zero);
}
@ -56,6 +58,8 @@ public class Curve implements Serializable {
return zeroP2;
case P3:
return zeroP3;
case P3PrecomputedDouble:
return zeroP3PrecomputedDouble;
case PRECOMP:
return zeroPrecomp;
default:
@ -64,9 +68,7 @@ public class Curve implements Serializable {
}
public GroupElement createPoint(byte[] P, boolean precompute) {
GroupElement ge = new GroupElement(this, P);
if (precompute)
ge.precompute(true);
GroupElement ge = new GroupElement(this, P, precompute);
return ge;
}

View File

@ -63,5 +63,10 @@ public abstract class FieldElement implements Serializable {
public abstract FieldElement pow22523();
/**
* @since 0.9.36
*/
public abstract FieldElement cmov(FieldElement val, final int b);
// Note: concrete subclasses must implement hashCode() and equals()
}

View File

@ -40,6 +40,11 @@ public class GroupElement implements Serializable {
P2,
/** Extended (P^3): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT */
P3,
/**
* Can only be requested. Results in P3 representation but also populates dblPrecmp.
* @since 0.9.36
*/
P3PrecomputedDouble,
/** Completed (P x P): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T */
P1P1,
/** Precomputed (Duif): (y+x,y-x,2dxy) */
@ -81,7 +86,29 @@ public class GroupElement implements Serializable {
final FieldElement Y,
final FieldElement Z,
final FieldElement T) {
return new GroupElement(curve, Representation.P3, X, Y, Z, T);
return p3(curve, X, Y, Z, T, false);
}
/**
* Creates a new group element in P3 representation, potentially with pre-computation.
*
* @param curve The curve.
* @param X The X coordinate.
* @param Y The Y coordinate.
* @param Z The Z coordinate.
* @param T The T coordinate.
* @param precomputeDoubleOnly If true, populate dblPrecmp, else set to null.
* @return The group element in P3 representation.
* @since 0.9.36
*/
public static GroupElement p3(
final Curve curve,
final FieldElement X,
final FieldElement Y,
final FieldElement Z,
final FieldElement T,
final boolean precomputeDoubleOnly) {
return new GroupElement(curve, Representation.P3, X, Y, Z, T, precomputeDoubleOnly);
}
/**
@ -175,7 +202,7 @@ public class GroupElement implements Serializable {
* <p>
* Variable is package private only so that tests run.
*/
GroupElement[][] precmp;
final GroupElement[][] precmp;
/**
* Precomputed table for {@link #doubleScalarMultiplyVariableTime(GroupElement, byte[], byte[])},
@ -183,10 +210,10 @@ public class GroupElement implements Serializable {
* <p>
* Variable is package private only so that tests run.
*/
GroupElement[] dblPrecmp;
final GroupElement[] dblPrecmp;
/**
* Creates a group element for a curve.
* Creates a group element for a curve, without any pre-computation.
*
* @param curve The curve.
* @param repr The representation used to represent the group element.
@ -202,12 +229,37 @@ public class GroupElement implements Serializable {
final FieldElement Y,
final FieldElement Z,
final FieldElement T) {
this(curve, repr, X, Y, Z, T, false);
}
/**
* Creates a group element for a curve, with optional pre-computation.
*
* @param curve The curve.
* @param repr The representation used to represent the group element.
* @param X The X coordinate.
* @param Y The Y coordinate.
* @param Z The Z coordinate.
* @param T The T coordinate.
* @param precomputeDouble If true, populate dblPrecmp, else set to null.
* @since 0.9.36
*/
public GroupElement(
final Curve curve,
final Representation repr,
final FieldElement X,
final FieldElement Y,
final FieldElement Z,
final FieldElement T,
final boolean precomputeDouble) {
this.curve = curve;
this.repr = repr;
this.X = X;
this.Y = Y;
this.Z = Z;
this.T = T;
this.precmp = null;
this.dblPrecmp = precomputeDouble ? precomputeDouble() : null;
}
/**
@ -215,8 +267,28 @@ public class GroupElement implements Serializable {
* <p>
* A point (x,y) is encoded by storing y in bit 0 to bit 254 and the sign of x in bit 255.
* x is recovered in the following way:
* <p><ul>
* </p><ul>
* <li>x = sign(x) * sqrt((y^2 - 1) / (d * y^2 + 1)) = sign(x) * sqrt(u / v) with u = y^2 - 1 and v = d * y^2 + 1.
* <li>Setting = (u * v^3) * (u * v^7)^((q - 5) / 8) one has ^2 = +-(u / v).
* <li>If v * = -u multiply with i=sqrt(-1).
* <li>Set x := .
* <li>If sign(x) != bit 255 of s then negate x.
* </ul>
*
* @param curve The curve.
* @param s The encoded point.
*/
public GroupElement(final Curve curve, final byte[] s) {
this(curve, s, false);
}
/**
* Creates a group element for a curve from a given encoded point. With optional pre-computation.
* <p>
* A point (x,y) is encoded by storing y in bit 0 to bit 254 and the sign of x in bit 255.
* x is recovered in the following way:
* </p><ul>
* <li>x = sign(x) * \sqrt{(y^2 - 1) / (d * y^2 + 1)} = sign(x) * \sqrt{u / v} with u = y^2 - 1 and v = d * y^2 + 1.
* <li>Setting β = (u * v^3) * (u * v^7)^((q - 5) / 8) one has β^2 = +-(u / v).
* <li>If v * β = -u multiply β with i=sqrt(-1).
* <li>Set x := β.
@ -225,8 +297,10 @@ public class GroupElement implements Serializable {
*
* @param curve The curve.
* @param s The encoded point.
* @param precomputeSingleAndDouble If true, populate both precmp and dblPrecmp, else set both to null.
* @since 0.9.36
*/
public GroupElement(final Curve curve, final byte[] s) {
public GroupElement(final Curve curve, final byte[] s, boolean precomputeSingleAndDouble) {
FieldElement x, y, yy, u, v, v3, vxx, check;
y = curve.getField().fromByteArray(s);
yy = y.square();
@ -241,7 +315,7 @@ public class GroupElement implements Serializable {
v3 = v.square().multiply(v);
// x = (v3^2)vu, aka x = uv^7
x = v3.square().multiply(v).multiply(u);
x = v3.square().multiply(v).multiply(u);
// x = (uv^7)^((q-5)/8)
x = x.pow22523();
@ -269,6 +343,13 @@ public class GroupElement implements Serializable {
this.Y = y;
this.Z = curve.getField().ONE;
this.T = this.X.multiply(this.Y);
if (precomputeSingleAndDouble) {
precmp = precomputeSingle();
dblPrecmp = precomputeDouble();
} else {
precmp = null;
dblPrecmp = null;
}
}
/**
@ -367,6 +448,16 @@ public class GroupElement implements Serializable {
return toRep(Representation.P3);
}
/**
* Converts the group element to the P3 representation, with dblPrecmp populated.
*
* @return The group element in the P3 representation.
* @since 0.9.36
*/
public GroupElement toP3PrecomputeDouble() {
return toRep(Representation.P3PrecomputedDouble);
}
/**
* Converts the group element to the CACHED representation.
*
@ -382,7 +473,7 @@ public class GroupElement implements Serializable {
* r = p
* <p>
* Supported conversions:
* <p><ul>
* </p><ul>
* <li>P3 -> P2
* <li>P3 -> CACHED (1 multiply, 1 add, 1 subtract)
* <li>P1P1 -> P2 (3 multiply)
@ -416,7 +507,9 @@ public class GroupElement implements Serializable {
case P2:
return p2(this.curve, this.X.multiply(this.T), Y.multiply(this.Z), this.Z.multiply(this.T));
case P3:
return p3(this.curve, this.X.multiply(this.T), Y.multiply(this.Z), this.Z.multiply(this.T), this.X.multiply(this.Y));
return p3(this.curve, this.X.multiply(this.T), Y.multiply(this.Z), this.Z.multiply(this.T), this.X.multiply(this.Y), false);
case P3PrecomputedDouble:
return p3(this.curve, this.X.multiply(this.T), Y.multiply(this.Z), this.Z.multiply(this.T), this.X.multiply(this.Y), true);
case P1P1:
return p1p1(this.curve, this.X, this.Y, this.Z, this.T);
default:
@ -442,51 +535,49 @@ public class GroupElement implements Serializable {
}
/**
* Precomputes several tables.
* <p>
* The precomputed tables are used for {@link #scalarMultiply(byte[])}
* and {@link #doubleScalarMultiplyVariableTime(GroupElement, byte[], byte[])}.
*
* @param precomputeSingle should the matrix for scalarMultiply() be precomputed?
* Precomputes table for {@link #scalarMultiply(byte[])}.
* @since 0.9.36 split out from precompute()
*/
public synchronized void precompute(final boolean precomputeSingle) {
GroupElement Bi;
if (precomputeSingle && this.precmp == null) {
// Precomputation for single scalar multiplication.
this.precmp = new GroupElement[32][8];
// TODO-CR BR: check that this == base point when the method is called.
Bi = this;
for (int i = 0; i < 32; i++) {
GroupElement Bij = Bi;
for (int j = 0; j < 8; j++) {
final FieldElement recip = Bij.Z.invert();
final FieldElement x = Bij.X.multiply(recip);
final FieldElement y = Bij.Y.multiply(recip);
this.precmp[i][j] = precomp(this.curve, y.add(x), y.subtract(x), x.multiply(y).multiply(this.curve.get2D()));
Bij = Bij.add(Bi.toCached()).toP3();
}
// Only every second summand is precomputed (16^2 = 256)
for (int k = 0; k < 8; k++) {
Bi = Bi.add(Bi.toCached()).toP3();
}
private GroupElement[][] precomputeSingle() {
// Precomputation for single scalar multiplication.
GroupElement[][] precmp = new GroupElement[32][8];
// TODO-CR BR: check that this == base point when the method is called.
GroupElement Bi = this;
for (int i = 0; i < 32; i++) {
GroupElement Bij = Bi;
for (int j = 0; j < 8; j++) {
final FieldElement recip = Bij.Z.invert();
final FieldElement x = Bij.X.multiply(recip);
final FieldElement y = Bij.Y.multiply(recip);
precmp[i][j] = precomp(this.curve, y.add(x), y.subtract(x), x.multiply(y).multiply(this.curve.get2D()));
Bij = Bij.add(Bi.toCached()).toP3();
}
// Only every second summand is precomputed (16^2 = 256)
for (int k = 0; k < 8; k++) {
Bi = Bi.add(Bi.toCached()).toP3();
}
}
return precmp;
}
/**
* Precomputes table for {@link #doubleScalarMultiplyVariableTime(GroupElement, byte[], byte[])}.
* @since 0.9.36 split out from precompute()
*/
private GroupElement[] precomputeDouble() {
// Precomputation for double scalar multiplication.
// P,3P,5P,7P,9P,11P,13P,15P
if (this.dblPrecmp != null)
return;
this.dblPrecmp = new GroupElement[8];
Bi = this;
GroupElement[] dblPrecmp = new GroupElement[8];
GroupElement Bi = this;
for (int i = 0; i < 8; i++) {
final FieldElement recip = Bi.Z.invert();
final FieldElement x = Bi.X.multiply(recip);
final FieldElement y = Bi.Y.multiply(recip);
this.dblPrecmp[i] = precomp(this.curve, y.add(x), y.subtract(x), x.multiply(y).multiply(this.curve.get2D()));
dblPrecmp[i] = precomp(this.curve, y.add(x), y.subtract(x), x.multiply(y).multiply(this.curve.get2D()));
// Bi = edwards(B,edwards(B,Bi))
Bi = this.add(this.add(Bi.toCached()).toP3().toCached()).toP3();
}
return dblPrecmp;
}
/**
@ -496,7 +587,7 @@ public class GroupElement implements Serializable {
* r in P x P representation:
* <p>
* r = ((X' : Z'), (Y' : T')) where
* <p><ul>
* </p><ul>
* <li>X' = (X + Y)^2 - (Y^2 + X^2)
* <li>Y' = Y^2 + X^2
* <li>Z' = y^2 - X^2
@ -505,7 +596,7 @@ public class GroupElement implements Serializable {
* r converted from P x P to P^2 representation:
* <p>
* r = (X'' : Y'' : Z'') where
* <p><ul>
* </p><ul>
* <li>X'' = X' * Z' = ((X + Y)^2 - Y^2 - X^2) * (2 * Z^2 - (y^2 - X^2))
* <li>Y'' = Y' * T' = (Y^2 + X^2) * (2 * Z^2 - (y^2 - X^2))
* <li>Z'' = Z' * T' = (y^2 - X^2) * (2 * Z^2 - (y^2 - X^2))
@ -548,14 +639,14 @@ public class GroupElement implements Serializable {
* r in P x P representation:
* <p>
* r = ((X' : Z'), (Y' : T')) where
* <p><ul>
* </p><ul>
* <li>X' = (Y1 + X1) * q.X - (Y1 - X1) * q.Y = ((Y1 + X1) * (Y2 + X2) - (Y1 - X1) * (Y2 - X2)) * 1/Z2
* <li>Y' = (Y1 + X1) * q.X + (Y1 - X1) * q.Y = ((Y1 + X1) * (Y2 + X2) + (Y1 - X1) * (Y2 - X2)) * 1/Z2
* <li>Z' = 2 * Z1 + T1 * q.Z = 2 * Z1 + T1 * 2 * d * X2 * Y2 * 1/Z2^2 = (2 * Z1 * Z2 + 2 * d * T1 * T2) * 1/Z2
* <li>T' = 2 * Z1 - T1 * q.Z = 2 * Z1 - T1 * 2 * d * X2 * Y2 * 1/Z2^2 = (2 * Z1 * Z2 - 2 * d * T1 * T2) * 1/Z2
* </ul><p>
* Setting A = (Y1 - X1) * (Y2 - X2), B = (Y1 + X1) * (Y2 + X2), C = 2 * d * T1 * T2, D = 2 * Z1 * Z2 we get
* <p><ul>
* </p><ul>
* <li>X' = (B - A) * 1/Z2
* <li>Y' = (B + A) * 1/Z2
* <li>Z' = (D + C) * 1/Z2
@ -564,7 +655,7 @@ public class GroupElement implements Serializable {
* r converted from P x P to P^2 representation:
* <p>
* r = (X'' : Y'' : Z'' : T'') where
* <p><ul>
* </p><ul>
* <li>X'' = X' * Z' = (B - A) * (D + C) * 1/Z2^2
* <li>Y'' = Y' * T' = (B + A) * (D - C) * 1/Z2^2
* <li>Z'' = Z' * T' = (D + C) * (D - C) * 1/Z2^2
@ -634,14 +725,14 @@ public class GroupElement implements Serializable {
* r = p + q where p = this = (X1 : Y1 : Z1 : T1), q = (q.X, q.Y, q.Z, q.T) = (Y2 + X2, Y2 - X2, Z2, 2 * d * T2)
* <p>
* r in P x P representation:
* <p><ul>
* </p><ul>
* <li>X' = (Y1 + X1) * (Y2 + X2) - (Y1 - X1) * (Y2 - X2)
* <li>Y' = (Y1 + X1) * (Y2 + X2) + (Y1 - X1) * (Y2 - X2)
* <li>Z' = 2 * Z1 * Z2 + 2 * d * T1 * T2
* <li>T' = 2 * Z1 * T2 - 2 * d * T1 * T2
* </ul><p>
* Setting A = (Y1 - X1) * (Y2 - X2), B = (Y1 + X1) * (Y2 + X2), C = 2 * d * T1 * T2, D = 2 * Z1 * Z2 we get
* <p><ul>
* </p><ul>
* <li>X' = (B - A)
* <li>Y' = (B + A)
* <li>Z' = (D + C)
@ -708,7 +799,7 @@ public class GroupElement implements Serializable {
public GroupElement negate() {
if (this.repr != Representation.P3)
throw new UnsupportedOperationException();
return this.curve.getZero(Representation.P3).sub(toCached()).toP3();
return this.curve.getZero(Representation.P3).sub(toCached()).toP3PrecomputeDouble();
}
@Override
@ -804,19 +895,10 @@ public class GroupElement implements Serializable {
*
* @param u The group element to return if b == 1.
* @param b in {0, 1}
* @return u if b == 1; this if b == 0; null otherwise.
* @return u if b == 1; this if b == 0; Results undefined if b is not in {0, 1}.
*/
GroupElement cmov(final GroupElement u, final int b) {
GroupElement ret = null;
for (int i = 0; i < b; i++) {
// Only for b == 1
ret = u;
}
for (int i = 0; i < 1-b; i++) {
// Only for b == 0
ret = this;
}
return ret;
return precomp(curve, X.cmov(u.X, b), Y.cmov(u.Y, b), Z.cmov(u.Z, b));
}
/**
@ -873,22 +955,16 @@ public class GroupElement implements Serializable {
final byte[] e = toRadix16(a);
GroupElement h = this.curve.getZero(Representation.P3);
synchronized(this) {
// TODO: Get opinion from a crypto professional.
// This should in practice never be necessary, the only point that
// this should get called on is EdDSA's B.
//precompute();
for (i = 1; i < 64; i += 2) {
t = select(i/2, e[i]);
h = h.madd(t).toP3();
}
for (i = 1; i < 64; i += 2) {
t = select(i/2, e[i]);
h = h.madd(t).toP3();
}
h = h.dbl().toP2().dbl().toP2().dbl().toP2().dbl().toP3();
h = h.dbl().toP2().dbl().toP2().dbl().toP2().dbl().toP3();
for (i = 0; i < 64; i += 2) {
t = select(i/2, e[i]);
h = h.madd(t).toP3();
}
for (i = 0; i < 64; i += 2) {
t = select(i/2, e[i]);
h = h.madd(t).toP3();
}
return h;
@ -965,14 +1041,8 @@ public class GroupElement implements Serializable {
if (aslide[i] != 0 || bslide[i] != 0) break;
}
synchronized(this) {
// TODO-CR BR strange comment below.
// TODO: Get opinion from a crypto professional.
// This should in practice never be necessary, the only point that
// this should get called on is EdDSA's B.
//precompute();
for (; i >= 0; --i) {
GroupElement t = r.dbl();
for (; i >= 0; --i) {
GroupElement t = r.dbl();
if (aslide[i] > 0) {
t = t.toP3().madd(A.dblPrecmp[aslide[i]/2]);
@ -986,8 +1056,7 @@ public class GroupElement implements Serializable {
t = t.toP3().msub(this.dblPrecmp[(-bslide[i])/2]);
}
r = t.toP2();
}
r = t.toP2();
}
return r;

View File

@ -93,6 +93,16 @@ public class BigIntegerFieldElement extends FieldElement implements Serializable
return pow(f.getQm5d8());
}
/**
* @since 0.9.36
*/
@Override
public FieldElement cmov(FieldElement val, int b) {
// Not constant-time, but it doesn't really matter because none of the underlying BigInteger operations
// are either, so there's not much point in trying hard here ...
return b == 0 ? this : val;
}
@Override
public int hashCode() {
return bi.hashCode();

View File

@ -51,12 +51,12 @@ public class Ed25519FieldElement extends FieldElement {
* TODO-CR BR: h is allocated via new, probably not a good idea. Do we need the copying into temp variables if we do that?
* <p>
* Preconditions:
* <p><ul>
* </p><ul>
* <li>|f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
* <li>|g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
* </ul><p>
* Postconditions:
* <p><ul>
* </p><ul>
* <li>|h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
* </ul>
*
@ -80,12 +80,12 @@ public class Ed25519FieldElement extends FieldElement {
* TODO-CR BR: See above.
* <p>
* Preconditions:
* <p><ul>
* </p><ul>
* <li>|f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
* <li>|g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
* </ul><p>
* Postconditions:
* <p><ul>
* </p><ul>
* <li>|h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
* </ul>
*
@ -107,11 +107,11 @@ public class Ed25519FieldElement extends FieldElement {
* TODO-CR BR: see above.
* <p>
* Preconditions:
* <p><ul>
* </p><ul>
* <li>|f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
* </ul><p>
* Postconditions:
* <p><ul>
* </p><ul>
* <li>|h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
* </ul>
*
@ -131,14 +131,14 @@ public class Ed25519FieldElement extends FieldElement {
* Can overlap h with f or g.
* <p>
* Preconditions:
* <p><ul>
* </p><ul>
* <li>|f| bounded by
* 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.
* <li>|g| bounded by
* 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.
* </ul><p>
* Postconditions:
* <p><ul>
* </p><ul>
* <li>|h| bounded by
* 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
* </ul><p>
@ -382,11 +382,11 @@ public class Ed25519FieldElement extends FieldElement {
* Can overlap h with f.
* <p>
* Preconditions:
* <p><ul>
* </p><ul>
* <li>|f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.
* </ul><p>
* Postconditions:
* <p><ul>
* </p><ul>
* <li>|h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
* </ul><p>
* See {@link #multiply(FieldElement)} for discussion
@ -538,11 +538,11 @@ public class Ed25519FieldElement extends FieldElement {
* Can overlap h with f.
* <p>
* Preconditions:
* <p><ul>
* </p><ul>
* <li>|f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.
* </ul><p>
* Postconditions:
* <p><ul>
* </p><ul>
* <li>|h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
* </ul><p>
* See {@link #multiply(FieldElement)} for discussion
@ -934,6 +934,30 @@ public class Ed25519FieldElement extends FieldElement {
return multiply(t0);
}
/**
* Constant-time conditional move. Well, actually it is a conditional copy.
* Logic is inspired by the SUPERCOP implementation at:
* https://github.com/floodyberry/supercop/blob/master/crypto_sign/ed25519/ref10/fe_cmov.c
*
* @param val the other field element.
* @param b must be 0 or 1, otherwise results are undefined.
* @return a copy of this if b == 0, or a copy of val if b == 1.
* @since 0.9.36
*/
@Override
public FieldElement cmov(FieldElement val, int b) {
Ed25519FieldElement that = (Ed25519FieldElement) val;
b = -b;
int[] result = new int[10];
for (int i = 0; i < 10; i++) {
result[i] = this.t[i];
int x = this.t[i] ^ that.t[i];
x &= b;
result[i] ^= x;
}
return new Ed25519FieldElement(this.f, result);
}
@Override
public int hashCode() {
return Arrays.hashCode(t);

View File

@ -1,6 +1,7 @@
package net.i2p.crypto.eddsa.spec;
import java.util.Hashtable;
import java.util.HashMap;
import java.util.Locale;
import net.i2p.crypto.eddsa.Utils;
import net.i2p.crypto.eddsa.math.Curve;
@ -16,6 +17,9 @@ import net.i2p.crypto.eddsa.math.ed25519.Ed25519ScalarOps;
*
*/
public class EdDSANamedCurveTable {
/** RFC 8032 */
public static final String ED_25519 = "Ed25519";
/** old name */
public static final String CURVE_ED25519_SHA512 = "ed25519-sha-512";
private static final Field ed25519field = new Field(
@ -27,8 +31,8 @@ public class EdDSANamedCurveTable {
Utils.hexToBytes("a3785913ca4deb75abd841414d0a700098e879777940c78c73fe6f2bee6c0352"), // d
ed25519field.fromByteArray(Utils.hexToBytes("b0a00e4a271beec478e42fad0618432fa7d7fb3d99004d2b0bdfc14f8024832b"))); // I
private static final EdDSANamedCurveSpec ed25519sha512 = new EdDSANamedCurveSpec(
CURVE_ED25519_SHA512,
public static final EdDSANamedCurveSpec ED_25519_CURVE_SPEC = new EdDSANamedCurveSpec(
ED_25519,
ed25519curve,
"SHA-512", // H
new Ed25519ScalarOps(), // l
@ -36,17 +40,34 @@ public class EdDSANamedCurveTable {
Utils.hexToBytes("5866666666666666666666666666666666666666666666666666666666666666"),
true)); // Precompute tables for B
private static final Hashtable<String, EdDSANamedCurveSpec> curves = new Hashtable<String, EdDSANamedCurveSpec>();
private static volatile HashMap<String, EdDSANamedCurveSpec> curves = new HashMap<String, EdDSANamedCurveSpec>();
public static void defineCurve(String name, EdDSANamedCurveSpec curve) {
curves.put(name, curve);
private static synchronized void putCurve(String name, EdDSANamedCurveSpec curve) {
HashMap<String, EdDSANamedCurveSpec> newCurves = new HashMap<String, EdDSANamedCurveSpec>(curves);
newCurves.put(name, curve);
curves = newCurves;
}
public static void defineCurve(EdDSANamedCurveSpec curve) {
putCurve(curve.getName().toLowerCase(Locale.ENGLISH), curve);
}
static void defineCurveAlias(String name, String alias) {
EdDSANamedCurveSpec curve = curves.get(name.toLowerCase(Locale.ENGLISH));
if (curve == null) {
throw new IllegalStateException();
}
putCurve(alias.toLowerCase(Locale.ENGLISH), curve);
}
static {
defineCurve(CURVE_ED25519_SHA512, ed25519sha512);
// RFC 8032
defineCurve(ED_25519_CURVE_SPEC);
// old name
defineCurveAlias(ED_25519, CURVE_ED25519_SHA512);
}
public static EdDSANamedCurveSpec getByName(String name) {
return curves.get(name);
return curves.get(name.toLowerCase(Locale.ENGLISH));
}
}

View File

@ -25,6 +25,10 @@ public class EdDSAParameterSpec implements AlgorithmParameterSpec, Serializable
private final GroupElement B;
/**
* @param curve the curve
* @param hashAlgo the JCA string for the hash algorithm
* @param sc the parameter L represented as ScalarOps
* @param B the parameter B
* @throws IllegalArgumentException if hash algorithm is unsupported or length is wrong
*/
public EdDSAParameterSpec(Curve curve, String hashAlgo,

View File

@ -21,6 +21,8 @@ public class EdDSAPrivateKeySpec implements KeySpec {
private final EdDSAParameterSpec spec;
/**
* @param seed the private key
* @param spec the parameter specification for this key
* @throws IllegalArgumentException if seed length is wrong or hash algorithm is unsupported
*/
public EdDSAPrivateKeySpec(byte[] seed, EdDSAParameterSpec spec) {
@ -58,6 +60,7 @@ public class EdDSAPrivateKeySpec implements KeySpec {
* Initialize directly from the hash.
* getSeed() will return null if this constructor is used.
*
* @param spec the parameter specification for this key
* @param h the private key
* @throws IllegalArgumentException if hash length is wrong
* @since 0.9.27 (GitHub issue #17)

View File

@ -12,10 +12,12 @@ import net.i2p.crypto.eddsa.math.GroupElement;
*/
public class EdDSAPublicKeySpec implements KeySpec {
private final GroupElement A;
private final GroupElement Aneg;
private GroupElement Aneg;
private final EdDSAParameterSpec spec;
/**
* @param pk the public key
* @param spec the parameter specification for this key
* @throws IllegalArgumentException if key length is wrong
*/
public EdDSAPublicKeySpec(byte[] pk, EdDSAParameterSpec spec) {
@ -23,16 +25,11 @@ public class EdDSAPublicKeySpec implements KeySpec {
throw new IllegalArgumentException("public-key length is wrong");
this.A = new GroupElement(spec.getCurve(), pk);
// Precompute -A for use in verification.
this.Aneg = A.negate();
Aneg.precompute(false);
this.spec = spec;
}
public EdDSAPublicKeySpec(GroupElement A, EdDSAParameterSpec spec) {
this.A = A;
this.Aneg = A.negate();
Aneg.precompute(false);
this.spec = spec;
}
@ -41,7 +38,13 @@ public class EdDSAPublicKeySpec implements KeySpec {
}
public GroupElement getNegativeA() {
return Aneg;
// Only read Aneg once, otherwise read re-ordering might occur between here and return. Requires all GroupElement's fields to be final.
GroupElement ourAneg = Aneg;
if(ourAneg == null) {
ourAneg = A.negate();
Aneg = ourAneg;
}
return ourAneg;
}
public EdDSAParameterSpec getParams() {

View File

@ -1,6 +1,9 @@
<html><body>
<p>
Specifications for curves and keys, and a table for named curves,
initially containing only the 25519 curve "ed25519-sha-512".
Specifications for curves and keys, and a table for named curves.
Contains the following curves:
</p>
<ul>
<li>"Ed25519"</li>
</ul>
</body></html>

View File

@ -760,7 +760,7 @@ public class GroupElementTest {
GroupElement A = new GroupElement(curve, Utils.hexToBytes("d4cf8595571830644bd14af416954d09ab7159751ad9e0f7a6cbd92379e71a66"));
GroupElement B = ed25519.getB();
GroupElement geZero = curve.getZero(GroupElement.Representation.P3);
geZero.precompute(false);
//geZero.precompute(false);
// 0 * GE(0) + 0 * GE(0) = GE(0)
assertThat(geZero.doubleScalarMultiplyVariableTime(geZero, zero, zero),
@ -802,7 +802,7 @@ public class GroupElementTest {
// Arrange:
final GroupElement basePoint = ed25519.getB();
final GroupElement g = MathUtils.getRandomGroupElement();
g.precompute(false);
//g.precompute(false);
final FieldElement f1 = MathUtils.getRandomFieldElement();
final FieldElement f2 = MathUtils.getRandomFieldElement();

View File

@ -1,3 +1,6 @@
2018-07-01 zzz
* Crypto: Backport EdDSA versions 0.2/0.3 from github
2018-06-30 zzz
* Console: Fix reading flags when symlinked (ticket #2270)
* Router: Reselect jbigi lib when processor changes (ticket #2277)

View File

@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 2;
public final static long BUILD = 3;
/** for example "-test" */
public final static String EXTRA = "";