remove eddsa submodule, remove bouncycastle, add xbib net security

This commit is contained in:
Jörg Prante 2022-11-16 23:12:46 +01:00
parent 1af5f2c928
commit 0618123c37
85 changed files with 158 additions and 11647 deletions

View file

@ -4,7 +4,7 @@ plugins {
} }
wrapper { wrapper {
gradleVersion = "${rootProject.property('gradle.wrapper.version')}" gradleVersion = libs.versions.gradle.get()
distributionType = Wrapper.DistributionType.ALL distributionType = Wrapper.DistributionType.ALL
} }

View file

@ -1,5 +0,0 @@
This work is derived from
https://github.com/str4d/ed25519-java/
released under Creative Commons Legal Code, CC0 1.0 Universal

View file

@ -1,4 +0,0 @@
dependencies {
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:${project.property('junit.version')}"
testImplementation "junit:junit:${project.property('junit4.version')}"
}

View file

@ -1,6 +0,0 @@
module org.xbib.eddsa {
exports org.xbib.io.sshd.eddsa;
exports org.xbib.io.sshd.eddsa.spec;
provides java.security.Provider
with org.xbib.io.sshd.eddsa.EdDSASecurityProvider;
}

View file

@ -1,455 +0,0 @@
package org.xbib.io.sshd.eddsa;
import org.xbib.io.sshd.eddsa.math.Curve;
import org.xbib.io.sshd.eddsa.math.GroupElement;
import org.xbib.io.sshd.eddsa.math.ScalarOps;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
/**
* Signing and verification for EdDSA.
* <p>
* The EdDSA sign and verify algorithms do not interact well with
* the Java Signature API, as one or more update() methods must be
* called before sign() or verify(). Using the standard API,
* this implementation must copy and buffer all data passed in
* via update().
* </p><p>
* This implementation offers two ways to avoid this copying,
* but only if all data to be signed or verified is available
* in a single byte array.
* </p><p>
* Option 1:
* </p><ol>
* <li>Call initSign() or initVerify() as usual.
* </li><li>Call setParameter(ONE_SHOT_MODE)
* </li><li>Call update(byte[]) or update(byte[], int, int) exactly once
* </li><li>Call sign() or verify() as usual.
* </li><li>If doing additional one-shot signs or verifies with this object, you must
* call setParameter(ONE_SHOT_MODE) each time
* </li></ol>
* <p>
* Option 2:
* </p><ol>
* <li>Call initSign() or initVerify() as usual.
* </li><li>Call one of the signOneShot() or verifyOneShot() methods.
* </li><li>If doing additional one-shot signs or verifies with this object,
* just call signOneShot() or verifyOneShot() again.
* </li></ol>
*/
public final class EdDSAEngine extends Signature {
public static final String SIGNATURE_ALGORITHM = "NONEwithEdDSA";
/**
* To efficiently sign or verify data in one shot, pass this to setParameters()
* after initSign() or initVerify() but BEFORE THE FIRST AND ONLY
* update(data) or update(data, off, len). The data reference will be saved
* and then used in sign() or verify() without copying the data.
* Violate these rules and you will get a SignatureException.
*/
public static final AlgorithmParameterSpec ONE_SHOT_MODE = new OneShotSpec();
private MessageDigest digest;
private ByteArrayOutputStream baos;
private EdDSAKey key;
private boolean oneShotMode;
private byte[] oneShotBytes;
private int oneShotOffset;
private int oneShotLength;
/**
* No specific EdDSA-internal hash requested, allows any EdDSA key.
*/
public EdDSAEngine() {
super(SIGNATURE_ALGORITHM);
}
/**
* 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) {
this();
this.digest = digest;
}
private void reset() {
if (digest != null) {
digest.reset();
}
if (baos != null) {
baos.reset();
}
oneShotMode = false;
oneShotBytes = null;
}
@Override
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
reset();
if (privateKey instanceof EdDSAPrivateKey) {
EdDSAPrivateKey privKey = (EdDSAPrivateKey) privateKey;
key = privKey;
if (digest == null) {
try {
digest = MessageDigest.getInstance(key.getParams().getHashAlgorithm());
} catch (NoSuchAlgorithmException e) {
throw new InvalidKeyException("cannot get required digest " + key.getParams().getHashAlgorithm() + " for private key.");
}
} else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm()))
throw new InvalidKeyException("Key hash algorithm does not match chosen digest");
digestInitSign(privKey);
} else {
throw new InvalidKeyException("cannot identify EdDSA private key: " + privateKey.getClass());
}
}
private void digestInitSign(EdDSAPrivateKey privKey) {
// Preparing for hash
// r = H(h_b,...,h_2b-1,M)
int b = privKey.getParams().getCurve().getField().getb();
digest.update(privKey.getH(), b / 8, b / 4 - b / 8);
}
@Override
protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
reset();
if (publicKey instanceof EdDSAPublicKey) {
key = (EdDSAPublicKey) publicKey;
if (digest == null) {
try {
digest = MessageDigest.getInstance(key.getParams().getHashAlgorithm());
} catch (NoSuchAlgorithmException e) {
throw new InvalidKeyException("cannot get required digest " + key.getParams().getHashAlgorithm() + " for private key.");
}
} else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm()))
throw new InvalidKeyException("Key hash algorithm does not match chosen digest");
} else {
throw new InvalidKeyException("cannot identify EdDSA public key: " + publicKey.getClass());
}
}
/**
* @throws SignatureException if in one-shot mode
*/
@Override
protected void engineUpdate(byte b) throws SignatureException {
if (oneShotMode)
throw new SignatureException("unsupported in one-shot mode");
if (baos == null)
baos = new ByteArrayOutputStream(256);
baos.write(b);
}
/**
* @throws SignatureException if one-shot rules are violated
*/
@Override
protected void engineUpdate(byte[] b, int off, int len)
throws SignatureException {
if (oneShotMode) {
if (oneShotBytes != null)
throw new SignatureException("update() already called");
oneShotBytes = b;
oneShotOffset = off;
oneShotLength = len;
} else {
if (baos == null)
baos = new ByteArrayOutputStream(256);
baos.write(b, off, len);
}
}
@Override
protected byte[] engineSign() throws SignatureException {
try {
return x_engineSign();
} finally {
reset();
// must leave the object ready to sign again with
// the same key, as required by the API
EdDSAPrivateKey privKey = (EdDSAPrivateKey) key;
digestInitSign(privKey);
}
}
private byte[] x_engineSign() throws SignatureException {
Curve curve = key.getParams().getCurve();
ScalarOps sc = key.getParams().getScalarOps();
byte[] a = ((EdDSAPrivateKey) key).geta();
byte[] message;
int offset, length;
if (oneShotMode) {
if (oneShotBytes == null)
throw new SignatureException("update() not called first");
message = oneShotBytes;
offset = oneShotOffset;
length = oneShotLength;
} else {
if (baos == null)
message = new byte[0];
else
message = baos.toByteArray();
offset = 0;
length = message.length;
}
// r = H(h_b,...,h_2b-1,M)
digest.update(message, offset, length);
byte[] r = digest.digest();
// r mod l
// Reduces r from 64 bytes to 32 bytes
r = sc.reduce(r);
// R = rB
GroupElement R = key.getParams().getB().scalarMultiply(r);
byte[] Rbyte = R.toByteArray();
// S = (r + H(Rbar,Abar,M)*a) mod l
digest.update(Rbyte);
digest.update(((EdDSAPrivateKey) key).getAbyte());
digest.update(message, offset, length);
byte[] h = digest.digest();
h = sc.reduce(h);
byte[] S = sc.multiplyAndAdd(h, a, r);
// R+S
int b = curve.getField().getb();
ByteBuffer out = ByteBuffer.allocate(b / 4);
out.put(Rbyte).put(S);
return out.array();
}
@Override
protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
try {
return x_engineVerify(sigBytes);
} finally {
reset();
}
}
private boolean x_engineVerify(byte[] sigBytes) throws SignatureException {
Curve curve = key.getParams().getCurve();
int b = curve.getField().getb();
if (sigBytes.length != b / 4)
throw new SignatureException("signature length is wrong");
// R is first b/8 bytes of sigBytes, S is second b/8 bytes
digest.update(sigBytes, 0, b / 8);
digest.update(((EdDSAPublicKey) key).getAbyte());
// h = H(Rbar,Abar,M)
byte[] message;
int offset, length;
if (oneShotMode) {
if (oneShotBytes == null)
throw new SignatureException("update() not called first");
message = oneShotBytes;
offset = oneShotOffset;
length = oneShotLength;
} else {
if (baos == null)
message = new byte[0];
else
message = baos.toByteArray();
offset = 0;
length = message.length;
}
digest.update(message, offset, length);
byte[] h = digest.digest();
// h mod l
h = key.getParams().getScalarOps().reduce(h);
byte[] Sbyte = Arrays.copyOfRange(sigBytes, b / 8, b / 4);
// R = SB - H(Rbar,Abar,M)A
GroupElement R = key.getParams().getB().doubleScalarMultiplyVariableTime(
((EdDSAPublicKey) key).getNegativeA(), h, Sbyte);
// Variable time. This should be okay, because there are no secret
// values used anywhere in verification.
byte[] Rcalc = R.toByteArray();
for (int i = 0; i < Rcalc.length; i++) {
if (Rcalc[i] != sigBytes[i])
return false;
}
return true;
}
/**
* To efficiently sign all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
* <p>
* Same as:
* <pre>
* setParameter(ONE_SHOT_MODE)
* update(data)
* sig = sign()
* </pre>
*
* @param data the message to be signed
* @return the signature
* @throws SignatureException if update() already called
* @see #ONE_SHOT_MODE
*/
public byte[] signOneShot(byte[] data) throws SignatureException {
return signOneShot(data, 0, data.length);
}
/**
* To efficiently sign all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
* <p>
* Same as:
* <pre>
* setParameter(ONE_SHOT_MODE)
* update(data, off, len)
* 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
*/
public byte[] signOneShot(byte[] data, int off, int len) throws SignatureException {
oneShotMode = true;
update(data, off, len);
return sign();
}
/**
* To efficiently verify all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
* <p>
* Same as:
* <pre>
* setParameter(ONE_SHOT_MODE)
* update(data)
* 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
*/
public boolean verifyOneShot(byte[] data, byte[] signature) throws SignatureException {
return verifyOneShot(data, 0, data.length, signature, 0, signature.length);
}
/**
* To efficiently verify all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
* <p>
* Same as:
* <pre>
* setParameter(ONE_SHOT_MODE)
* update(data, off, len)
* 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
*/
public boolean verifyOneShot(byte[] data, int off, int len, byte[] signature) throws SignatureException {
return verifyOneShot(data, off, len, signature, 0, signature.length);
}
/**
* To efficiently verify all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
* <p>
* Same as:
* <pre>
* setParameter(ONE_SHOT_MODE)
* update(data)
* 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
*/
public boolean verifyOneShot(byte[] data, byte[] signature, int sigoff, int siglen) throws SignatureException {
return verifyOneShot(data, 0, data.length, signature, sigoff, siglen);
}
/**
* To efficiently verify all the data in one shot, if it is available,
* use this method, which will avoid copying the data.
* <p>
* Same as:
* <pre>
* setParameter(ONE_SHOT_MODE)
* update(data, off, len)
* 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
*/
public boolean verifyOneShot(byte[] data, int off, int len, byte[] signature, int sigoff, int siglen) throws SignatureException {
oneShotMode = true;
update(data, off, len);
return verify(signature, sigoff, siglen);
}
/**
* @throws InvalidAlgorithmParameterException if spec is ONE_SHOT_MODE and update() already called
* @see #ONE_SHOT_MODE
*/
@Override
protected void engineSetParameter(AlgorithmParameterSpec spec) throws InvalidAlgorithmParameterException {
if (spec.equals(ONE_SHOT_MODE)) {
if (oneShotBytes != null || (baos != null && baos.size() > 0))
throw new InvalidAlgorithmParameterException("update() already called");
oneShotMode = true;
} else {
super.engineSetParameter(spec);
}
}
@Override
protected void engineSetParameter(String param, Object value) {
throw new UnsupportedOperationException("engineSetParameter unsupported");
}
@Override
protected Object engineGetParameter(String param) {
throw new UnsupportedOperationException("engineSetParameter unsupported");
}
private static class OneShotSpec implements AlgorithmParameterSpec {
}
}

View file

@ -1,19 +0,0 @@
package org.xbib.io.sshd.eddsa;
import org.xbib.io.sshd.eddsa.spec.EdDSAParameterSpec;
/**
* Common interface for all EdDSA keys.
*/
public interface EdDSAKey {
/**
* The reported key algorithm for all EdDSA keys
*/
String KEY_ALGORITHM = "EdDSA";
/**
* @return a parameter specification representing the EdDSA domain
* parameters for the key.
*/
EdDSAParameterSpec getParams();
}

View file

@ -1,321 +0,0 @@
package org.xbib.io.sshd.eddsa;
import org.xbib.io.sshd.eddsa.math.GroupElement;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveTable;
import org.xbib.io.sshd.eddsa.spec.EdDSAParameterSpec;
import org.xbib.io.sshd.eddsa.spec.EdDSAPrivateKeySpec;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
/**
* An EdDSA private key.
* <p>
* Warning: Private key encoding is based on the current curdle WG draft,
* and is subject to change. See getEncoded().
* </p><p>
* 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>
*/
public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
// compatible with JDK 1.1
private static final long serialVersionUID = 23495873459878957L;
// 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;
private final byte[] seed;
private final byte[] h;
private final byte[] a;
private final GroupElement A;
private final byte[] Abyte;
private final EdDSAParameterSpec edDsaSpec;
public EdDSAPrivateKey(EdDSAPrivateKeySpec spec) {
this.seed = spec.getSeed();
this.h = spec.getH();
this.a = spec.geta();
this.A = spec.getA();
this.Abyte = this.A.toByteArray();
this.edDsaSpec = spec.getParams();
}
public EdDSAPrivateKey(PKCS8EncodedKeySpec spec) throws InvalidKeySpecException {
this(new EdDSAPrivateKeySpec(decode(spec.getEncoded()),
EdDSANamedCurveTable.getByName("Ed25519")));
}
/**
* 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
*/
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++] != (totlen - 2) ||
d[idx++] != 0x02 ||
d[idx++] != 1 ||
d[idx++] != 0 ||
d[idx++] != 0x30 ||
d[idx++] != idlen ||
d[idx++] != 0x06 ||
d[idx++] != 3 ||
d[idx++] != (1 * 40) + 3 ||
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");
}
byte[] rv = new byte[32];
System.arraycopy(d, idx, rv, 0, 32);
return rv;
} catch (IndexOutOfBoundsException ioobe) {
throw new InvalidKeySpecException(ioobe);
}
}
@Override
public String getAlgorithm() {
return KEY_ALGORITHM;
}
@Override
public String getFormat() {
return "PKCS#8";
}
/**
* Returns the public key in its canonical encoding.
* 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>
* OneAsymmetricKey ::= SEQUENCE {
* version Version,
* privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
* privateKey PrivateKey,
* 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>
* ... 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>
* <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 48 bytes for Ed25519, null for other curves
*/
@Override
public byte[] getEncoded() {
if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName("Ed25519")))
return null;
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) (totlen - 2);
// version
rv[idx++] = 0x02;
rv[idx++] = 1;
// v1 - no public key included
rv[idx++] = 0;
// Algorithm Identifier
// sequence
rv[idx++] = 0x30;
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++] = (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;
}
@Override
public EdDSAParameterSpec getParams() {
return edDsaSpec;
}
/**
* @return will be null if constructed from a spec which was
* directly constructed from H
*/
public byte[] getSeed() {
return seed;
}
/**
* @return the hash of the seed
*/
public byte[] getH() {
return h;
}
/**
* @return the private key
*/
public byte[] geta() {
return a;
}
/**
* @return the public key
*/
public GroupElement getA() {
return A;
}
/**
* @return the public key
*/
public byte[] getAbyte() {
return Abyte;
}
@Override
public int hashCode() {
return Arrays.hashCode(seed);
}
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof EdDSAPrivateKey))
return false;
EdDSAPrivateKey pk = (EdDSAPrivateKey) o;
return Arrays.equals(seed, pk.getSeed()) &&
edDsaSpec.equals(pk.getParams());
}
}

View file

@ -1,257 +0,0 @@
package org.xbib.io.sshd.eddsa;
import org.xbib.io.sshd.eddsa.math.GroupElement;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveTable;
import org.xbib.io.sshd.eddsa.spec.EdDSAParameterSpec;
import org.xbib.io.sshd.eddsa.spec.EdDSAPublicKeySpec;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
/**
* An EdDSA public key.
* <p>
* Warning: Public key encoding is is based on the current curdle WG draft,
* and is subject to change. See getEncoded().
* </p><p>
* 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>
*/
public class EdDSAPublicKey implements EdDSAKey, PublicKey {
private static final long serialVersionUID = 9837459837498475L;
// 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;
private final GroupElement A;
private final GroupElement Aneg;
private final byte[] Abyte;
private final EdDSAParameterSpec edDsaSpec;
public EdDSAPublicKey(EdDSAPublicKeySpec spec) {
this.A = spec.getA();
this.Aneg = spec.getNegativeA();
this.Abyte = this.A.toByteArray();
this.edDsaSpec = spec.getParams();
}
public EdDSAPublicKey(X509EncodedKeySpec spec) throws InvalidKeySpecException {
this(new EdDSAPublicKeySpec(decode(spec.getEncoded()),
EdDSANamedCurveTable.getByName("Ed25519")));
}
/**
* 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
*/
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++] != (totlen - 2) ||
d[idx++] != 0x30 ||
d[idx++] != idlen ||
d[idx++] != 0x06 ||
d[idx++] != 3 ||
d[idx++] != (1 * 40) + 3 ||
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");
}
byte[] rv = new byte[32];
System.arraycopy(d, idx, rv, 0, 32);
return rv;
} catch (IndexOutOfBoundsException ioobe) {
throw new InvalidKeySpecException(ioobe);
}
}
@Override
public String getAlgorithm() {
return KEY_ALGORITHM;
}
@Override
public String getFormat() {
return "X.509";
}
/**
* Returns the public key in its canonical encoding.
* 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>
* In the X.509 certificate, the subjectPublicKeyInfo field has the
* SubjectPublicKeyInfo type, which has the following ASN.1 syntax:
*
* SubjectPublicKeyInfo ::= SEQUENCE {
* algorithm AlgorithmIdentifier,
* subjectPublicKey BIT STRING
* }
* </pre>
* <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
*/
@Override
public byte[] getEncoded() {
if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName("Ed25519")))
return null;
int totlen = 12 + Abyte.length;
byte[] rv = new byte[totlen];
int idx = 0;
// sequence
rv[idx++] = 0x30;
rv[idx++] = (byte) (totlen - 2);
// Algorithm Identifier
// sequence
rv[idx++] = 0x30;
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++] = (byte) OID_ED25519;
// params - absent
// the key
rv[idx++] = 0x03; // bit string
rv[idx++] = (byte) (1 + Abyte.length);
rv[idx++] = 0; // number of trailing unused bits
System.arraycopy(Abyte, 0, rv, idx, Abyte.length);
return rv;
}
@Override
public EdDSAParameterSpec getParams() {
return edDsaSpec;
}
public GroupElement getA() {
return A;
}
public GroupElement getNegativeA() {
return Aneg;
}
public byte[] getAbyte() {
return Abyte;
}
@Override
public int hashCode() {
return Arrays.hashCode(Abyte);
}
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof EdDSAPublicKey))
return false;
EdDSAPublicKey pk = (EdDSAPublicKey) o;
return Arrays.equals(Abyte, pk.getAbyte()) &&
edDsaSpec.equals(pk.getParams());
}
}

View file

@ -1,45 +0,0 @@
package org.xbib.io.sshd.eddsa;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.Provider;
import java.security.Security;
/**
* A security {@link Provider} that can be registered via {@link Security#addProvider(Provider)}.
*/
public class EdDSASecurityProvider extends Provider {
public static final String PROVIDER_NAME = "EdDSA";
private static final long serialVersionUID = 1210027906682292307L;
public EdDSASecurityProvider() {
super(PROVIDER_NAME, 0.1, "xbib " + PROVIDER_NAME + " security provider wrapper");
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
setup();
return null;
});
}
protected void setup() {
// See https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/HowToImplAProvider.html
put("KeyFactory." + EdDSAKey.KEY_ALGORITHM, "org.xbib.io.sshd.eddsa.KeyFactory");
put("KeyPairGenerator." + EdDSAKey.KEY_ALGORITHM, "org.xbib.io.sshd.eddsa.KeyPairGenerator");
put("Signature." + EdDSAEngine.SIGNATURE_ALGORITHM, "org.xbib.io.sshd.eddsa.EdDSAEngine");
// OID Mappings
// See section "Mapping from OID to name".
// The Key* -> OID mappings correspond to the default algorithm in KeyPairGenerator.
//
// From draft-ieft-curdle-pkix-04:
// id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 }
put("Alg.Alias.KeyFactory.1.3.101.112", EdDSAKey.KEY_ALGORITHM);
put("Alg.Alias.KeyFactory.OID.1.3.101.112", EdDSAKey.KEY_ALGORITHM);
put("Alg.Alias.KeyPairGenerator.1.3.101.112", EdDSAKey.KEY_ALGORITHM);
put("Alg.Alias.KeyPairGenerator.OID.1.3.101.112", EdDSAKey.KEY_ALGORITHM);
put("Alg.Alias.Signature.1.3.101.112", EdDSAEngine.SIGNATURE_ALGORITHM);
put("Alg.Alias.Signature.OID.1.3.101.112", EdDSAEngine.SIGNATURE_ALGORITHM);
}
}

View file

@ -1,62 +0,0 @@
package org.xbib.io.sshd.eddsa;
import org.xbib.io.sshd.eddsa.spec.EdDSAPrivateKeySpec;
import org.xbib.io.sshd.eddsa.spec.EdDSAPublicKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactorySpi;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
*/
public final class KeyFactory extends KeyFactorySpi {
protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
throws InvalidKeySpecException {
if (keySpec instanceof EdDSAPrivateKeySpec) {
return new EdDSAPrivateKey((EdDSAPrivateKeySpec) keySpec);
}
if (keySpec instanceof PKCS8EncodedKeySpec) {
return new EdDSAPrivateKey((PKCS8EncodedKeySpec) keySpec);
}
throw new InvalidKeySpecException("key spec not recognised: " + keySpec.getClass());
}
protected PublicKey engineGeneratePublic(KeySpec keySpec)
throws InvalidKeySpecException {
if (keySpec instanceof EdDSAPublicKeySpec) {
return new EdDSAPublicKey((EdDSAPublicKeySpec) keySpec);
}
if (keySpec instanceof X509EncodedKeySpec) {
return new EdDSAPublicKey((X509EncodedKeySpec) keySpec);
}
throw new InvalidKeySpecException("key spec not recognised: " + keySpec.getClass());
}
@SuppressWarnings("unchecked")
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
throws InvalidKeySpecException {
if (keySpec.isAssignableFrom(EdDSAPublicKeySpec.class) && key instanceof EdDSAPublicKey) {
EdDSAPublicKey k = (EdDSAPublicKey) key;
if (k.getParams() != null) {
return (T) new EdDSAPublicKeySpec(k.getA(), k.getParams());
}
} else if (keySpec.isAssignableFrom(EdDSAPrivateKeySpec.class) && key instanceof EdDSAPrivateKey) {
EdDSAPrivateKey k = (EdDSAPrivateKey) key;
if (k.getParams() != null) {
return (T) new EdDSAPrivateKeySpec(k.getSeed(), k.getH(), k.geta(), k.getA(), k.getParams());
}
}
throw new InvalidKeySpecException("not implemented yet " + key + " " + keySpec);
}
protected Key engineTranslateKey(Key key) throws InvalidKeyException {
throw new InvalidKeyException("No other EdDSA key providers known");
}
}

View file

@ -1,86 +0,0 @@
package org.xbib.io.sshd.eddsa;
import org.xbib.io.sshd.eddsa.spec.EdDSAGenParameterSpec;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveSpec;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveTable;
import org.xbib.io.sshd.eddsa.spec.EdDSAParameterSpec;
import org.xbib.io.sshd.eddsa.spec.EdDSAPrivateKeySpec;
import org.xbib.io.sshd.eddsa.spec.EdDSAPublicKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidParameterException;
import java.security.KeyPair;
import java.security.KeyPairGeneratorSpi;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Hashtable;
/**
* Default keysize is 256 (Ed25519).
*/
public final class KeyPairGenerator extends KeyPairGeneratorSpi {
private static final int DEFAULT_KEYSIZE = 256;
private static final Hashtable<Integer, AlgorithmParameterSpec> edParameters;
static {
edParameters = new Hashtable<>();
edParameters.put(256, new EdDSAGenParameterSpec("Ed25519"));
}
private EdDSAParameterSpec edParams;
private SecureRandom random;
private boolean initialized;
public void initialize(int keysize, SecureRandom random) {
AlgorithmParameterSpec edParams = edParameters.get(keysize);
if (edParams == null)
throw new InvalidParameterException("unknown key type.");
try {
initialize(edParams, random);
} catch (InvalidAlgorithmParameterException e) {
throw new InvalidParameterException("key type not configurable.");
}
}
@Override
public void initialize(AlgorithmParameterSpec params, SecureRandom random) throws InvalidAlgorithmParameterException {
if (params instanceof EdDSAParameterSpec) {
edParams = (EdDSAParameterSpec) params;
} else if (params instanceof EdDSAGenParameterSpec) {
edParams = createNamedCurveSpec(((EdDSAGenParameterSpec) params).getName());
} else
throw new InvalidAlgorithmParameterException("parameter object not a EdDSAParameterSpec");
this.random = random;
initialized = true;
}
public KeyPair generateKeyPair() {
if (!initialized)
initialize(DEFAULT_KEYSIZE, new SecureRandom());
byte[] seed = new byte[edParams.getCurve().getField().getb() / 8];
random.nextBytes(seed);
EdDSAPrivateKeySpec privKey = new EdDSAPrivateKeySpec(seed, edParams);
EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(privKey.getA(), edParams);
return new KeyPair(new EdDSAPublicKey(pubKey), new EdDSAPrivateKey(privKey));
}
/**
* Create an EdDSANamedCurveSpec from the provided curve name. The current
* implementation fetches the pre-created curve spec from a table.
*
* @param curveName the EdDSA named curve.
* @return the specification for the named curve.
* @throws InvalidAlgorithmParameterException if the named curve is unknown.
*/
protected EdDSANamedCurveSpec createNamedCurveSpec(String curveName) throws InvalidAlgorithmParameterException {
EdDSANamedCurveSpec spec = EdDSANamedCurveTable.getByName(curveName);
if (spec == null) {
throw new InvalidAlgorithmParameterException("unknown curve name: " + curveName);
}
return spec;
}
}

View file

@ -1,95 +0,0 @@
package org.xbib.io.sshd.eddsa;
/**
* Basic utilities for EdDSA.
* Not for external use, not maintained as a public API.
*/
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) {
int result = 0;
int xor = b ^ c;
for (int i = 0; i < 8; i++) {
result |= xor >> i;
}
return (result ^ 0x01) & 0x01;
}
/**
* 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) {
int result = 0;
for (int i = 0; i < 32; i++) {
result |= b[i] ^ c[i];
}
return equal(result, 0);
}
/**
* Constant-time determine if byte is negative.
*
* @param b the byte to check.
* @return 1 if the byte is negative, 0 otherwise.
*/
public static int negative(int b) {
return (b >> 8) & 1;
}
/**
* Get the i'th bit of a byte array.
*
* @param h the byte array.
* @param i the bit index.
* @return 0 or 1, the value of the i'th bit in h
*/
public static int bit(byte[] h, int i) {
return (h[i >> 3] >> (i & 7)) & 1;
}
/**
* Converts a hex string to bytes.
*
* @param s the hex string to be converted.
* @return the byte[]
*/
public static byte[] hexToBytes(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
/**
* Converts bytes to a hex string.
*
* @param raw the byte[] to be converted.
* @return the hex representation as a string.
*/
public static String bytesToHex(byte[] raw) {
if (raw == null) {
return null;
}
final StringBuilder hex = new StringBuilder(2 * raw.length);
for (final byte b : raw) {
hex.append(Character.forDigit((b & 0xF0) >> 4, 16))
.append(Character.forDigit((b & 0x0F), 16));
}
return hex.toString();
}
}

View file

@ -1,15 +0,0 @@
package org.xbib.io.sshd.eddsa.math;
import org.xbib.io.sshd.eddsa.Utils;
/**
*
*/
final class Constants {
public static final byte[] ZERO = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000");
public static final byte[] ONE = Utils.hexToBytes("0100000000000000000000000000000000000000000000000000000000000000");
public static final byte[] TWO = Utils.hexToBytes("0200000000000000000000000000000000000000000000000000000000000000");
public static final byte[] FOUR = Utils.hexToBytes("0400000000000000000000000000000000000000000000000000000000000000");
public static final byte[] FIVE = Utils.hexToBytes("0500000000000000000000000000000000000000000000000000000000000000");
public static final byte[] EIGHT = Utils.hexToBytes("0800000000000000000000000000000000000000000000000000000000000000");
}

View file

@ -1,87 +0,0 @@
package org.xbib.io.sshd.eddsa.math;
//import java.io.Serializable;
/**
* A twisted Edwards curve.
* Points on the curve satisfy $-x^2 + y^2 = 1 + d x^2y^2$
*/
public class Curve /*implements Serializable*/ {
//private static final long serialVersionUID = 4578920872509827L;
private final Field f;
private final FieldElement d;
private final FieldElement d2;
private final FieldElement I;
private final GroupElement zeroP2;
private final GroupElement zeroP3;
private final GroupElement zeroPrecomp;
public Curve(Field f, byte[] d, FieldElement I) {
this.f = f;
this.d = f.fromByteArray(d);
this.d2 = this.d.add(this.d);
this.I = I;
FieldElement zero = f.ZERO;
FieldElement one = f.ONE;
zeroP2 = GroupElement.p2(this, zero, one, one);
zeroP3 = GroupElement.p3(this, zero, one, one, zero);
zeroPrecomp = GroupElement.precomp(this, one, one, zero);
}
public Field getField() {
return f;
}
public FieldElement getD() {
return d;
}
public FieldElement get2D() {
return d2;
}
public FieldElement getI() {
return I;
}
public GroupElement getZero(GroupElement.Representation repr) {
switch (repr) {
case P2:
return zeroP2;
case P3:
return zeroP3;
case PRECOMP:
return zeroPrecomp;
default:
return null;
}
}
public GroupElement createPoint(byte[] P, boolean precompute) {
GroupElement ge = new GroupElement(this, P);
if (precompute)
ge.precompute(true);
return ge;
}
@Override
public int hashCode() {
return f.hashCode() ^
d.hashCode() ^
I.hashCode();
}
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Curve))
return false;
Curve c = (Curve) o;
return f.equals(c.getField()) &&
d.equals(c.getD()) &&
I.equals(c.getI());
}
}

View file

@ -1,43 +0,0 @@
package org.xbib.io.sshd.eddsa.math;
/**
* Common interface for all $(b-1)$-bit encodings of elements of EdDSA finite fields.
*/
public abstract class Encoding {
protected Field f;
public synchronized void setField(Field f) {
if (this.f != null)
throw new IllegalStateException("already set");
this.f = f;
}
/**
* Encode a FieldElement in its $(b-1)$-bit encoding.
*
* @param x the FieldElement to encode
* @return the $(b-1)$-bit encoding of this FieldElement.
*/
public abstract byte[] encode(FieldElement x);
/**
* Decode a FieldElement from its $(b-1)$-bit encoding.
* The highest bit is masked out.
*
* @param in the $(b-1)$-bit encoding of a FieldElement.
* @return the FieldElement represented by 'val'.
*/
public abstract FieldElement decode(byte[] in);
/**
* From the Ed25519 paper:<br>
* $x$ is negative if the $(b-1)$-bit encoding of $x$ is lexicographically larger
* than the $(b-1)$-bit encoding of -x. If $q$ is an odd prime and the encoding
* is the little-endian representation of $\{0, 1,\dots, q-1\}$ then the negative
* elements of $F_q$ are $\{1, 3, 5,\dots, q-2\}$.
*
* @param x the FieldElement to check
* @return true if negative
*/
public abstract boolean isNegative(FieldElement x);
}

View file

@ -1,84 +0,0 @@
package org.xbib.io.sshd.eddsa.math;
/**
* An EdDSA finite field. Includes several pre-computed values.
*/
public class Field /*implements Serializable*/ {
//private static final long serialVersionUID = 8746587465875676L;
public final FieldElement ZERO;
public final FieldElement ONE;
public final FieldElement TWO;
public final FieldElement FOUR;
public final FieldElement FIVE;
public final FieldElement EIGHT;
private final int b;
private final FieldElement q;
/**
* q-2
*/
private final FieldElement qm2;
/**
* (q-5) / 8
*/
private final FieldElement qm5d8;
private final Encoding enc;
public Field(int b, byte[] q, Encoding enc) {
this.b = b;
this.enc = enc;
this.enc.setField(this);
this.q = fromByteArray(q);
// Set up constants
ZERO = fromByteArray(Constants.ZERO);
ONE = fromByteArray(Constants.ONE);
TWO = fromByteArray(Constants.TWO);
FOUR = fromByteArray(Constants.FOUR);
FIVE = fromByteArray(Constants.FIVE);
EIGHT = fromByteArray(Constants.EIGHT);
// Precompute values
qm2 = this.q.subtract(TWO);
qm5d8 = this.q.subtract(FIVE).divide(EIGHT);
}
public FieldElement fromByteArray(byte[] x) {
return enc.decode(x);
}
public int getb() {
return b;
}
public FieldElement getQ() {
return q;
}
public FieldElement getQm2() {
return qm2;
}
public FieldElement getQm5d8() {
return qm5d8;
}
public Encoding getEncoding() {
return enc;
}
@Override
public int hashCode() {
return q.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Field))
return false;
Field f = (Field) obj;
return b == f.b && q.equals(f.q);
}
}

View file

@ -1,66 +0,0 @@
package org.xbib.io.sshd.eddsa.math;
//import java.io.Serializable;
/**
* Note: concrete subclasses must implement hashCode() and equals()
*/
public abstract class FieldElement /*implements Serializable*/ {
//private static final long serialVersionUID = 1239527465875676L;
protected final Field f;
public FieldElement(Field f) {
if (null == f) {
throw new IllegalArgumentException("field cannot be null");
}
this.f = f;
}
/**
* Encode a FieldElement in its $(b-1)$-bit encoding.
*
* @return the $(b-1)$-bit encoding of this FieldElement.
*/
public byte[] toByteArray() {
return f.getEncoding().encode(this);
}
public abstract boolean isNonZero();
public boolean isNegative() {
return f.getEncoding().isNegative(this);
}
public abstract FieldElement add(FieldElement val);
public FieldElement addOne() {
return add(f.ONE);
}
public abstract FieldElement subtract(FieldElement val);
public FieldElement subtractOne() {
return subtract(f.ONE);
}
public abstract FieldElement negate();
public FieldElement divide(FieldElement val) {
return multiply(val.invert());
}
public abstract FieldElement multiply(FieldElement val);
public abstract FieldElement square();
public abstract FieldElement squareAndDouble();
public abstract FieldElement invert();
public abstract FieldElement pow22523();
public abstract FieldElement cmov(FieldElement val, final int b);
// Note: concrete subclasses must implement hashCode() and equals()
}

View file

@ -1,27 +0,0 @@
package org.xbib.io.sshd.eddsa.math;
/**
*
*/
public interface ScalarOps {
/**
* Reduce the given scalar mod $l$.
* From the Ed25519 paper:
* Here we interpret $2b$-bit strings in little-endian form as integers in
* $\{0, 1,..., 2^{(2b)}-1\}$.
*
* @param s the scalar to reduce
* @return $s \bmod l$
*/
byte[] reduce(byte[] s);
/**
* $r = (a * b + c) \bmod l$
*
* @param a a scalar
* @param b a scalar
* @param c a scalar
* @return $(a*b + c) \bmod l$
*/
byte[] multiplyAndAdd(byte[] a, byte[] b, byte[] c);
}

View file

@ -1,118 +0,0 @@
package org.xbib.io.sshd.eddsa.math.bigint;
import org.xbib.io.sshd.eddsa.math.Field;
import org.xbib.io.sshd.eddsa.math.FieldElement;
import java.io.Serializable;
import java.math.BigInteger;
/**
* A particular element of the field \Z/(2^255-19).
*/
public class BigIntegerFieldElement extends FieldElement implements Serializable {
private static final long serialVersionUID = 4890398908392808L;
/**
* Variable is package private for encoding.
*/
final BigInteger bi;
public BigIntegerFieldElement(Field f, BigInteger bi) {
super(f);
this.bi = bi;
}
public boolean isNonZero() {
return !bi.equals(BigInteger.ZERO);
}
public FieldElement add(FieldElement val) {
return new BigIntegerFieldElement(f, bi.add(((BigIntegerFieldElement) val).bi)).mod(f.getQ());
}
@Override
public FieldElement addOne() {
return new BigIntegerFieldElement(f, bi.add(BigInteger.ONE)).mod(f.getQ());
}
public FieldElement subtract(FieldElement val) {
return new BigIntegerFieldElement(f, bi.subtract(((BigIntegerFieldElement) val).bi)).mod(f.getQ());
}
@Override
public FieldElement subtractOne() {
return new BigIntegerFieldElement(f, bi.subtract(BigInteger.ONE)).mod(f.getQ());
}
public FieldElement negate() {
return f.getQ().subtract(this);
}
@Override
public FieldElement divide(FieldElement val) {
return divide(((BigIntegerFieldElement) val).bi);
}
public FieldElement divide(BigInteger val) {
return new BigIntegerFieldElement(f, bi.divide(val)).mod(f.getQ());
}
public FieldElement multiply(FieldElement val) {
return new BigIntegerFieldElement(f, bi.multiply(((BigIntegerFieldElement) val).bi)).mod(f.getQ());
}
public FieldElement square() {
return multiply(this);
}
public FieldElement squareAndDouble() {
FieldElement sq = square();
return sq.add(sq);
}
public FieldElement invert() {
// Euler's theorem
//return modPow(f.getQm2(), f.getQ());
return new BigIntegerFieldElement(f, bi.modInverse(((BigIntegerFieldElement) f.getQ()).bi));
}
public FieldElement mod(FieldElement m) {
return new BigIntegerFieldElement(f, bi.mod(((BigIntegerFieldElement) m).bi));
}
public FieldElement modPow(FieldElement e, FieldElement m) {
return new BigIntegerFieldElement(f, bi.modPow(((BigIntegerFieldElement) e).bi, ((BigIntegerFieldElement) m).bi));
}
public FieldElement pow(FieldElement e) {
return modPow(e, f.getQ());
}
public FieldElement pow22523() {
return pow(f.getQm5d8());
}
@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();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof BigIntegerFieldElement))
return false;
BigIntegerFieldElement fe = (BigIntegerFieldElement) obj;
return bi.equals(fe.bi);
}
@Override
public String toString() {
return "[BigIntegerFieldElement val=" + bi + "]";
}
}

View file

@ -1,95 +0,0 @@
package org.xbib.io.sshd.eddsa.math.bigint;
import org.xbib.io.sshd.eddsa.math.Encoding;
import org.xbib.io.sshd.eddsa.math.Field;
import org.xbib.io.sshd.eddsa.math.FieldElement;
import java.io.Serializable;
import java.math.BigInteger;
/**
*
*/
public class BigIntegerLittleEndianEncoding extends Encoding implements Serializable {
private static final long serialVersionUID = 3984579843759837L;
/**
* Mask where only the first b-1 bits are set.
*/
private BigInteger mask;
@Override
public synchronized void setField(Field f) {
super.setField(f);
mask = BigInteger.ONE.shiftLeft(f.getb() - 1).subtract(BigInteger.ONE);
}
public byte[] encode(FieldElement x) {
return encode(((BigIntegerFieldElement) x).bi.and(mask));
}
/**
* Convert $x$ to little endian.
* Constant time.
*
* @param x the BigInteger value to encode
* @return array of length $b/8$
* @throws IllegalStateException if field not set
*/
public byte[] encode(BigInteger x) {
if (f == null)
throw new IllegalStateException("field not set");
byte[] in = x.toByteArray();
byte[] out = new byte[f.getb() / 8];
for (int i = 0; i < in.length; i++) {
out[i] = in[in.length - 1 - i];
}
for (int i = in.length; i < out.length; i++) {
out[i] = 0;
}
return out;
}
/**
* Decode a FieldElement from its $(b-1)$-bit encoding.
* The highest bit is masked out.
*
* @param in the $(b-1)$-bit encoding of a FieldElement.
* @return the FieldElement represented by 'val'.
* @throws IllegalStateException if field not set
* @throws IllegalArgumentException if encoding is invalid
*/
public FieldElement decode(byte[] in) {
if (f == null)
throw new IllegalStateException("field not set");
if (in.length != f.getb() / 8)
throw new IllegalArgumentException("Not a valid encoding");
return new BigIntegerFieldElement(f, toBigInteger(in).and(mask));
}
/**
* Convert in to big endian
*
* @param in the $(b-1)$-bit encoding of a FieldElement.
* @return the decoded value as a BigInteger
*/
public BigInteger toBigInteger(byte[] in) {
byte[] out = new byte[in.length];
for (int i = 0; i < in.length; i++) {
out[i] = in[in.length - 1 - i];
}
return new BigInteger(1, out);
}
/**
* From the Ed25519 paper:<br>
* $x$ is negative if the $(b-1)$-bit encoding of $x$ is lexicographically larger
* than the $(b-1)$-bit encoding of $-x$. If $q$ is an odd prime and the encoding
* is the little-endian representation of $\{0, 1,\dots, q-1\}$ then the negative
* elements of $F_q$ are $\{1, 3, 5,\dots, q-2\}$.
*
* @return true if negative
*/
public boolean isNegative(FieldElement x) {
return ((BigIntegerFieldElement) x).bi.testBit(0);
}
}

View file

@ -1,36 +0,0 @@
/**
* EdDSA-Java by str4d
* <p>
* To the extent possible under law, the person who associated CC0 with
* EdDSA-Java has waived all copyright and related or neighboring rights
* to EdDSA-Java.
* <p>
* You should have received a copy of the CC0 legalcode along with this
* work. If not, see <https://creativecommons.org/publicdomain/zero/1.0/>.
*/
package org.xbib.io.sshd.eddsa.math.bigint;
import org.xbib.io.sshd.eddsa.math.Field;
import org.xbib.io.sshd.eddsa.math.ScalarOps;
import java.math.BigInteger;
public class BigIntegerScalarOps implements ScalarOps {
private final BigInteger l;
private final BigIntegerLittleEndianEncoding enc;
public BigIntegerScalarOps(Field f, BigInteger l) {
this.l = l;
enc = new BigIntegerLittleEndianEncoding();
enc.setField(f);
}
public byte[] reduce(byte[] s) {
return enc.encode(enc.toBigInteger(s).mod(l));
}
public byte[] multiplyAndAdd(byte[] a, byte[] b, byte[] c) {
return enc.encode(enc.toBigInteger(a).multiply(enc.toBigInteger(b)).add(enc.toBigInteger(c)).mod(l));
}
}

View file

@ -1,4 +0,0 @@
/**
* Low-level, non-optimized implementation using BigIntegers for any curve.
*/
package org.xbib.io.sshd.eddsa.math.bigint;

View file

@ -1,284 +0,0 @@
package org.xbib.io.sshd.eddsa.math.ed25519;
import org.xbib.io.sshd.eddsa.math.Encoding;
import org.xbib.io.sshd.eddsa.math.FieldElement;
/**
* Helper class for encoding/decoding from/to the 32 byte representation.
* Reviewed/commented by Bloody Rookie (nemproject@gmx.de)
*/
public class Ed25519LittleEndianEncoding extends Encoding {
static int load_3(byte[] in, int offset) {
int result = in[offset++] & 0xff;
result |= (in[offset++] & 0xff) << 8;
result |= (in[offset] & 0xff) << 16;
return result;
}
static long load_4(byte[] in, int offset) {
int result = in[offset++] & 0xff;
result |= (in[offset++] & 0xff) << 8;
result |= (in[offset++] & 0xff) << 16;
result |= in[offset] << 24;
return ((long) result) & 0xffffffffL;
}
/**
* Encodes a given field element in its 32 byte representation. This is done in two steps:
* <ol>
* <li>Reduce the value of the field element modulo $p$.
* <li>Convert the field element to the 32 byte representation.
* </ol><p>
* The idea for the modulo $p$ reduction algorithm is as follows:
* </p>
* <h2>Assumption:</h2>
* <ul>
* <li>$p = 2^{255} - 19$
* <li>$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$.
* <li>$h \cong r \mod p$, i.e. $h = r + q * p$ for some suitable $0 \le r \lt p$ and an integer $q$.
* </ul><p>
* Then $q = [2^{-255} * (h + 19 * 2^{-25} * h_9 + 1/2)]$ where $[x] = floor(x)$.
* </p>
* <h2>Proof:</h2>
* <p>
* We begin with some very raw estimation for the bounds of some expressions:
* <p>
* $$
* \begin{equation}
* |h| \lt 2^{230} * 2^{30} = 2^{260} \Rightarrow |r + q * p| \lt 2^{260} \Rightarrow |q| \lt 2^{10}. \\
* \Rightarrow -1/4 \le a := 19^2 * 2^{-255} * q \lt 1/4. \\
* |h - 2^{230} * h_9| = |h_0 + \dots + 2^{204} * h_8| \lt 2^{204} * 2^{30} = 2^{234}. \\
* \Rightarrow -1/4 \le b := 19 * 2^{-255} * (h - 2^{230} * h_9) \lt 1/4
* \end{equation}
* $$
* <p>
* Therefore $0 \lt 1/2 - a - b \lt 1$.
* <p>
* Set $x := r + 19 * 2^{-255} * r + 1/2 - a - b$. Then:
* <p>
* $$
* 0 \le x \lt 255 - 20 + 19 + 1 = 2^{255} \\
* \Rightarrow 0 \le 2^{-255} * x \lt 1.
* $$
* <p>
* Since $q$ is an integer we have
* <p>
* $$
* [q + 2^{-255} * x] = q \quad (1)
* $$
* <p>
* Have a closer look at $x$:
* <p>
* $$
* \begin{align}
* x &amp;= 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) \\
* &amp;= 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 \\
* &amp;= h + 19 * 2^{-25} * h_9 + 1/2 - q^{255}.
* \end{align}
* $$
* <p>
* Inserting the expression for $x$ into $(1)$ we get the desired expression for $q$.
*/
public byte[] encode(FieldElement x) {
int[] h = ((Ed25519FieldElement) x).t;
int h0 = h[0];
int h1 = h[1];
int h2 = h[2];
int h3 = h[3];
int h4 = h[4];
int h5 = h[5];
int h6 = h[6];
int h7 = h[7];
int h8 = h[8];
int h9 = h[9];
int q;
int carry0;
int carry1;
int carry2;
int carry3;
int carry4;
int carry5;
int carry6;
int carry7;
int carry8;
int carry9;
// Step 1:
// Calculate q
q = (19 * h9 + (1 << 24)) >> 25;
q = (h0 + q) >> 26;
q = (h1 + q) >> 25;
q = (h2 + q) >> 26;
q = (h3 + q) >> 25;
q = (h4 + q) >> 26;
q = (h5 + q) >> 25;
q = (h6 + q) >> 26;
q = (h7 + q) >> 25;
q = (h8 + q) >> 26;
q = (h9 + q) >> 25;
// r = h - q * p = h - 2^255 * q + 19 * q
// First add 19 * q then discard the bit 255
h0 += 19 * q;
carry0 = h0 >> 26;
h1 += carry0;
h0 -= carry0 << 26;
carry1 = h1 >> 25;
h2 += carry1;
h1 -= carry1 << 25;
carry2 = h2 >> 26;
h3 += carry2;
h2 -= carry2 << 26;
carry3 = h3 >> 25;
h4 += carry3;
h3 -= carry3 << 25;
carry4 = h4 >> 26;
h5 += carry4;
h4 -= carry4 << 26;
carry5 = h5 >> 25;
h6 += carry5;
h5 -= carry5 << 25;
carry6 = h6 >> 26;
h7 += carry6;
h6 -= carry6 << 26;
carry7 = h7 >> 25;
h8 += carry7;
h7 -= carry7 << 25;
carry8 = h8 >> 26;
h9 += carry8;
h8 -= carry8 << 26;
carry9 = h9 >> 25;
h9 -= carry9 << 25;
// Step 2 (straight forward conversion):
byte[] s = new byte[32];
s[0] = (byte) h0;
s[1] = (byte) (h0 >> 8);
s[2] = (byte) (h0 >> 16);
s[3] = (byte) ((h0 >> 24) | (h1 << 2));
s[4] = (byte) (h1 >> 6);
s[5] = (byte) (h1 >> 14);
s[6] = (byte) ((h1 >> 22) | (h2 << 3));
s[7] = (byte) (h2 >> 5);
s[8] = (byte) (h2 >> 13);
s[9] = (byte) ((h2 >> 21) | (h3 << 5));
s[10] = (byte) (h3 >> 3);
s[11] = (byte) (h3 >> 11);
s[12] = (byte) ((h3 >> 19) | (h4 << 6));
s[13] = (byte) (h4 >> 2);
s[14] = (byte) (h4 >> 10);
s[15] = (byte) (h4 >> 18);
s[16] = (byte) h5;
s[17] = (byte) (h5 >> 8);
s[18] = (byte) (h5 >> 16);
s[19] = (byte) ((h5 >> 24) | (h6 << 1));
s[20] = (byte) (h6 >> 7);
s[21] = (byte) (h6 >> 15);
s[22] = (byte) ((h6 >> 23) | (h7 << 3));
s[23] = (byte) (h7 >> 5);
s[24] = (byte) (h7 >> 13);
s[25] = (byte) ((h7 >> 21) | (h8 << 4));
s[26] = (byte) (h8 >> 4);
s[27] = (byte) (h8 >> 12);
s[28] = (byte) ((h8 >> 20) | (h9 << 6));
s[29] = (byte) (h9 >> 2);
s[30] = (byte) (h9 >> 10);
s[31] = (byte) (h9 >> 18);
return s;
}
/**
* Decodes a given field element in its 10 byte $2^{25.5}$ representation.
*
* @param in The 32 byte representation.
* @return The field element in its $2^{25.5}$ bit representation.
*/
public FieldElement decode(byte[] in) {
long h0 = load_4(in, 0);
long h1 = load_3(in, 4) << 6;
long h2 = load_3(in, 7) << 5;
long h3 = load_3(in, 10) << 3;
long h4 = load_3(in, 13) << 2;
long h5 = load_4(in, 16);
long h6 = load_3(in, 20) << 7;
long h7 = load_3(in, 23) << 5;
long h8 = load_3(in, 26) << 4;
long h9 = (load_3(in, 29) & 0x7FFFFF) << 2;
long carry0;
long carry1;
long carry2;
long carry3;
long carry4;
long carry5;
long carry6;
long carry7;
long carry8;
long carry9;
// Remember: 2^255 congruent 19 modulo p
carry9 = (h9 + (long) (1 << 24)) >> 25;
h0 += carry9 * 19;
h9 -= carry9 << 25;
carry1 = (h1 + (long) (1 << 24)) >> 25;
h2 += carry1;
h1 -= carry1 << 25;
carry3 = (h3 + (long) (1 << 24)) >> 25;
h4 += carry3;
h3 -= carry3 << 25;
carry5 = (h5 + (long) (1 << 24)) >> 25;
h6 += carry5;
h5 -= carry5 << 25;
carry7 = (h7 + (long) (1 << 24)) >> 25;
h8 += carry7;
h7 -= carry7 << 25;
carry0 = (h0 + (long) (1 << 25)) >> 26;
h1 += carry0;
h0 -= carry0 << 26;
carry2 = (h2 + (long) (1 << 25)) >> 26;
h3 += carry2;
h2 -= carry2 << 26;
carry4 = (h4 + (long) (1 << 25)) >> 26;
h5 += carry4;
h4 -= carry4 << 26;
carry6 = (h6 + (long) (1 << 25)) >> 26;
h7 += carry6;
h6 -= carry6 << 26;
carry8 = (h8 + (long) (1 << 25)) >> 26;
h9 += carry8;
h8 -= carry8 << 26;
int[] h = new int[10];
h[0] = (int) h0;
h[1] = (int) h1;
h[2] = (int) h2;
h[3] = (int) h3;
h[4] = (int) h4;
h[5] = (int) h5;
h[6] = (int) h6;
h[7] = (int) h7;
h[8] = (int) h8;
h[9] = (int) h9;
return new Ed25519FieldElement(f, h);
}
/**
* Is the FieldElement negative in this encoding?
* <p>
* Return true if $x$ is in $\{1,3,5,\dots,q-2\}$<br>
* Return false if $x$ is in $\{0,2,4,\dots,q-1\}$
* <p>
* Preconditions:
* </p><ul>
* <li>$|x|$ bounded by $1.1*2^{26},1.1*2^{25},1.1*2^{26},1.1*2^{25}$, etc.
* </ul>
*
* @return true if $x$ is in $\{1,3,5,\dots,q-2\}$, false otherwise.
*/
public boolean isNegative(FieldElement x) {
byte[] s = encode(x);
return (s[0] & 1) != 0;
}
}

View file

@ -1,913 +0,0 @@
package org.xbib.io.sshd.eddsa.math.ed25519;
import org.xbib.io.sshd.eddsa.math.ScalarOps;
import static org.xbib.io.sshd.eddsa.math.ed25519.Ed25519LittleEndianEncoding.load_3;
import static org.xbib.io.sshd.eddsa.math.ed25519.Ed25519LittleEndianEncoding.load_4;
/**
* Class for reducing a huge integer modulo the group order q and
* doing a combined multiply plus add plus reduce operation.
* <p>
* $q = 2^{252} + 27742317777372353535851937790883648493$.
* <p>
* Reviewed/commented by Bloody Rookie (nemproject@gmx.de)
*/
public class Ed25519ScalarOps implements ScalarOps {
/**
* Reduction modulo the group order $q$.
* <p>
* Input:
* $s[0]+256*s[1]+\dots+256^{63}*s[63] = s$
* <p>
* Output:
* $s[0]+256*s[1]+\dots+256^{31}*s[31] = s \bmod q$
* where $q = 2^{252} + 27742317777372353535851937790883648493$.
*/
public byte[] reduce(byte[] s) {
// s0,..., s22 have 21 bits, s23 has 29 bits
long s0 = 0x1FFFFF & load_3(s, 0);
long s1 = 0x1FFFFF & (load_4(s, 2) >> 5);
long s2 = 0x1FFFFF & (load_3(s, 5) >> 2);
long s3 = 0x1FFFFF & (load_4(s, 7) >> 7);
long s4 = 0x1FFFFF & (load_4(s, 10) >> 4);
long s5 = 0x1FFFFF & (load_3(s, 13) >> 1);
long s6 = 0x1FFFFF & (load_4(s, 15) >> 6);
long s7 = 0x1FFFFF & (load_3(s, 18) >> 3);
long s8 = 0x1FFFFF & load_3(s, 21);
long s9 = 0x1FFFFF & (load_4(s, 23) >> 5);
long s10 = 0x1FFFFF & (load_3(s, 26) >> 2);
long s11 = 0x1FFFFF & (load_4(s, 28) >> 7);
long s12 = 0x1FFFFF & (load_4(s, 31) >> 4);
long s13 = 0x1FFFFF & (load_3(s, 34) >> 1);
long s14 = 0x1FFFFF & (load_4(s, 36) >> 6);
long s15 = 0x1FFFFF & (load_3(s, 39) >> 3);
long s16 = 0x1FFFFF & load_3(s, 42);
long s17 = 0x1FFFFF & (load_4(s, 44) >> 5);
long s18 = 0x1FFFFF & (load_3(s, 47) >> 2);
long s19 = 0x1FFFFF & (load_4(s, 49) >> 7);
long s20 = 0x1FFFFF & (load_4(s, 52) >> 4);
long s21 = 0x1FFFFF & (load_3(s, 55) >> 1);
long s22 = 0x1FFFFF & (load_4(s, 57) >> 6);
long s23 = (load_4(s, 60) >> 3);
long carry0;
long carry1;
long carry2;
long carry3;
long carry4;
long carry5;
long carry6;
long carry7;
long carry8;
long carry9;
long carry10;
long carry11;
long carry12;
long carry13;
long carry14;
long carry15;
long carry16;
/**
* Lots of magic numbers :)
* To understand what's going on below, note that
*
* (1) q = 2^252 + q0 where q0 = 27742317777372353535851937790883648493.
* (2) s11 is the coefficient of 2^(11*21), s23 is the coefficient of 2^(^23*21) and 2^252 = 2^((23-11) * 21)).
* (3) 2^252 congruent -q0 modulo q.
* (4) -q0 = 666643 * 2^0 + 470296 * 2^21 + 654183 * 2^(2*21) - 997805 * 2^(3*21) + 136657 * 2^(4*21) - 683901 * 2^(5*21)
*
* Thus
* s23 * 2^(23*11) = s23 * 2^(12*21) * 2^(11*21) = s3 * 2^252 * 2^(11*21) congruent
* s23 * (666643 * 2^0 + 470296 * 2^21 + 654183 * 2^(2*21) - 997805 * 2^(3*21) + 136657 * 2^(4*21) - 683901 * 2^(5*21)) * 2^(11*21) modulo q =
* s23 * (666643 * 2^(11*21) + 470296 * 2^(12*21) + 654183 * 2^(13*21) - 997805 * 2^(14*21) + 136657 * 2^(15*21) - 683901 * 2^(16*21)).
*
* The same procedure is then applied for s22,...,s18.
*/
s11 += s23 * 666643;
s12 += s23 * 470296;
s13 += s23 * 654183;
s14 -= s23 * 997805;
s15 += s23 * 136657;
s16 -= s23 * 683901;
// not used again
//s23 = 0;
s10 += s22 * 666643;
s11 += s22 * 470296;
s12 += s22 * 654183;
s13 -= s22 * 997805;
s14 += s22 * 136657;
s15 -= s22 * 683901;
// not used again
//s22 = 0;
s9 += s21 * 666643;
s10 += s21 * 470296;
s11 += s21 * 654183;
s12 -= s21 * 997805;
s13 += s21 * 136657;
s14 -= s21 * 683901;
// not used again
//s21 = 0;
s8 += s20 * 666643;
s9 += s20 * 470296;
s10 += s20 * 654183;
s11 -= s20 * 997805;
s12 += s20 * 136657;
s13 -= s20 * 683901;
// not used again
//s20 = 0;
s7 += s19 * 666643;
s8 += s19 * 470296;
s9 += s19 * 654183;
s10 -= s19 * 997805;
s11 += s19 * 136657;
s12 -= s19 * 683901;
// not used again
//s19 = 0;
s6 += s18 * 666643;
s7 += s18 * 470296;
s8 += s18 * 654183;
s9 -= s18 * 997805;
s10 += s18 * 136657;
s11 -= s18 * 683901;
// not used again
//s18 = 0;
/**
* Time to reduce the coefficient in order not to get an overflow.
*/
carry6 = (s6 + (1 << 20)) >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry8 = (s8 + (1 << 20)) >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry10 = (s10 + (1 << 20)) >> 21;
s11 += carry10;
s10 -= carry10 << 21;
carry12 = (s12 + (1 << 20)) >> 21;
s13 += carry12;
s12 -= carry12 << 21;
carry14 = (s14 + (1 << 20)) >> 21;
s15 += carry14;
s14 -= carry14 << 21;
carry16 = (s16 + (1 << 20)) >> 21;
s17 += carry16;
s16 -= carry16 << 21;
carry7 = (s7 + (1 << 20)) >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry9 = (s9 + (1 << 20)) >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry11 = (s11 + (1 << 20)) >> 21;
s12 += carry11;
s11 -= carry11 << 21;
carry13 = (s13 + (1 << 20)) >> 21;
s14 += carry13;
s13 -= carry13 << 21;
carry15 = (s15 + (1 << 20)) >> 21;
s16 += carry15;
s15 -= carry15 << 21;
/**
* Continue with above procedure.
*/
s5 += s17 * 666643;
s6 += s17 * 470296;
s7 += s17 * 654183;
s8 -= s17 * 997805;
s9 += s17 * 136657;
s10 -= s17 * 683901;
// not used again
//s17 = 0;
s4 += s16 * 666643;
s5 += s16 * 470296;
s6 += s16 * 654183;
s7 -= s16 * 997805;
s8 += s16 * 136657;
s9 -= s16 * 683901;
// not used again
//s16 = 0;
s3 += s15 * 666643;
s4 += s15 * 470296;
s5 += s15 * 654183;
s6 -= s15 * 997805;
s7 += s15 * 136657;
s8 -= s15 * 683901;
// not used again
//s15 = 0;
s2 += s14 * 666643;
s3 += s14 * 470296;
s4 += s14 * 654183;
s5 -= s14 * 997805;
s6 += s14 * 136657;
s7 -= s14 * 683901;
// not used again
//s14 = 0;
s1 += s13 * 666643;
s2 += s13 * 470296;
s3 += s13 * 654183;
s4 -= s13 * 997805;
s5 += s13 * 136657;
s6 -= s13 * 683901;
// not used again
//s13 = 0;
s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
// set below
//s12 = 0;
/**
* Reduce coefficients again.
*/
carry0 = (s0 + (1 << 20)) >> 21;
s1 += carry0;
s0 -= carry0 << 21;
carry2 = (s2 + (1 << 20)) >> 21;
s3 += carry2;
s2 -= carry2 << 21;
carry4 = (s4 + (1 << 20)) >> 21;
s5 += carry4;
s4 -= carry4 << 21;
carry6 = (s6 + (1 << 20)) >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry8 = (s8 + (1 << 20)) >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry10 = (s10 + (1 << 20)) >> 21;
s11 += carry10;
s10 -= carry10 << 21;
carry1 = (s1 + (1 << 20)) >> 21;
s2 += carry1;
s1 -= carry1 << 21;
carry3 = (s3 + (1 << 20)) >> 21;
s4 += carry3;
s3 -= carry3 << 21;
carry5 = (s5 + (1 << 20)) >> 21;
s6 += carry5;
s5 -= carry5 << 21;
carry7 = (s7 + (1 << 20)) >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry9 = (s9 + (1 << 20)) >> 21;
s10 += carry9;
s9 -= carry9 << 21;
//carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21;
carry11 = (s11 + (1 << 20)) >> 21;
s12 = carry11;
s11 -= carry11 << 21;
s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
// set below
//s12 = 0;
carry0 = s0 >> 21;
s1 += carry0;
s0 -= carry0 << 21;
carry1 = s1 >> 21;
s2 += carry1;
s1 -= carry1 << 21;
carry2 = s2 >> 21;
s3 += carry2;
s2 -= carry2 << 21;
carry3 = s3 >> 21;
s4 += carry3;
s3 -= carry3 << 21;
carry4 = s4 >> 21;
s5 += carry4;
s4 -= carry4 << 21;
carry5 = s5 >> 21;
s6 += carry5;
s5 -= carry5 << 21;
carry6 = s6 >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry7 = s7 >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry8 = s8 >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry9 = s9 >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry10 = s10 >> 21;
s11 += carry10;
s10 -= carry10 << 21;
//carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21;
carry11 = s11 >> 21;
s12 = carry11;
s11 -= carry11 << 21;
// TODO-CR BR: Is it really needed to do it TWO times? (it doesn't hurt, just a question).
s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
// not used again
//s12 = 0;
carry0 = s0 >> 21;
s1 += carry0;
s0 -= carry0 << 21;
carry1 = s1 >> 21;
s2 += carry1;
s1 -= carry1 << 21;
carry2 = s2 >> 21;
s3 += carry2;
s2 -= carry2 << 21;
carry3 = s3 >> 21;
s4 += carry3;
s3 -= carry3 << 21;
carry4 = s4 >> 21;
s5 += carry4;
s4 -= carry4 << 21;
carry5 = s5 >> 21;
s6 += carry5;
s5 -= carry5 << 21;
carry6 = s6 >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry7 = s7 >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry8 = s8 >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry9 = s9 >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry10 = s10 >> 21;
s11 += carry10;
s10 -= carry10 << 21;
// s0, ..., s11 got 21 bits each.
byte[] result = new byte[32];
result[0] = (byte) s0;
result[1] = (byte) (s0 >> 8);
result[2] = (byte) ((s0 >> 16) | (s1 << 5));
result[3] = (byte) (s1 >> 3);
result[4] = (byte) (s1 >> 11);
result[5] = (byte) ((s1 >> 19) | (s2 << 2));
result[6] = (byte) (s2 >> 6);
result[7] = (byte) ((s2 >> 14) | (s3 << 7));
result[8] = (byte) (s3 >> 1);
result[9] = (byte) (s3 >> 9);
result[10] = (byte) ((s3 >> 17) | (s4 << 4));
result[11] = (byte) (s4 >> 4);
result[12] = (byte) (s4 >> 12);
result[13] = (byte) ((s4 >> 20) | (s5 << 1));
result[14] = (byte) (s5 >> 7);
result[15] = (byte) ((s5 >> 15) | (s6 << 6));
result[16] = (byte) (s6 >> 2);
result[17] = (byte) (s6 >> 10);
result[18] = (byte) ((s6 >> 18) | (s7 << 3));
result[19] = (byte) (s7 >> 5);
result[20] = (byte) (s7 >> 13);
result[21] = (byte) s8;
result[22] = (byte) (s8 >> 8);
result[23] = (byte) ((s8 >> 16) | (s9 << 5));
result[24] = (byte) (s9 >> 3);
result[25] = (byte) (s9 >> 11);
result[26] = (byte) ((s9 >> 19) | (s10 << 2));
result[27] = (byte) (s10 >> 6);
result[28] = (byte) ((s10 >> 14) | (s11 << 7));
result[29] = (byte) (s11 >> 1);
result[30] = (byte) (s11 >> 9);
result[31] = (byte) (s11 >> 17);
return result;
}
/**
* $(ab+c) \bmod q$
* <p>
* Input:
* </p><ul>
* <li>$a[0]+256*a[1]+\dots+256^{31}*a[31] = a$
* <li>$b[0]+256*b[1]+\dots+256^{31}*b[31] = b$
* <li>$c[0]+256*c[1]+\dots+256^{31}*c[31] = c$
* </ul><p>
* Output:
* $result[0]+256*result[1]+\dots+256^{31}*result[31] = (ab+c) \bmod q$
* where $q = 2^{252} + 27742317777372353535851937790883648493$.
* <p>
* See the comments in {@link #reduce(byte[])} for an explanation of the algorithm.
*/
public byte[] multiplyAndAdd(byte[] a, byte[] b, byte[] c) {
long a0 = 0x1FFFFF & load_3(a, 0);
long a1 = 0x1FFFFF & (load_4(a, 2) >> 5);
long a2 = 0x1FFFFF & (load_3(a, 5) >> 2);
long a3 = 0x1FFFFF & (load_4(a, 7) >> 7);
long a4 = 0x1FFFFF & (load_4(a, 10) >> 4);
long a5 = 0x1FFFFF & (load_3(a, 13) >> 1);
long a6 = 0x1FFFFF & (load_4(a, 15) >> 6);
long a7 = 0x1FFFFF & (load_3(a, 18) >> 3);
long a8 = 0x1FFFFF & load_3(a, 21);
long a9 = 0x1FFFFF & (load_4(a, 23) >> 5);
long a10 = 0x1FFFFF & (load_3(a, 26) >> 2);
long a11 = (load_4(a, 28) >> 7);
long b0 = 0x1FFFFF & load_3(b, 0);
long b1 = 0x1FFFFF & (load_4(b, 2) >> 5);
long b2 = 0x1FFFFF & (load_3(b, 5) >> 2);
long b3 = 0x1FFFFF & (load_4(b, 7) >> 7);
long b4 = 0x1FFFFF & (load_4(b, 10) >> 4);
long b5 = 0x1FFFFF & (load_3(b, 13) >> 1);
long b6 = 0x1FFFFF & (load_4(b, 15) >> 6);
long b7 = 0x1FFFFF & (load_3(b, 18) >> 3);
long b8 = 0x1FFFFF & load_3(b, 21);
long b9 = 0x1FFFFF & (load_4(b, 23) >> 5);
long b10 = 0x1FFFFF & (load_3(b, 26) >> 2);
long b11 = (load_4(b, 28) >> 7);
long c0 = 0x1FFFFF & load_3(c, 0);
long c1 = 0x1FFFFF & (load_4(c, 2) >> 5);
long c2 = 0x1FFFFF & (load_3(c, 5) >> 2);
long c3 = 0x1FFFFF & (load_4(c, 7) >> 7);
long c4 = 0x1FFFFF & (load_4(c, 10) >> 4);
long c5 = 0x1FFFFF & (load_3(c, 13) >> 1);
long c6 = 0x1FFFFF & (load_4(c, 15) >> 6);
long c7 = 0x1FFFFF & (load_3(c, 18) >> 3);
long c8 = 0x1FFFFF & load_3(c, 21);
long c9 = 0x1FFFFF & (load_4(c, 23) >> 5);
long c10 = 0x1FFFFF & (load_3(c, 26) >> 2);
long c11 = (load_4(c, 28) >> 7);
long s0;
long s1;
long s2;
long s3;
long s4;
long s5;
long s6;
long s7;
long s8;
long s9;
long s10;
long s11;
long s12;
long s13;
long s14;
long s15;
long s16;
long s17;
long s18;
long s19;
long s20;
long s21;
long s22;
long s23;
long carry0;
long carry1;
long carry2;
long carry3;
long carry4;
long carry5;
long carry6;
long carry7;
long carry8;
long carry9;
long carry10;
long carry11;
long carry12;
long carry13;
long carry14;
long carry15;
long carry16;
long carry17;
long carry18;
long carry19;
long carry20;
long carry21;
long carry22;
s0 = c0 + a0 * b0;
s1 = c1 + a0 * b1 + a1 * b0;
s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0;
s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0;
s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0;
s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0;
s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0;
s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0;
s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1 + a8 * b0;
s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2 + a8 * b1 + a9 * b0;
s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0;
s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0;
s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3 + a10 * b2 + a11 * b1;
s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3 + a11 * b2;
s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + a11 * b3;
s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4;
s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5;
s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6;
s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7;
s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8;
s20 = a9 * b11 + a10 * b10 + a11 * b9;
s21 = a10 * b11 + a11 * b10;
s22 = a11 * b11;
// set below
//s23 = 0;
carry0 = (s0 + (1 << 20)) >> 21;
s1 += carry0;
s0 -= carry0 << 21;
carry2 = (s2 + (1 << 20)) >> 21;
s3 += carry2;
s2 -= carry2 << 21;
carry4 = (s4 + (1 << 20)) >> 21;
s5 += carry4;
s4 -= carry4 << 21;
carry6 = (s6 + (1 << 20)) >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry8 = (s8 + (1 << 20)) >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry10 = (s10 + (1 << 20)) >> 21;
s11 += carry10;
s10 -= carry10 << 21;
carry12 = (s12 + (1 << 20)) >> 21;
s13 += carry12;
s12 -= carry12 << 21;
carry14 = (s14 + (1 << 20)) >> 21;
s15 += carry14;
s14 -= carry14 << 21;
carry16 = (s16 + (1 << 20)) >> 21;
s17 += carry16;
s16 -= carry16 << 21;
carry18 = (s18 + (1 << 20)) >> 21;
s19 += carry18;
s18 -= carry18 << 21;
carry20 = (s20 + (1 << 20)) >> 21;
s21 += carry20;
s20 -= carry20 << 21;
//carry22 = (s22 + (1<<20)) >> 21; s23 += carry22; s22 -= carry22 << 21;
carry22 = (s22 + (1 << 20)) >> 21;
s23 = carry22;
s22 -= carry22 << 21;
carry1 = (s1 + (1 << 20)) >> 21;
s2 += carry1;
s1 -= carry1 << 21;
carry3 = (s3 + (1 << 20)) >> 21;
s4 += carry3;
s3 -= carry3 << 21;
carry5 = (s5 + (1 << 20)) >> 21;
s6 += carry5;
s5 -= carry5 << 21;
carry7 = (s7 + (1 << 20)) >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry9 = (s9 + (1 << 20)) >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry11 = (s11 + (1 << 20)) >> 21;
s12 += carry11;
s11 -= carry11 << 21;
carry13 = (s13 + (1 << 20)) >> 21;
s14 += carry13;
s13 -= carry13 << 21;
carry15 = (s15 + (1 << 20)) >> 21;
s16 += carry15;
s15 -= carry15 << 21;
carry17 = (s17 + (1 << 20)) >> 21;
s18 += carry17;
s17 -= carry17 << 21;
carry19 = (s19 + (1 << 20)) >> 21;
s20 += carry19;
s19 -= carry19 << 21;
carry21 = (s21 + (1 << 20)) >> 21;
s22 += carry21;
s21 -= carry21 << 21;
s11 += s23 * 666643;
s12 += s23 * 470296;
s13 += s23 * 654183;
s14 -= s23 * 997805;
s15 += s23 * 136657;
s16 -= s23 * 683901;
// not used again
//s23 = 0;
s10 += s22 * 666643;
s11 += s22 * 470296;
s12 += s22 * 654183;
s13 -= s22 * 997805;
s14 += s22 * 136657;
s15 -= s22 * 683901;
// not used again
//s22 = 0;
s9 += s21 * 666643;
s10 += s21 * 470296;
s11 += s21 * 654183;
s12 -= s21 * 997805;
s13 += s21 * 136657;
s14 -= s21 * 683901;
// not used again
//s21 = 0;
s8 += s20 * 666643;
s9 += s20 * 470296;
s10 += s20 * 654183;
s11 -= s20 * 997805;
s12 += s20 * 136657;
s13 -= s20 * 683901;
// not used again
//s20 = 0;
s7 += s19 * 666643;
s8 += s19 * 470296;
s9 += s19 * 654183;
s10 -= s19 * 997805;
s11 += s19 * 136657;
s12 -= s19 * 683901;
// not used again
//s19 = 0;
s6 += s18 * 666643;
s7 += s18 * 470296;
s8 += s18 * 654183;
s9 -= s18 * 997805;
s10 += s18 * 136657;
s11 -= s18 * 683901;
// not used again
//s18 = 0;
carry6 = (s6 + (1 << 20)) >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry8 = (s8 + (1 << 20)) >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry10 = (s10 + (1 << 20)) >> 21;
s11 += carry10;
s10 -= carry10 << 21;
carry12 = (s12 + (1 << 20)) >> 21;
s13 += carry12;
s12 -= carry12 << 21;
carry14 = (s14 + (1 << 20)) >> 21;
s15 += carry14;
s14 -= carry14 << 21;
carry16 = (s16 + (1 << 20)) >> 21;
s17 += carry16;
s16 -= carry16 << 21;
carry7 = (s7 + (1 << 20)) >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry9 = (s9 + (1 << 20)) >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry11 = (s11 + (1 << 20)) >> 21;
s12 += carry11;
s11 -= carry11 << 21;
carry13 = (s13 + (1 << 20)) >> 21;
s14 += carry13;
s13 -= carry13 << 21;
carry15 = (s15 + (1 << 20)) >> 21;
s16 += carry15;
s15 -= carry15 << 21;
s5 += s17 * 666643;
s6 += s17 * 470296;
s7 += s17 * 654183;
s8 -= s17 * 997805;
s9 += s17 * 136657;
s10 -= s17 * 683901;
// not used again
//s17 = 0;
s4 += s16 * 666643;
s5 += s16 * 470296;
s6 += s16 * 654183;
s7 -= s16 * 997805;
s8 += s16 * 136657;
s9 -= s16 * 683901;
// not used again
//s16 = 0;
s3 += s15 * 666643;
s4 += s15 * 470296;
s5 += s15 * 654183;
s6 -= s15 * 997805;
s7 += s15 * 136657;
s8 -= s15 * 683901;
// not used again
//s15 = 0;
s2 += s14 * 666643;
s3 += s14 * 470296;
s4 += s14 * 654183;
s5 -= s14 * 997805;
s6 += s14 * 136657;
s7 -= s14 * 683901;
// not used again
//s14 = 0;
s1 += s13 * 666643;
s2 += s13 * 470296;
s3 += s13 * 654183;
s4 -= s13 * 997805;
s5 += s13 * 136657;
s6 -= s13 * 683901;
// not used again
//s13 = 0;
s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
// set below
//s12 = 0;
carry0 = (s0 + (1 << 20)) >> 21;
s1 += carry0;
s0 -= carry0 << 21;
carry2 = (s2 + (1 << 20)) >> 21;
s3 += carry2;
s2 -= carry2 << 21;
carry4 = (s4 + (1 << 20)) >> 21;
s5 += carry4;
s4 -= carry4 << 21;
carry6 = (s6 + (1 << 20)) >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry8 = (s8 + (1 << 20)) >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry10 = (s10 + (1 << 20)) >> 21;
s11 += carry10;
s10 -= carry10 << 21;
carry1 = (s1 + (1 << 20)) >> 21;
s2 += carry1;
s1 -= carry1 << 21;
carry3 = (s3 + (1 << 20)) >> 21;
s4 += carry3;
s3 -= carry3 << 21;
carry5 = (s5 + (1 << 20)) >> 21;
s6 += carry5;
s5 -= carry5 << 21;
carry7 = (s7 + (1 << 20)) >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry9 = (s9 + (1 << 20)) >> 21;
s10 += carry9;
s9 -= carry9 << 21;
//carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21;
carry11 = (s11 + (1 << 20)) >> 21;
s12 = carry11;
s11 -= carry11 << 21;
s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
// set below
//s12 = 0;
carry0 = s0 >> 21;
s1 += carry0;
s0 -= carry0 << 21;
carry1 = s1 >> 21;
s2 += carry1;
s1 -= carry1 << 21;
carry2 = s2 >> 21;
s3 += carry2;
s2 -= carry2 << 21;
carry3 = s3 >> 21;
s4 += carry3;
s3 -= carry3 << 21;
carry4 = s4 >> 21;
s5 += carry4;
s4 -= carry4 << 21;
carry5 = s5 >> 21;
s6 += carry5;
s5 -= carry5 << 21;
carry6 = s6 >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry7 = s7 >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry8 = s8 >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry9 = s9 >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry10 = s10 >> 21;
s11 += carry10;
s10 -= carry10 << 21;
//carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21;
carry11 = s11 >> 21;
s12 = carry11;
s11 -= carry11 << 21;
s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
// not used again
//s12 = 0;
carry0 = s0 >> 21;
s1 += carry0;
s0 -= carry0 << 21;
carry1 = s1 >> 21;
s2 += carry1;
s1 -= carry1 << 21;
carry2 = s2 >> 21;
s3 += carry2;
s2 -= carry2 << 21;
carry3 = s3 >> 21;
s4 += carry3;
s3 -= carry3 << 21;
carry4 = s4 >> 21;
s5 += carry4;
s4 -= carry4 << 21;
carry5 = s5 >> 21;
s6 += carry5;
s5 -= carry5 << 21;
carry6 = s6 >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry7 = s7 >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry8 = s8 >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry9 = s9 >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry10 = s10 >> 21;
s11 += carry10;
s10 -= carry10 << 21;
byte[] result = new byte[32];
result[0] = (byte) s0;
result[1] = (byte) (s0 >> 8);
result[2] = (byte) ((s0 >> 16) | (s1 << 5));
result[3] = (byte) (s1 >> 3);
result[4] = (byte) (s1 >> 11);
result[5] = (byte) ((s1 >> 19) | (s2 << 2));
result[6] = (byte) (s2 >> 6);
result[7] = (byte) ((s2 >> 14) | (s3 << 7));
result[8] = (byte) (s3 >> 1);
result[9] = (byte) (s3 >> 9);
result[10] = (byte) ((s3 >> 17) | (s4 << 4));
result[11] = (byte) (s4 >> 4);
result[12] = (byte) (s4 >> 12);
result[13] = (byte) ((s4 >> 20) | (s5 << 1));
result[14] = (byte) (s5 >> 7);
result[15] = (byte) ((s5 >> 15) | (s6 << 6));
result[16] = (byte) (s6 >> 2);
result[17] = (byte) (s6 >> 10);
result[18] = (byte) ((s6 >> 18) | (s7 << 3));
result[19] = (byte) (s7 >> 5);
result[20] = (byte) (s7 >> 13);
result[21] = (byte) s8;
result[22] = (byte) (s8 >> 8);
result[23] = (byte) ((s8 >> 16) | (s9 << 5));
result[24] = (byte) (s9 >> 3);
result[25] = (byte) (s9 >> 11);
result[26] = (byte) ((s9 >> 19) | (s10 << 2));
result[27] = (byte) (s10 >> 6);
result[28] = (byte) ((s10 >> 14) | (s11 << 7));
result[29] = (byte) (s11 >> 1);
result[30] = (byte) (s11 >> 9);
result[31] = (byte) (s11 >> 17);
return result;
}
}

View file

@ -1,4 +0,0 @@
/**
* Low-level, optimized implementation using Radix $2^{51}$ for Curve 25519.
*/
package org.xbib.io.sshd.eddsa.math.ed25519;

View file

@ -1,6 +0,0 @@
/**
* Data structures that definie curves and fields, and the mathematical operaions on them.
* Low-level implementation in bigint for any curve using BigIntegers,
* and in ed25519 for Curve 25519 using Radix $2^{51}$.
*/
package org.xbib.io.sshd.eddsa.math;

View file

@ -1,19 +0,0 @@
package org.xbib.io.sshd.eddsa.spec;
import java.security.spec.AlgorithmParameterSpec;
/**
* Implementation of AlgorithmParameterSpec that holds the name of a named
* EdDSA curve specification.
*/
public class EdDSAGenParameterSpec implements AlgorithmParameterSpec {
private final String name;
public EdDSAGenParameterSpec(String stdName) {
name = stdName;
}
public String getName() {
return name;
}
}

View file

@ -1,23 +0,0 @@
package org.xbib.io.sshd.eddsa.spec;
import org.xbib.io.sshd.eddsa.math.Curve;
import org.xbib.io.sshd.eddsa.math.GroupElement;
import org.xbib.io.sshd.eddsa.math.ScalarOps;
/**
* EdDSA Curve specification that can also be referred to by name.
*/
public class EdDSANamedCurveSpec extends EdDSAParameterSpec {
private final String name;
public EdDSANamedCurveSpec(String name, Curve curve,
String hashAlgo, ScalarOps sc, GroupElement B) {
super(curve, hashAlgo, sc, B);
this.name = name;
}
public String getName() {
return name;
}
}

View file

@ -1,56 +0,0 @@
package org.xbib.io.sshd.eddsa.spec;
import org.xbib.io.sshd.eddsa.Utils;
import org.xbib.io.sshd.eddsa.math.Curve;
import org.xbib.io.sshd.eddsa.math.Field;
import org.xbib.io.sshd.eddsa.math.ed25519.Ed25519LittleEndianEncoding;
import org.xbib.io.sshd.eddsa.math.ed25519.Ed25519ScalarOps;
import java.util.Hashtable;
import java.util.Locale;
/**
* The named EdDSA curves.
*/
public class EdDSANamedCurveTable {
private static final Field ed25519field = new Field(
256, // b
Utils.hexToBytes("edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), // q
new Ed25519LittleEndianEncoding());
private static final Curve ed25519curve = new Curve(ed25519field,
Utils.hexToBytes("a3785913ca4deb75abd841414d0a700098e879777940c78c73fe6f2bee6c0352"), // d
ed25519field.fromByteArray(Utils.hexToBytes("b0a00e4a271beec478e42fad0618432fa7d7fb3d99004d2b0bdfc14f8024832b"))); // I
private static final EdDSANamedCurveSpec ed25519 = new EdDSANamedCurveSpec(
"Ed25519",
ed25519curve,
"SHA-512", // H
new Ed25519ScalarOps(), // l
ed25519curve.createPoint( // B
Utils.hexToBytes("5866666666666666666666666666666666666666666666666666666666666666"),
true)); // Precompute tables for B
private static final Hashtable<String, EdDSANamedCurveSpec> curves = new Hashtable<String, EdDSANamedCurveSpec>();
static {
// RFC 8032
defineCurve(ed25519);
}
public static void defineCurve(EdDSANamedCurveSpec curve) {
curves.put(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();
}
curves.put(alias.toLowerCase(Locale.ENGLISH), curve);
}
public static EdDSANamedCurveSpec getByName(String name) {
return curves.get(name.toLowerCase(Locale.ENGLISH));
}
}

View file

@ -1,84 +0,0 @@
package org.xbib.io.sshd.eddsa.spec;
import org.xbib.io.sshd.eddsa.math.Curve;
import org.xbib.io.sshd.eddsa.math.GroupElement;
import org.xbib.io.sshd.eddsa.math.ScalarOps;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
//import java.io.Serializable;
/**
* Parameter specification for an EdDSA algorithm.
*/
public class EdDSAParameterSpec implements AlgorithmParameterSpec/*, Serializable*/ {
// private static final long serialVersionUID = 8274987108472012L;
private final Curve curve;
private final String hashAlgo;
private final ScalarOps sc;
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,
ScalarOps sc, GroupElement B) {
try {
MessageDigest hash = MessageDigest.getInstance(hashAlgo);
// EdDSA hash function must produce 2b-bit output
if (curve.getField().getb() / 4 != hash.getDigestLength())
throw new IllegalArgumentException("Hash output is not 2b-bit");
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("Unsupported hash algorithm");
}
this.curve = curve;
this.hashAlgo = hashAlgo;
this.sc = sc;
this.B = B;
}
public Curve getCurve() {
return curve;
}
public String getHashAlgorithm() {
return hashAlgo;
}
public ScalarOps getScalarOps() {
return sc;
}
/**
* @return the base (generator)
*/
public GroupElement getB() {
return B;
}
@Override
public int hashCode() {
return hashAlgo.hashCode() ^
curve.hashCode() ^
B.hashCode();
}
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof EdDSAParameterSpec))
return false;
EdDSAParameterSpec s = (EdDSAParameterSpec) o;
return hashAlgo.equals(s.getHashAlgorithm()) &&
curve.equals(s.getCurve()) &&
B.equals(s.getB());
}
}

View file

@ -1,120 +0,0 @@
package org.xbib.io.sshd.eddsa.spec;
import org.xbib.io.sshd.eddsa.math.GroupElement;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.KeySpec;
import java.util.Arrays;
/**
*
*/
public class EdDSAPrivateKeySpec implements KeySpec {
private final byte[] seed;
private final byte[] h;
private final byte[] a;
private final GroupElement A;
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) {
if (seed.length != spec.getCurve().getField().getb() / 8)
throw new IllegalArgumentException("seed length is wrong");
this.spec = spec;
this.seed = seed;
try {
MessageDigest hash = MessageDigest.getInstance(spec.getHashAlgorithm());
int b = spec.getCurve().getField().getb();
// H(k)
h = hash.digest(seed);
/*a = BigInteger.valueOf(2).pow(b-2);
for (int i=3;i<(b-2);i++) {
a = a.add(BigInteger.valueOf(2).pow(i).multiply(BigInteger.valueOf(Utils.bit(h,i))));
}*/
// Saves ~0.4ms per key when running signing tests.
// TODO: are these bitflips the same for any hash function?
h[0] &= 248;
h[(b / 8) - 1] &= 63;
h[(b / 8) - 1] |= 64;
a = Arrays.copyOfRange(h, 0, b / 8);
A = spec.getB().scalarMultiply(a);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("Unsupported hash algorithm");
}
}
/**
* 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
*/
public EdDSAPrivateKeySpec(EdDSAParameterSpec spec, byte[] h) {
if (h.length != spec.getCurve().getField().getb() / 4)
throw new IllegalArgumentException("hash length is wrong");
this.seed = null;
this.h = h;
this.spec = spec;
int b = spec.getCurve().getField().getb();
h[0] &= 248;
h[(b / 8) - 1] &= 63;
h[(b / 8) - 1] |= 64;
a = Arrays.copyOfRange(h, 0, b / 8);
A = spec.getB().scalarMultiply(a);
}
public EdDSAPrivateKeySpec(byte[] seed, byte[] h, byte[] a, GroupElement A, EdDSAParameterSpec spec) {
this.seed = seed;
this.h = h;
this.a = a;
this.A = A;
this.spec = spec;
}
/**
* @return will be null if constructed directly from the private key
*/
public byte[] getSeed() {
return seed;
}
/**
* @return the hash
*/
public byte[] getH() {
return h;
}
/**
* @return the private key
*/
public byte[] geta() {
return a;
}
/**
* @return the public key
*/
public GroupElement getA() {
return A;
}
public EdDSAParameterSpec getParams() {
return spec;
}
}

View file

@ -1,49 +0,0 @@
package org.xbib.io.sshd.eddsa.spec;
import org.xbib.io.sshd.eddsa.math.GroupElement;
import java.security.spec.KeySpec;
/**
*
*/
public class EdDSAPublicKeySpec implements KeySpec {
private final GroupElement A;
private final 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) {
if (pk.length != spec.getCurve().getField().getb() / 8)
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;
}
public GroupElement getA() {
return A;
}
public GroupElement getNegativeA() {
return Aneg;
}
public EdDSAParameterSpec getParams() {
return spec;
}
}

View file

@ -1,5 +0,0 @@
/**
* Specifications for curves and keys, and a table for named curves.
* Contains the following curves: Ed25519
*/
package org.xbib.io.sshd.eddsa.spec;

View file

@ -1,54 +0,0 @@
package org.xbib.io.sshd.eddsa;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
*
*/
public class Ed25519TestVectors {
public static class TestTuple {
public static int numCases;
public int caseNum;
public byte[] seed;
public byte[] pk;
public byte[] message;
public byte[] sig;
public TestTuple(String line) {
caseNum = ++numCases;
String[] x = line.split(":");
seed = Utils.hexToBytes(x[0].substring(0, 64));
pk = Utils.hexToBytes(x[1]);
message = Utils.hexToBytes(x[2]);
sig = Utils.hexToBytes(x[3].substring(0, 128));
}
}
public static Collection<TestTuple> testCases = getTestData("test.data");
public static Collection<TestTuple> getTestData(String fileName) {
List<TestTuple> testCases = new ArrayList<TestTuple>();
BufferedReader file = null;
try {
InputStream is = Ed25519TestVectors.class.getResourceAsStream(fileName);
if (is == null)
throw new IOException("Resource not found: " + fileName);
file = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = file.readLine()) != null) {
testCases.add(new TestTuple(line));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (file != null) try { file.close(); } catch (IOException e) {}
}
return testCases;
}
}

View file

@ -1,204 +0,0 @@
package org.xbib.io.sshd.eddsa;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveTable;
import org.xbib.io.sshd.eddsa.spec.EdDSAParameterSpec;
import org.xbib.io.sshd.eddsa.spec.EdDSAPrivateKeySpec;
import org.xbib.io.sshd.eddsa.spec.EdDSAPublicKeySpec;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/**
*
*/
public class EdDSAEngineTest {
static final byte[] TEST_SEED = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000");
static final byte[] TEST_PK = Utils.hexToBytes("3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29");
static final byte[] TEST_MSG = "This is a secret message".getBytes(Charset.forName("UTF-8"));
static final byte[] TEST_MSG_SIG = Utils.hexToBytes("94825896c7075c31bcb81f06dba2bdcd9dcf16e79288d4b9f87c248215c8468d475f429f3de3b4a2cf67fe17077ae19686020364d6d4fa7a0174bab4a123ba0f");
@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void testSign() throws Exception {
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519");
//Signature sgr = Signature.getInstance("EdDSA", "I2P");
Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
for (Ed25519TestVectors.TestTuple testCase : Ed25519TestVectors.testCases) {
EdDSAPrivateKeySpec privKey = new EdDSAPrivateKeySpec(testCase.seed, spec);
PrivateKey sKey = new EdDSAPrivateKey(privKey);
sgr.initSign(sKey);
sgr.update(testCase.message);
assertThat("Test case " + testCase.caseNum + " failed",
sgr.sign(), is(equalTo(testCase.sig)));
}
}
@Test
public void testVerify() throws Exception {
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519");
//Signature sgr = Signature.getInstance("EdDSA", "I2P");
Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
for (Ed25519TestVectors.TestTuple testCase : Ed25519TestVectors.testCases) {
EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(testCase.pk, spec);
PublicKey vKey = new EdDSAPublicKey(pubKey);
sgr.initVerify(vKey);
sgr.update(testCase.message);
assertThat("Test case " + testCase.caseNum + " failed",
sgr.verify(testCase.sig), is(true));
}
}
/**
* Checks that a wrong-length signature throws an IAE.
*/
@Test
public void testVerifyWrongSigLength() throws Exception {
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519");
//Signature sgr = Signature.getInstance("EdDSA", "I2P");
Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(TEST_PK, spec);
PublicKey vKey = new EdDSAPublicKey(pubKey);
sgr.initVerify(vKey);
sgr.update(TEST_MSG);
exception.expect(SignatureException.class);
exception.expectMessage("signature length is wrong");
sgr.verify(new byte[] {0});
}
@Test
public void testSignResetsForReuse() throws Exception {
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519");
Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
EdDSAPrivateKeySpec privKey = new EdDSAPrivateKeySpec(TEST_SEED, spec);
PrivateKey sKey = new EdDSAPrivateKey(privKey);
sgr.initSign(sKey);
// First usage
sgr.update(new byte[] {0});
sgr.sign();
// Second usage
sgr.update(TEST_MSG);
assertThat("Second sign failed", sgr.sign(), is(equalTo(TEST_MSG_SIG)));
}
@Test
public void testVerifyResetsForReuse() throws Exception {
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519");
Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(TEST_PK, spec);
PublicKey vKey = new EdDSAPublicKey(pubKey);
sgr.initVerify(vKey);
// First usage
sgr.update(new byte[] {0});
sgr.verify(TEST_MSG_SIG);
// Second usage
sgr.update(TEST_MSG);
assertThat("Second verify failed", sgr.verify(TEST_MSG_SIG), is(true));
}
@Test
public void testSignOneShotMode() throws Exception {
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519");
Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
EdDSAPrivateKeySpec privKey = new EdDSAPrivateKeySpec(TEST_SEED, spec);
PrivateKey sKey = new EdDSAPrivateKey(privKey);
sgr.initSign(sKey);
sgr.setParameter(EdDSAEngine.ONE_SHOT_MODE);
sgr.update(TEST_MSG);
assertThat("One-shot mode sign failed", sgr.sign(), is(equalTo(TEST_MSG_SIG)));
}
@Test
public void testVerifyOneShotMode() throws Exception {
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519");
Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(TEST_PK, spec);
PublicKey vKey = new EdDSAPublicKey(pubKey);
sgr.initVerify(vKey);
sgr.setParameter(EdDSAEngine.ONE_SHOT_MODE);
sgr.update(TEST_MSG);
assertThat("One-shot mode verify failed", sgr.verify(TEST_MSG_SIG), is(true));
}
@Test
public void testSignOneShotModeMultipleUpdates() throws Exception {
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519");
Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
EdDSAPrivateKeySpec privKey = new EdDSAPrivateKeySpec(TEST_SEED, spec);
PrivateKey sKey = new EdDSAPrivateKey(privKey);
sgr.initSign(sKey);
sgr.setParameter(EdDSAEngine.ONE_SHOT_MODE);
sgr.update(TEST_MSG);
exception.expect(SignatureException.class);
exception.expectMessage("update() already called");
sgr.update(TEST_MSG);
}
@Test
public void testVerifyOneShotModeMultipleUpdates() throws Exception {
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519");
EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(TEST_PK, spec);
Signature sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
PublicKey vKey = new EdDSAPublicKey(pubKey);
sgr.initVerify(vKey);
sgr.setParameter(EdDSAEngine.ONE_SHOT_MODE);
sgr.update(TEST_MSG);
exception.expect(SignatureException.class);
exception.expectMessage("update() already called");
sgr.update(TEST_MSG);
}
@Test
public void testSignOneShot() throws Exception {
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519");
EdDSAPrivateKeySpec privKey = new EdDSAPrivateKeySpec(TEST_SEED, spec);
EdDSAEngine sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
PrivateKey sKey = new EdDSAPrivateKey(privKey);
sgr.initSign(sKey);
assertThat("signOneShot() failed", sgr.signOneShot(TEST_MSG), is(equalTo(TEST_MSG_SIG)));
}
@Test
public void testVerifyOneShot() throws Exception {
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519");
EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(TEST_PK, spec);
EdDSAEngine sgr = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
PublicKey vKey = new EdDSAPublicKey(pubKey);
sgr.initVerify(vKey);
assertThat("verifyOneShot() failed", sgr.verifyOneShot(TEST_MSG, TEST_MSG_SIG), is(true));
}
}

View file

@ -1,81 +0,0 @@
package org.xbib.io.sshd.eddsa;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.security.spec.PKCS8EncodedKeySpec;
import org.xbib.io.sshd.eddsa.spec.EdDSAPrivateKeySpec;
import org.junit.Test;
/**
*
*/
public class EdDSAPrivateKeyTest {
/**
* The example private key MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
* from https://tools.ietf.org/html/draft-ietf-curdle-pkix-04#section-10.3
*/
static final byte[] TEST_PRIVKEY = Utils.hexToBytes("302e020100300506032b657004220420d4ee72dbf913584ad5b6d8f1f769f8ad3afe7c28cbf1d4fbe097a88f44755842");
static final byte[] TEST_PRIVKEY_NULL_PARAMS = Utils.hexToBytes("3030020100300706032b6570050004220420d4ee72dbf913584ad5b6d8f1f769f8ad3afe7c28cbf1d4fbe097a88f44755842");
static final byte[] TEST_PRIVKEY_OLD = Utils.hexToBytes("302f020100300806032b65640a01010420d4ee72dbf913584ad5b6d8f1f769f8ad3afe7c28cbf1d4fbe097a88f44755842");
@Test
public void testDecodeAndEncode() throws Exception {
// Decode
PKCS8EncodedKeySpec encoded = new PKCS8EncodedKeySpec(TEST_PRIVKEY);
EdDSAPrivateKey keyIn = new EdDSAPrivateKey(encoded);
// Encode
EdDSAPrivateKeySpec decoded = new EdDSAPrivateKeySpec(
keyIn.getSeed(),
keyIn.getH(),
keyIn.geta(),
keyIn.getA(),
keyIn.getParams());
EdDSAPrivateKey keyOut = new EdDSAPrivateKey(decoded);
// Check
assertThat(keyOut.getEncoded(), is(equalTo(TEST_PRIVKEY)));
}
@Test
public void testDecodeWithNullAndEncode() throws Exception {
// Decode
PKCS8EncodedKeySpec encoded = new PKCS8EncodedKeySpec(TEST_PRIVKEY_NULL_PARAMS);
EdDSAPrivateKey keyIn = new EdDSAPrivateKey(encoded);
// Encode
EdDSAPrivateKeySpec decoded = new EdDSAPrivateKeySpec(
keyIn.getSeed(),
keyIn.getH(),
keyIn.geta(),
keyIn.getA(),
keyIn.getParams());
EdDSAPrivateKey keyOut = new EdDSAPrivateKey(decoded);
// Check
assertThat(keyOut.getEncoded(), is(equalTo(TEST_PRIVKEY)));
}
@Test
public void testReEncodeOldEncoding() throws Exception {
// Decode
PKCS8EncodedKeySpec encoded = new PKCS8EncodedKeySpec(TEST_PRIVKEY_OLD);
EdDSAPrivateKey keyIn = new EdDSAPrivateKey(encoded);
// Encode
EdDSAPrivateKeySpec decoded = new EdDSAPrivateKeySpec(
keyIn.getSeed(),
keyIn.getH(),
keyIn.geta(),
keyIn.getA(),
keyIn.getParams());
EdDSAPrivateKey keyOut = new EdDSAPrivateKey(decoded);
// Check
assertThat(keyOut.getEncoded(), is(equalTo(TEST_PRIVKEY)));
}
}

View file

@ -1,72 +0,0 @@
package org.xbib.io.sshd.eddsa;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.security.spec.X509EncodedKeySpec;
import org.xbib.io.sshd.eddsa.spec.EdDSAPublicKeySpec;
import org.junit.Test;
/**
*
*/
public class EdDSAPublicKeyTest {
/**
* The example public key MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=
* from https://tools.ietf.org/html/draft-ietf-curdle-pkix-04#section-10.1
*/
static final byte[] TEST_PUBKEY = Utils.hexToBytes("302a300506032b657003210019bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1");
static final byte[] TEST_PUBKEY_NULL_PARAMS = Utils.hexToBytes("302c300706032b6570050003210019bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1");
static final byte[] TEST_PUBKEY_OLD = Utils.hexToBytes("302d300806032b65640a010103210019bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1");
@Test
public void testDecodeAndEncode() throws Exception {
// Decode
X509EncodedKeySpec encoded = new X509EncodedKeySpec(TEST_PUBKEY);
EdDSAPublicKey keyIn = new EdDSAPublicKey(encoded);
// Encode
EdDSAPublicKeySpec decoded = new EdDSAPublicKeySpec(
keyIn.getA(),
keyIn.getParams());
EdDSAPublicKey keyOut = new EdDSAPublicKey(decoded);
// Check
assertThat(keyOut.getEncoded(), is(equalTo(TEST_PUBKEY)));
}
@Test
public void testDecodeWithNullAndEncode() throws Exception {
// Decode
X509EncodedKeySpec encoded = new X509EncodedKeySpec(TEST_PUBKEY_NULL_PARAMS);
EdDSAPublicKey keyIn = new EdDSAPublicKey(encoded);
// Encode
EdDSAPublicKeySpec decoded = new EdDSAPublicKeySpec(
keyIn.getA(),
keyIn.getParams());
EdDSAPublicKey keyOut = new EdDSAPublicKey(decoded);
// Check
assertThat(keyOut.getEncoded(), is(equalTo(TEST_PUBKEY)));
}
@Test
public void testReEncodeOldEncoding() throws Exception {
// Decode
X509EncodedKeySpec encoded = new X509EncodedKeySpec(TEST_PUBKEY_OLD);
EdDSAPublicKey keyIn = new EdDSAPublicKey(encoded);
// Encode
EdDSAPublicKeySpec decoded = new EdDSAPublicKeySpec(
keyIn.getA(),
keyIn.getParams());
EdDSAPublicKey keyOut = new EdDSAPublicKey(decoded);
// Check
assertThat(keyOut.getEncoded(), is(equalTo(TEST_PUBKEY)));
}
}

View file

@ -1,37 +0,0 @@
package org.xbib.io.sshd.eddsa;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.Signature;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/**
*
*/
public class EdDSASecurityProviderTest {
@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void canGetInstancesWhenProviderIsPresent() throws Exception {
Security.addProvider(new EdDSASecurityProvider());
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EdDSA", "EdDSA");
KeyFactory keyFac = KeyFactory.getInstance("EdDSA", "EdDSA");
Signature sgr = Signature.getInstance("NONEwithEdDSA", "EdDSA");
Security.removeProvider("EdDSA");
}
@Test
public void cannotGetInstancesWhenProviderIsNotPresent() throws Exception {
exception.expect(NoSuchProviderException.class);
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EdDSA", "EdDSA");
}
}

View file

@ -1,121 +0,0 @@
package org.xbib.io.sshd.eddsa;
import org.hamcrest.core.IsEqual;
import org.junit.Assert;
import org.junit.Test;
import java.security.SecureRandom;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
/**
* additional test by the NEM project team.
*
*/
public class UtilsTest {
private static final String hex1 = "3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29";
private static final String hex2 = "47a3f5b71494bcd961f3a4e859a238d6eaf8e648746d2f56a89b5e236f98d45f";
private static final String hex3 = "5fd396e4a2b5dc9078f57e3ab5a87c28fd128e5f78cc4a97f4122dc45f6e4bb9";
private static final byte[] bytes1 = { 59, 106, 39, -68, -50, -74, -92, 45, 98, -93, -88, -48, 42, 111, 13, 115,
101, 50, 21, 119, 29, -30, 67, -90, 58, -64, 72, -95, -117, 89, -38, 41 };
private static final byte[] bytes2 = { 71, -93, -11, -73, 20, -108, -68, -39, 97, -13, -92, -24, 89, -94, 56, -42,
-22, -8, -26, 72, 116, 109, 47, 86, -88, -101, 94, 35, 111, -104, -44, 95 };
private static final byte[] bytes3 = { 95, -45, -106, -28, -94, -75, -36, -112, 120, -11, 126, 58, -75, -88, 124, 40,
-3, 18, -114, 95, 120, -52, 74, -105, -12, 18, 45, -60, 95, 110, 75, -71 };
/**
* Test method for {@link Utils#equal(int, int)}.
*/
@Test
public void testIntEqual() {
assertThat(Utils.equal(0, 0), is(1));
assertThat(Utils.equal(1, 1), is(1));
assertThat(Utils.equal(1, 0), is(0));
assertThat(Utils.equal(1, 127), is(0));
assertThat(Utils.equal(-127, 127), is(0));
assertThat(Utils.equal(-42, -42), is(1));
assertThat(Utils.equal(255, 255), is(1));
assertThat(Utils.equal(-255, -256), is(0));
}
@Test
public void equalsReturnsOneForEqualByteArrays() {
final SecureRandom random = new SecureRandom();
final byte[] bytes1 = new byte[32];
final byte[] bytes2 = new byte[32];
for (int i=0; i<100; i++) {
random.nextBytes(bytes1);
System.arraycopy(bytes1, 0, bytes2, 0, 32);
Assert.assertThat(Utils.equal(bytes1, bytes2), IsEqual.equalTo(1));
}
}
@Test
public void equalsReturnsZeroForUnequalByteArrays() {
final SecureRandom random = new SecureRandom();
final byte[] bytes1 = new byte[32];
final byte[] bytes2 = new byte[32];
random.nextBytes(bytes1);
for (int i=0; i<32; i++) {
System.arraycopy(bytes1, 0, bytes2, 0, 32);
bytes2[i] = (byte)(bytes2[i] ^ 0xff);
Assert.assertThat(Utils.equal(bytes1, bytes2), IsEqual.equalTo(0));
}
}
/**
* Test method for {@link Utils#equal(byte[], byte[])}.
*/
@Test
public void testByteArrayEqual() {
byte[] zero = new byte[32];
byte[] one = new byte[32];
one[0] = 1;
assertThat(Utils.equal(zero, zero), is(1));
assertThat(Utils.equal(one, one), is(1));
assertThat(Utils.equal(one, zero), is(0));
assertThat(Utils.equal(zero, one), is(0));
}
/**
* Test method for {@link Utils#negative(int)}.
*/
@Test
public void testNegative() {
assertThat(Utils.negative(0), is(0));
assertThat(Utils.negative(1), is(0));
assertThat(Utils.negative(-1), is(1));
assertThat(Utils.negative(32), is(0));
assertThat(Utils.negative(-100), is(1));
assertThat(Utils.negative(127), is(0));
assertThat(Utils.negative(-255), is(1));
}
/**
* Test method for {@link Utils#bit(byte[], int)}.
*/
@Test
public void testBit() {
assertThat(Utils.bit(new byte[] {0}, 0), is(0));
assertThat(Utils.bit(new byte[] {8}, 3), is(1));
assertThat(Utils.bit(new byte[] {1, 2, 3}, 9), is(1));
assertThat(Utils.bit(new byte[] {1, 2, 3}, 15), is(0));
assertThat(Utils.bit(new byte[] {1, 2, 3}, 16), is(1));
}
@Test
public void hexToBytesReturnsCorrectByteArray() {
Assert.assertThat(Utils.hexToBytes(hex1), IsEqual.equalTo(bytes1));
Assert.assertThat(Utils.hexToBytes(hex2), IsEqual.equalTo(bytes2));
Assert.assertThat(Utils.hexToBytes(hex3), IsEqual.equalTo(bytes3));
}
@Test
public void bytesToHexReturnsCorrectHexString() {
Assert.assertThat(Utils.bytesToHex(bytes1), IsEqual.equalTo(hex1));
Assert.assertThat(Utils.bytesToHex(bytes2), IsEqual.equalTo(hex2));
Assert.assertThat(Utils.bytesToHex(bytes3), IsEqual.equalTo(hex3));
}
}

View file

@ -1,183 +0,0 @@
package org.xbib.io.sshd.eddsa.math;
import org.hamcrest.core.IsEqual;
import org.hamcrest.core.IsNot;
import org.junit.Assert;
import org.junit.Test;
import java.math.BigInteger;
/**
* Tests rely on the BigInteger class.
*/
public abstract class AbstractFieldElementTest {
protected abstract FieldElement getRandomFieldElement();
protected abstract BigInteger toBigInteger(FieldElement f);
protected abstract BigInteger getQ();
protected abstract Field getField();
protected abstract FieldElement getZeroFieldElement();
protected abstract FieldElement getNonZeroFieldElement();
@Test
public void isNonZeroReturnsFalseIfFieldElementIsZero() {
final FieldElement f = getZeroFieldElement();
Assert.assertThat(f.isNonZero(), IsEqual.equalTo(false));
}
@Test
public void isNonZeroReturnsTrueIfFieldElementIsNonZero() {
final FieldElement f = getNonZeroFieldElement();
Assert.assertThat(f.isNonZero(), IsEqual.equalTo(true));
}
@Test
public void addReturnsCorrectResult() {
for (int i=0; i<1000; i++) {
final FieldElement f1 = getRandomFieldElement();
final FieldElement f2 = getRandomFieldElement();
final BigInteger b1 = toBigInteger(f1);
final BigInteger b2 = toBigInteger(f2);
final FieldElement f3 = f1.add(f2);
final BigInteger b3 = toBigInteger(f3).mod(getQ());
Assert.assertThat(b3, IsEqual.equalTo(b1.add(b2).mod(getQ())));
}
}
@Test
public void subtractReturnsCorrectResult() {
for (int i=0; i<1000; i++) {
final FieldElement f1 = getRandomFieldElement();
final FieldElement f2 = getRandomFieldElement();
final BigInteger b1 = toBigInteger(f1);
final BigInteger b2 = toBigInteger(f2);
final FieldElement f3 = f1.subtract(f2);
final BigInteger b3 = toBigInteger(f3).mod(getQ());
Assert.assertThat(b3, IsEqual.equalTo(b1.subtract(b2).mod(getQ())));
}
}
@Test
public void negateReturnsCorrectResult() {
for (int i=0; i<1000; i++) {
final FieldElement f1 = getRandomFieldElement();
final BigInteger b1 = toBigInteger(f1);
final FieldElement f2 = f1.negate();
final BigInteger b2 = toBigInteger(f2).mod(getQ());
Assert.assertThat(b2, IsEqual.equalTo(b1.negate().mod(getQ())));
}
}
@Test
public void multiplyReturnsCorrectResult() {
for (int i=0; i<1000; i++) {
final FieldElement f1 = getRandomFieldElement();
final FieldElement f2 = getRandomFieldElement();
final BigInteger b1 = toBigInteger(f1);
final BigInteger b2 = toBigInteger(f2);
final FieldElement f3 = f1.multiply(f2);
final BigInteger b3 = toBigInteger(f3).mod(getQ());
Assert.assertThat(b3, IsEqual.equalTo(b1.multiply(b2).mod(getQ())));
}
}
@Test
public void squareReturnsCorrectResult() {
for (int i=0; i<1000; i++) {
final FieldElement f1 = getRandomFieldElement();
final BigInteger b1 = toBigInteger(f1);
final FieldElement f2 = f1.square();
final BigInteger b2 = toBigInteger(f2).mod(getQ());
Assert.assertThat(b2, IsEqual.equalTo(b1.multiply(b1).mod(getQ())));
}
}
@Test
public void squareAndDoubleReturnsCorrectResult() {
for (int i=0; i<1000; i++) {
final FieldElement f1 = getRandomFieldElement();
final BigInteger b1 = toBigInteger(f1);
final FieldElement f2 = f1.squareAndDouble();
final BigInteger b2 = toBigInteger(f2).mod(getQ());
Assert.assertThat(b2, IsEqual.equalTo(b1.multiply(b1).multiply(new BigInteger("2")).mod(getQ())));
}
}
@Test
public void invertReturnsCorrectResult() {
for (int i=0; i<1000; i++) {
final FieldElement f1 = getRandomFieldElement();
final BigInteger b1 = toBigInteger(f1);
final FieldElement f2 = f1.invert();
final BigInteger b2 = toBigInteger(f2).mod(getQ());
Assert.assertThat(b2, IsEqual.equalTo(b1.modInverse(getQ())));
}
}
@Test
public void pow22523ReturnsCorrectResult() {
for (int i=0; i<1000; i++) {
final FieldElement f1 = getRandomFieldElement();
final BigInteger b1 = toBigInteger(f1);
final FieldElement f2 = f1.pow22523();
final BigInteger b2 = toBigInteger(f2).mod(getQ());
Assert.assertThat(b2, IsEqual.equalTo(b1.modPow(BigInteger.ONE.shiftLeft(252).subtract(new BigInteger("3")), getQ())));
}
}
@Test
public void cmovReturnsCorrectResult() {
final FieldElement zero = getZeroFieldElement();
final FieldElement nz = getNonZeroFieldElement();
final FieldElement f = getRandomFieldElement();
Assert.assertThat(zero.cmov(nz, 0), IsEqual.equalTo(zero));
Assert.assertThat(zero.cmov(nz, 1), IsEqual.equalTo(nz));
Assert.assertThat(f.cmov(nz, 0), IsEqual.equalTo(f));
Assert.assertThat(f.cmov(nz, 1), IsEqual.equalTo(nz));
}
@Test
public void equalsOnlyReturnsTrueForEquivalentObjects() {
final FieldElement f1 = getRandomFieldElement();
final FieldElement f2 = getField().getEncoding().decode(f1.toByteArray());
final FieldElement f3 = getRandomFieldElement();
final FieldElement f4 = getRandomFieldElement();
Assert.assertThat(f1, IsEqual.equalTo(f2));
Assert.assertThat(f1, IsNot.not(IsEqual.equalTo(f3)));
Assert.assertThat(f1, IsNot.not(IsEqual.equalTo(f4)));
Assert.assertThat(f3, IsNot.not(IsEqual.equalTo(f4)));
}
@Test
public void hashCodesAreEqualForEquivalentObjects() {
final FieldElement f1 = getRandomFieldElement();
final FieldElement f2 = getField().getEncoding().decode(f1.toByteArray());
final FieldElement f3 = getRandomFieldElement();
final FieldElement f4 = getRandomFieldElement();
Assert.assertThat(f1.hashCode(), IsEqual.equalTo(f2.hashCode()));
Assert.assertThat(f1.hashCode(), IsNot.not(IsEqual.equalTo(f3.hashCode())));
Assert.assertThat(f1.hashCode(), IsNot.not(IsEqual.equalTo(f4.hashCode())));
Assert.assertThat(f3.hashCode(), IsNot.not(IsEqual.equalTo(f4.hashCode())));
}
}

View file

@ -1,78 +0,0 @@
package org.xbib.io.sshd.eddsa.math;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveSpec;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveTable;
import org.junit.Test;
/**
* Based on the tests in checkparams.py from the Python Ed25519 implementation.
*
*/
public class ConstantsTest {
static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
static final Curve curve = ed25519.getCurve();
static final FieldElement ZERO = curve.getField().ZERO;
static final FieldElement ONE = curve.getField().ONE;
static final FieldElement TWO = curve.getField().TWO;
static final GroupElement P3_ZERO = GroupElement.p3(curve, ZERO, ONE, ONE, ZERO);
@Test
public void testb() {
int b = curve.getField().getb();
assertThat(b, is(greaterThanOrEqualTo(10)));
try {
MessageDigest h = MessageDigest.getInstance(ed25519.getHashAlgorithm());
assertThat(8 * h.getDigestLength(), is(equalTo(2 * b)));
} catch (NoSuchAlgorithmException e) {
fail(e.getMessage());
}
}
/*@Test
public void testq() {
FieldElement q = curve.getField().getQ();
assertThat(TWO.modPow(q.subtractOne(), q), is(equalTo(ONE)));
assertThat(q.mod(curve.getField().FOUR), is(equalTo(ONE)));
}
@Test
public void testl() {
int b = curve.getField().getb();
BigInteger l = ed25519.getL();
assertThat(TWO.modPow(l.subtract(BigInteger.ONE), l), is(equalTo(ONE)));
assertThat(l, is(greaterThanOrEqualTo(BigInteger.valueOf(2).pow(b-4))));
assertThat(l, is(lessThanOrEqualTo(BigInteger.valueOf(2).pow(b-3))));
}
@Test
public void testd() {
FieldElement q = curve.getField().getQ();
FieldElement qm1 = q.subtractOne();
assertThat(curve.getD().modPow(qm1.divide(curve.getField().TWO), q), is(equalTo(qm1)));
}
@Test
public void testI() {
FieldElement q = curve.getField().getQ();
assertThat(curve.getI().modPow(curve.getField().TWO, q), is(equalTo(q.subtractOne())));
}*/
@Test
public void testB() {
GroupElement B = ed25519.getB();
assertThat(B.isOnCurve(curve), is(true));
//assertThat(B.scalarMultiply(new BigIntegerLittleEndianEncoding().encode(ed25519.getL(), curve.getField().getb()/8)), is(equalTo(P3_ZERO)));
}
}

View file

@ -1,865 +0,0 @@
package org.xbib.io.sshd.eddsa.math;
import org.xbib.io.sshd.eddsa.Ed25519TestVectors;
import org.xbib.io.sshd.eddsa.Utils;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveSpec;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveTable;
import org.hamcrest.core.IsEqual;
import org.hamcrest.core.IsNot;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.math.BigInteger;
import java.util.Arrays;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
/**
* Additional tests by NEM project team.
*
*/
public class GroupElementTest {
static final byte[] BYTES_ZEROZERO = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000");
static final byte[] BYTES_ONEONE = Utils.hexToBytes("0100000000000000000000000000000000000000000000000000000000000080");
static final byte[] BYTES_TENZERO = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000");
static final byte[] BYTES_ONETEN = Utils.hexToBytes("0a00000000000000000000000000000000000000000000000000000000000080");
static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
static final Curve curve = ed25519.getCurve();
static final FieldElement ZERO = curve.getField().ZERO;
static final FieldElement ONE = curve.getField().ONE;
static final FieldElement TWO = curve.getField().TWO;
static final FieldElement TEN = curve.getField().fromByteArray(Utils.hexToBytes("0a00000000000000000000000000000000000000000000000000000000000000"));
static final GroupElement P2_ZERO = GroupElement.p2(curve, ZERO, ONE, ONE);
static final FieldElement[] PKR = new FieldElement[] {
curve.getField().fromByteArray(Utils.hexToBytes("5849722e338aced7b50c7f0e9328f9a10c847b08e40af5c5b0577b0fd8984f15")),
curve.getField().fromByteArray(Utils.hexToBytes("3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29"))
};
static final byte[] BYTES_PKR = Utils.hexToBytes("3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29");
@Rule
public ExpectedException exception = ExpectedException.none();
/**
* Test method for {@link GroupElement#p2(Curve, FieldElement, FieldElement, FieldElement)}.
*/
@Test
public void testP2() {
final GroupElement t = GroupElement.p2(curve, ZERO, ONE, ONE);
assertThat(t.curve, is(equalTo(curve)));
assertThat(t.repr, is(GroupElement.Representation.P2));
assertThat(t.X, is(ZERO));
assertThat(t.Y, is(ONE));
assertThat(t.Z, is(ONE));
assertThat(t.T, is((FieldElement) null));
}
/**
* Test method for {@link GroupElement#p3(Curve, FieldElement, FieldElement, FieldElement, FieldElement)}.
*/
@Test
public void testP3() {
final GroupElement t = GroupElement.p3(curve, ZERO, ONE, ONE, ZERO);
assertThat(t.curve, is(equalTo(curve)));
assertThat(t.repr, is(GroupElement.Representation.P3));
assertThat(t.X, is(ZERO));
assertThat(t.Y, is(ONE));
assertThat(t.Z, is(ONE));
assertThat(t.T, is(ZERO));
}
/**
* Test method for {@link GroupElement#p1p1(Curve, FieldElement, FieldElement, FieldElement, FieldElement)}.
*/
@Test
public void testP1p1() {
final GroupElement t = GroupElement.p1p1(curve, ZERO, ONE, ONE, ONE);
assertThat(t.curve, is(equalTo(curve)));
assertThat(t.repr, is(GroupElement.Representation.P1P1));
assertThat(t.X, is(ZERO));
assertThat(t.Y, is(ONE));
assertThat(t.Z, is(ONE));
assertThat(t.T, is(ONE));
}
/**
* Test method for {@link GroupElement#precomp(Curve, FieldElement, FieldElement, FieldElement)}.
*/
@Test
public void testPrecomp() {
final GroupElement t = GroupElement.precomp(curve, ONE, ONE, ZERO);
assertThat(t.curve, is(equalTo(curve)));
assertThat(t.repr, is(GroupElement.Representation.PRECOMP));
assertThat(t.X, is(ONE));
assertThat(t.Y, is(ONE));
assertThat(t.Z, is(ZERO));
assertThat(t.T, is((FieldElement) null));
}
/**
* Test method for {@link GroupElement#cached(Curve, FieldElement, FieldElement, FieldElement, FieldElement)}.
*/
@Test
public void testCached() {
final GroupElement t = GroupElement.cached(curve, ONE, ONE, ONE, ZERO);
assertThat(t.curve, is(equalTo(curve)));
assertThat(t.repr, is(GroupElement.Representation.CACHED));
assertThat(t.X, is(ONE));
assertThat(t.Y, is(ONE));
assertThat(t.Z, is(ONE));
assertThat(t.T, is(ZERO));
}
/**
* Test method for {@link GroupElement#GroupElement(Curve, GroupElement.Representation, FieldElement, FieldElement, FieldElement, FieldElement)}.
*/
@Test
public void testGroupElementCurveRepresentationFieldElementFieldElementFieldElementFieldElement() {
final GroupElement t = new GroupElement(curve, GroupElement.Representation.P3, ZERO, ONE, ONE, ZERO);
assertThat(t.curve, is(equalTo(curve)));
assertThat(t.repr, is(GroupElement.Representation.P3));
assertThat(t.X, is(ZERO));
assertThat(t.Y, is(ONE));
assertThat(t.Z, is(ONE));
assertThat(t.T, is(ZERO));
}
/**
* Tests {@link GroupElement#GroupElement(Curve, byte[])} and
* {@link GroupElement#toByteArray()} against valid public keys.
*/
@Test
public void testToAndFromByteArray() {
GroupElement t;
for (Ed25519TestVectors.TestTuple testCase : Ed25519TestVectors.testCases) {
t = new GroupElement(curve, testCase.pk);
assertThat("Test case " + testCase.caseNum + " failed",
t.toByteArray(), is(equalTo(testCase.pk)));
}
}
/**
* Test method for {@link GroupElement#GroupElement(Curve, byte[])}.
*/
@Test
public void testGroupElementByteArray() {
final GroupElement t = new GroupElement(curve, BYTES_PKR);
final GroupElement s = GroupElement.p3(curve, PKR[0], PKR[1], ONE, PKR[0].multiply(PKR[1]));
assertThat(t, is(equalTo(s)));
}
@Test
public void constructorUsingByteArrayReturnsExpectedResult() {
for (int i=0; i<100; i++) {
// Arrange:
final GroupElement g = MathUtils.getRandomGroupElement();
final byte[] bytes = g.toByteArray();
// Act:
final GroupElement h1 = new GroupElement(curve, bytes);
final GroupElement h2 = MathUtils.toGroupElement(bytes);
// Assert:
Assert.assertThat(h1, IsEqual.equalTo(h2));
}
}
/**
* Test method for {@link GroupElement#toByteArray()}.
* <p>
* TODO 20141001 BR: why test with points which are not on the curve?
*/
@Test
public void testToByteArray() {
byte[] zerozero = GroupElement.p2(curve, ZERO, ZERO, ONE).toByteArray();
assertThat(zerozero.length, is(equalTo(BYTES_ZEROZERO.length)));
assertThat(zerozero, is(equalTo(BYTES_ZEROZERO)));
byte[] oneone = GroupElement.p2(curve, ONE, ONE, ONE).toByteArray();
assertThat(oneone.length, is(equalTo(BYTES_ONEONE.length)));
assertThat(oneone, is(equalTo(BYTES_ONEONE)));
byte[] tenzero = GroupElement.p2(curve, TEN, ZERO, ONE).toByteArray();
assertThat(tenzero.length, is(equalTo(BYTES_TENZERO.length)));
assertThat(tenzero, is(equalTo(BYTES_TENZERO)));
byte[] oneten = GroupElement.p2(curve, ONE, TEN, ONE).toByteArray();
assertThat(oneten.length, is(equalTo(BYTES_ONETEN.length)));
assertThat(oneten, is(equalTo(BYTES_ONETEN)));
byte[] pkr = GroupElement.p2(curve, PKR[0], PKR[1], ONE).toByteArray();
assertThat(pkr.length, is(equalTo(BYTES_PKR.length)));
assertThat(pkr, is(equalTo(BYTES_PKR)));
}
@Test
public void toByteArrayReturnsExpectedResult() {
for (int i=0; i<100; i++) {
// Arrange:
final GroupElement g = MathUtils.getRandomGroupElement();
// Act:
final byte[] gBytes = g.toByteArray();
final byte[] bytes = MathUtils.toByteArray(MathUtils.toBigInteger(g.getY()));
if (MathUtils.toBigInteger(g.getX()).mod(new BigInteger("2")).equals(BigInteger.ONE)) {
bytes[31] |= 0x80;
}
// Assert:
Assert.assertThat(Arrays.equals(gBytes, bytes), IsEqual.equalTo(true));
}
}
// region toX where X is the representation
/**
* Test method for {@link GroupElement#toP2()}.
*/
@Test
public void testToP2() {
GroupElement p3zero = curve.getZero(GroupElement.Representation.P3);
GroupElement t = p3zero.toP2();
assertThat(t.repr, is(GroupElement.Representation.P2));
assertThat(t.X, is(p3zero.X));
assertThat(t.Y, is(p3zero.Y));
assertThat(t.Z, is(p3zero.Z));
assertThat(t.T, is((FieldElement) null));
GroupElement B = ed25519.getB();
t = B.toP2();
assertThat(t.repr, is(GroupElement.Representation.P2));
assertThat(t.X, is(B.X));
assertThat(t.Y, is(B.Y));
assertThat(t.Z, is(B.Z));
assertThat(t.T, is((FieldElement) null));
}
@Test (expected = IllegalArgumentException.class)
public void toP2ThrowsIfGroupElementHasPrecompRepresentation() {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.PRECOMP);
// Assert:
g.toP2();
}
@Test (expected = IllegalArgumentException.class)
public void toP2ThrowsIfGroupElementHasCachedRepresentation() {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.CACHED);
// Assert:
g.toP2();
}
@Test
public void toP2ReturnsExpectedResultIfGroupElementHasP2Representation() {
for (int i=0; i<10; i++) {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.P2);
// Act:
final GroupElement h = g.toP2();
// Assert:
Assert.assertThat(h, IsEqual.equalTo(g));
Assert.assertThat(h.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.P2));
Assert.assertThat(h.getX(), IsEqual.equalTo(g.getX()));
Assert.assertThat(h.getY(), IsEqual.equalTo(g.getY()));
Assert.assertThat(h.getZ(), IsEqual.equalTo(g.getZ()));
Assert.assertThat(h.getT(), IsEqual.equalTo(null));
}
}
@Test
public void toP2ReturnsExpectedResultIfGroupElementHasP3Representation() {
for (int i=0; i<10; i++) {
// Arrange:
final GroupElement g = MathUtils.getRandomGroupElement();
// Act:
final GroupElement h1 = g.toP2();
final GroupElement h2 = MathUtils.toRepresentation(g, GroupElement.Representation.P2);
// Assert:
Assert.assertThat(h1, IsEqual.equalTo(h2));
Assert.assertThat(h1.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.P2));
Assert.assertThat(h1.getX(), IsEqual.equalTo(g.getX()));
Assert.assertThat(h1.getY(), IsEqual.equalTo(g.getY()));
Assert.assertThat(h1.getZ(), IsEqual.equalTo(g.getZ()));
Assert.assertThat(h1.getT(), IsEqual.equalTo(null));
}
}
@Test
public void toP2ReturnsExpectedResultIfGroupElementHasP1P1Representation() {
for (int i=0; i<10; i++) {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.P1P1);
// Act:
final GroupElement h1 = g.toP2();
final GroupElement h2 = MathUtils.toRepresentation(g, GroupElement.Representation.P2);
// Assert:
Assert.assertThat(h1, IsEqual.equalTo(h2));
Assert.assertThat(h1.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.P2));
Assert.assertThat(h1.getX(), IsEqual.equalTo(g.getX().multiply(g.getT())));
Assert.assertThat(h1.getY(), IsEqual.equalTo(g.getY().multiply(g.getZ())));
Assert.assertThat(h1.getZ(), IsEqual.equalTo(g.getZ().multiply(g.getT())));
Assert.assertThat(h1.getT(), IsEqual.equalTo(null));
}
}
@Test (expected = IllegalArgumentException.class)
public void toP3ThrowsIfGroupElementHasP2Representation() {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.P2);
// Assert:
g.toP3();
}
@Test (expected = IllegalArgumentException.class)
public void toP3ThrowsIfGroupElementHasPrecompRepresentation() {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.PRECOMP);
// Assert:
g.toP3();
}
@Test (expected = IllegalArgumentException.class)
public void toP3ThrowsIfGroupElementHasCachedRepresentation() {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.CACHED);
// Assert:
g.toP3();
}
@Test
public void toP3ReturnsExpectedResultIfGroupElementHasP1P1Representation() {
for (int i=0; i<10; i++) {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.P1P1);
// Act:
final GroupElement h1 = g.toP3();
final GroupElement h2 = MathUtils.toRepresentation(g, GroupElement.Representation.P3);
// Assert:
Assert.assertThat(h1, IsEqual.equalTo(h2));
Assert.assertThat(h1.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.P3));
Assert.assertThat(h1.getX(), IsEqual.equalTo(g.getX().multiply(g.getT())));
Assert.assertThat(h1.getY(), IsEqual.equalTo(g.getY().multiply(g.getZ())));
Assert.assertThat(h1.getZ(), IsEqual.equalTo(g.getZ().multiply(g.getT())));
Assert.assertThat(h1.getT(), IsEqual.equalTo(g.getX().multiply(g.getY())));
}
}
@Test
public void toP3ReturnsExpectedResultIfGroupElementHasP3Representation() {
for (int i=0; i<10; i++) {
// Arrange:
final GroupElement g = MathUtils.getRandomGroupElement();
// Act:
final GroupElement h = g.toP3();
// Assert:
Assert.assertThat(h, IsEqual.equalTo(g));
Assert.assertThat(h.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.P3));
Assert.assertThat(h, IsEqual.equalTo(g));
Assert.assertThat(h.getX(), IsEqual.equalTo(g.getX()));
Assert.assertThat(h.getY(), IsEqual.equalTo(g.getY()));
Assert.assertThat(h.getZ(), IsEqual.equalTo(g.getZ()));
Assert.assertThat(h.getT(), IsEqual.equalTo(g.getT()));
}
}
@Test (expected = IllegalArgumentException.class)
public void toCachedThrowsIfGroupElementHasP2Representation() {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.P2);
// Assert:
g.toCached();
}
@Test (expected = IllegalArgumentException.class)
public void toCachedThrowsIfGroupElementHasPrecompRepresentation() {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.PRECOMP);
// Assert:
g.toCached();
}
@Test (expected = IllegalArgumentException.class)
public void toCachedThrowsIfGroupElementHasP1P1Representation() {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.P1P1);
// Assert:
g.toCached();
}
@Test
public void toCachedReturnsExpectedResultIfGroupElementHasCachedRepresentation() {
for (int i=0; i<10; i++) {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.CACHED);
// Act:
final GroupElement h = g.toCached();
// Assert:
Assert.assertThat(h, IsEqual.equalTo(g));
Assert.assertThat(h.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.CACHED));
Assert.assertThat(h, IsEqual.equalTo(g));
Assert.assertThat(h.getX(), IsEqual.equalTo(g.getX()));
Assert.assertThat(h.getY(), IsEqual.equalTo(g.getY()));
Assert.assertThat(h.getZ(), IsEqual.equalTo(g.getZ()));
Assert.assertThat(h.getT(), IsEqual.equalTo(g.getT()));
}
}
@Test
public void toCachedReturnsExpectedResultIfGroupElementHasP3Representation() {
for (int i=0; i<10; i++) {
// Arrange:
final GroupElement g = MathUtils.getRandomGroupElement();
// Act:
final GroupElement h1 = g.toCached();
final GroupElement h2 = MathUtils.toRepresentation(g, GroupElement.Representation.CACHED);
// Assert:
Assert.assertThat(h1, IsEqual.equalTo(h2));
Assert.assertThat(h1.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.CACHED));
Assert.assertThat(h1, IsEqual.equalTo(g));
Assert.assertThat(h1.getX(), IsEqual.equalTo(g.getY().add(g.getX())));
Assert.assertThat(h1.getY(), IsEqual.equalTo(g.getY().subtract(g.getX())));
Assert.assertThat(h1.getZ(), IsEqual.equalTo(g.getZ()));
Assert.assertThat(h1.getT(), IsEqual.equalTo(g.getT().multiply(curve.get2D())));
}
}
// endregion
/**
* Test method for {@link GroupElement#precompute(boolean)}.
*/
@Test
public void testPrecompute() {
GroupElement B = ed25519.getB();
assertThat(B.precmp, is(equalTo(PrecomputationTestVectors.testPrecmp)));
assertThat(B.dblPrecmp, is(equalTo(PrecomputationTestVectors.testDblPrecmp)));
}
@Test
public void precomputedTableContainsExpectedGroupElements() {
// Arrange:
GroupElement g = ed25519.getB();
// Act + Assert:
for (int i = 0; i < 32; i++) {
GroupElement h = g;
for (int j = 0; j < 8; j++) {
Assert.assertThat(MathUtils.toRepresentation(h, GroupElement.Representation.PRECOMP), IsEqual.equalTo(ed25519.getB().precmp[i][j]));
h = MathUtils.addGroupElements(h, g);
}
for (int k = 0; k < 8; k++) {
g = MathUtils.addGroupElements(g, g);
}
}
}
@Test
public void dblPrecomputedTableContainsExpectedGroupElements() {
// Arrange:
GroupElement g = ed25519.getB();
GroupElement h = MathUtils.addGroupElements(g, g);
// Act + Assert:
for (int i=0; i<8; i++) {
Assert.assertThat(MathUtils.toRepresentation(g, GroupElement.Representation.PRECOMP), IsEqual.equalTo(ed25519.getB().dblPrecmp[i]));
g = MathUtils.addGroupElements(g, h);
}
}
/**
* Test method for {@link GroupElement#dbl()}.
*/
@Test
public void testDbl() {
GroupElement B = ed25519.getB();
// 2 * B = B + B
assertThat(B.dbl(), is(equalTo(B.add(B.toCached()))));
}
@Test
public void dblReturnsExpectedResult() {
for (int i=0; i<1000; i++) {
// Arrange:
final GroupElement g = MathUtils.getRandomGroupElement();
// Act:
final GroupElement h1 = g.dbl();
final GroupElement h2 = MathUtils.doubleGroupElement(g);
// Assert:
Assert.assertThat(h2, IsEqual.equalTo(h1));
}
}
@Test
public void addingNeutralGroupElementDoesNotChangeGroupElement() {
final GroupElement neutral = GroupElement.p3(curve, curve.getField().ZERO, curve.getField().ONE, curve.getField().ONE, curve.getField().ZERO);
for (int i=0; i<1000; i++) {
// Arrange:
final GroupElement g = MathUtils.getRandomGroupElement();
// Act:
final GroupElement h1 = g.add(neutral.toCached());
final GroupElement h2 = neutral.add(g.toCached());
// Assert:
Assert.assertThat(g, IsEqual.equalTo(h1));
Assert.assertThat(g, IsEqual.equalTo(h2));
}
}
@Test
public void addReturnsExpectedResult() {
for (int i=0; i<1000; i++) {
// Arrange:
final GroupElement g1 = MathUtils.getRandomGroupElement();
final GroupElement g2 = MathUtils.getRandomGroupElement();
// Act:
final GroupElement h1 = g1.add(g2.toCached());
final GroupElement h2 = MathUtils.addGroupElements(g1, g2);
// Assert:
Assert.assertThat(h2, IsEqual.equalTo(h1));
}
}
@Test
public void subReturnsExpectedResult() {
for (int i=0; i<1000; i++) {
// Arrange:
final GroupElement g1 = MathUtils.getRandomGroupElement();
final GroupElement g2 = MathUtils.getRandomGroupElement();
// Act:
final GroupElement h1 = g1.sub(g2.toCached());
final GroupElement h2 = MathUtils.addGroupElements(g1, MathUtils.negateGroupElement(g2));
// Assert:
Assert.assertThat(h2, IsEqual.equalTo(h1));
}
}
// region hashCode / equals
/**
* Test method for {@link GroupElement#equals(Object)}.
*/
@Test
public void testEqualsObject() {
assertThat(GroupElement.p2(curve, ZERO, ONE, ONE),
is(equalTo(P2_ZERO)));
}
@Test
public void equalsOnlyReturnsTrueForEquivalentObjects() {
// Arrange:
final GroupElement g1 = MathUtils.getRandomGroupElement();
final GroupElement g2 = MathUtils.toRepresentation(g1, GroupElement.Representation.P2);
final GroupElement g3 = MathUtils.toRepresentation(g1, GroupElement.Representation.CACHED);
final GroupElement g4 = MathUtils.toRepresentation(g1, GroupElement.Representation.P1P1);
final GroupElement g5 = MathUtils.getRandomGroupElement();
// Assert
Assert.assertThat(g2, IsEqual.equalTo(g1));
Assert.assertThat(g3, IsEqual.equalTo(g1));
Assert.assertThat(g1, IsEqual.equalTo(g4));
Assert.assertThat(g1, IsNot.not(IsEqual.equalTo(g5)));
Assert.assertThat(g2, IsNot.not(IsEqual.equalTo(g5)));
Assert.assertThat(g3, IsNot.not(IsEqual.equalTo(g5)));
Assert.assertThat(g5, IsNot.not(IsEqual.equalTo(g4)));
}
@Test
public void hashCodesAreEqualForEquivalentObjects() {
// Arrange:
final GroupElement g1 = MathUtils.getRandomGroupElement();
final GroupElement g2 = MathUtils.toRepresentation(g1, GroupElement.Representation.P2);
final GroupElement g3 = MathUtils.toRepresentation(g1, GroupElement.Representation.P1P1);
final GroupElement g4 = MathUtils.getRandomGroupElement();
// Assert
Assert.assertThat(g2.hashCode(), IsEqual.equalTo(g1.hashCode()));
Assert.assertThat(g3.hashCode(), IsEqual.equalTo(g1.hashCode()));
Assert.assertThat(g1.hashCode(), IsNot.not(IsEqual.equalTo(g4.hashCode())));
Assert.assertThat(g2.hashCode(), IsNot.not(IsEqual.equalTo(g4.hashCode())));
Assert.assertThat(g3.hashCode(), IsNot.not(IsEqual.equalTo(g4.hashCode())));
}
// endregion
static final byte[] BYTES_ZERO = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000");
static final byte[] BYTES_ONE = Utils.hexToBytes("0100000000000000000000000000000000000000000000000000000000000000");
static final byte[] BYTES_42 = Utils.hexToBytes("2A00000000000000000000000000000000000000000000000000000000000000");
static final byte[] BYTES_1234567890 = Utils.hexToBytes("D202964900000000000000000000000000000000000000000000000000000000");
static final byte[] RADIX16_ZERO = Utils.hexToBytes("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
static final byte[] RADIX16_ONE = Utils.hexToBytes("01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
static final byte[] RADIX16_42 = Utils.hexToBytes("FA030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
/**
* Test method for {@link GroupElement#toRadix16(byte[])}.
*/
@Test
public void testToRadix16() {
assertThat(GroupElement.toRadix16(BYTES_ZERO), is(RADIX16_ZERO));
assertThat(GroupElement.toRadix16(BYTES_ONE), is(RADIX16_ONE));
assertThat(GroupElement.toRadix16(BYTES_42), is(RADIX16_42));
byte[] from1234567890 = GroupElement.toRadix16(BYTES_1234567890);
int total = 0;
for (int i = 0; i < from1234567890.length; i++) {
assertThat(from1234567890[i], is(greaterThanOrEqualTo((byte)-8)));
assertThat(from1234567890[i], is(lessThanOrEqualTo((byte)8)));
total += from1234567890[i] * Math.pow(16, i);
}
assertThat(total, is(1234567890));
byte[] pkrR16 = GroupElement.toRadix16(BYTES_PKR);
for (int i = 0; i < pkrR16.length; i++) {
assertThat(pkrR16[i], is(greaterThanOrEqualTo((byte)-8)));
assertThat(pkrR16[i], is(lessThanOrEqualTo((byte)8)));
}
}
/**
* Test method for {@link GroupElement#cmov(GroupElement, int)}.
*/
@Test
public void testCmov() {
GroupElement a = curve.getZero(GroupElement.Representation.PRECOMP);
GroupElement b = GroupElement.precomp(curve, TWO, ZERO, TEN);
assertThat(a.cmov(b, 0), is(equalTo(a)));
assertThat(a.cmov(b, 1), is(equalTo(b)));
}
/**
* Test method for {@link GroupElement#select(int, int)}.
*/
@Test
public void testSelect() {
GroupElement B = ed25519.getB();
for (int i = 0; i < 32; i++) {
// 16^i 0 B
assertThat(i + ",0", B.select(i, 0),
is(equalTo(GroupElement.precomp(curve, ONE, ONE, ZERO))));
for (int j = 1; j < 8; j++) {
// 16^i r_i B
GroupElement t = B.select(i, j);
assertThat(i + "," + j,
t, is(equalTo(B.precmp[i][j-1])));
// -16^i r_i B
t = B.select(i, -j);
GroupElement neg = GroupElement.precomp(curve,
B.precmp[i][j-1].Y,
B.precmp[i][j-1].X,
B.precmp[i][j-1].Z.negate());
assertThat(i + "," + -j,
t, is(equalTo(neg)));
}
}
}
// region scalar multiplication
/**
* Test method for {@link GroupElement#scalarMultiply(byte[])}.
* Test values generated with Python Ed25519 implementation.
*/
@Test
public void testScalarMultiplyByteArray() {
// Little-endian
byte[] zero = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000");
byte[] one = Utils.hexToBytes("0100000000000000000000000000000000000000000000000000000000000000");
byte[] two = Utils.hexToBytes("0200000000000000000000000000000000000000000000000000000000000000");
byte[] a = Utils.hexToBytes("d072f8dd9c07fa7bc8d22a4b325d26301ee9202f6db89aa7c3731529e37e437c");
GroupElement A = new GroupElement(curve, Utils.hexToBytes("d4cf8595571830644bd14af416954d09ab7159751ad9e0f7a6cbd92379e71a66"));
assertThat("scalarMultiply(0) failed",
ed25519.getB().scalarMultiply(zero), is(equalTo(curve.getZero(GroupElement.Representation.P3))));
assertThat("scalarMultiply(1) failed",
ed25519.getB().scalarMultiply(one), is(equalTo(ed25519.getB())));
assertThat("scalarMultiply(2) failed",
ed25519.getB().scalarMultiply(two), is(equalTo(ed25519.getB().dbl())));
assertThat("scalarMultiply(a) failed",
ed25519.getB().scalarMultiply(a), is(equalTo(A)));
}
@Test
public void scalarMultiplyBasePointWithZeroReturnsNeutralElement() {
// Arrange:
final GroupElement basePoint = ed25519.getB();
// Act:
final GroupElement g = basePoint.scalarMultiply(curve.getField().ZERO.toByteArray());
// Assert:
Assert.assertThat(curve.getZero(GroupElement.Representation.P3), IsEqual.equalTo(g));
}
@Test
public void scalarMultiplyBasePointWithOneReturnsBasePoint() {
// Arrange:
final GroupElement basePoint = ed25519.getB();
// Act:
final GroupElement g = basePoint.scalarMultiply(curve.getField().ONE.toByteArray());
// Assert:
Assert.assertThat(basePoint, IsEqual.equalTo(g));
}
// This test is slow (~6s) due to math utils using an inferior algorithm to calculate the result.
@Test
public void scalarMultiplyBasePointReturnsExpectedResult() {
for (int i=0; i<10; i++) {
// Arrange:
final GroupElement basePoint = ed25519.getB();
final FieldElement f = MathUtils.getRandomFieldElement();
// Act:
final GroupElement g = basePoint.scalarMultiply(f.toByteArray());
final GroupElement h = MathUtils.scalarMultiplyGroupElement(basePoint, f);
// Assert:
Assert.assertThat(g, IsEqual.equalTo(h));
}
}
@Test
public void testDoubleScalarMultiplyVariableTime() {
// Little-endian
byte[] zero = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000");
byte[] one = Utils.hexToBytes("0100000000000000000000000000000000000000000000000000000000000000");
byte[] two = Utils.hexToBytes("0200000000000000000000000000000000000000000000000000000000000000");
byte[] a = Utils.hexToBytes("d072f8dd9c07fa7bc8d22a4b325d26301ee9202f6db89aa7c3731529e37e437c");
GroupElement A = new GroupElement(curve, Utils.hexToBytes("d4cf8595571830644bd14af416954d09ab7159751ad9e0f7a6cbd92379e71a66"));
GroupElement B = ed25519.getB();
GroupElement geZero = curve.getZero(GroupElement.Representation.P3);
geZero.precompute(false);
// 0 * GE(0) + 0 * GE(0) = GE(0)
assertThat(geZero.doubleScalarMultiplyVariableTime(geZero, zero, zero),
is(equalTo(geZero)));
// 0 * GE(0) + 0 * B = GE(0)
assertThat(B.doubleScalarMultiplyVariableTime(geZero, zero, zero),
is(equalTo(geZero)));
// 1 * GE(0) + 0 * B = GE(0)
assertThat(B.doubleScalarMultiplyVariableTime(geZero, one, zero),
is(equalTo(geZero)));
// 1 * GE(0) + 1 * B = B
assertThat(B.doubleScalarMultiplyVariableTime(geZero, one, one),
is(equalTo(B)));
// 1 * B + 1 * B = 2 * B
assertThat(B.doubleScalarMultiplyVariableTime(B, one, one),
is(equalTo(B.dbl())));
// 1 * B + 2 * B = 3 * B
assertThat(B.doubleScalarMultiplyVariableTime(B, one, two),
is(equalTo(B.dbl().toP3().add(B.toCached()))));
// 2 * B + 2 * B = 4 * B
assertThat(B.doubleScalarMultiplyVariableTime(B, two, two),
is(equalTo(B.dbl().toP3().dbl())));
// 0 * B + a * B = A
assertThat(B.doubleScalarMultiplyVariableTime(B, zero, a),
is(equalTo(A)));
// a * B + 0 * B = A
assertThat(B.doubleScalarMultiplyVariableTime(B, a, zero),
is(equalTo(A)));
// a * B + a * B = 2 * A
assertThat(B.doubleScalarMultiplyVariableTime(B, a, a),
is(equalTo(A.dbl())));
}
// This test is slow (~6s) due to math utils using an inferior algorithm to calculate the result.
@Test
public void doubleScalarMultiplyVariableTimeReturnsExpectedResult() {
for (int i=0; i<10; i++) {
// Arrange:
final GroupElement basePoint = ed25519.getB();
final GroupElement g = MathUtils.getRandomGroupElement();
g.precompute(false);
final FieldElement f1 = MathUtils.getRandomFieldElement();
final FieldElement f2 = MathUtils.getRandomFieldElement();
// Act:
final GroupElement h1 = basePoint.doubleScalarMultiplyVariableTime(g, f2.toByteArray(), f1.toByteArray());
final GroupElement h2 = MathUtils.doubleScalarMultiplyGroupElements(basePoint, f1, g, f2);
// Assert:
Assert.assertThat(h1, IsEqual.equalTo(h2));
}
}
// endregion
/**
* Test method for {@link GroupElement#isOnCurve(Curve)}.
*/
@Test
public void testIsOnCurve() {
assertThat(P2_ZERO.isOnCurve(curve),
is(true));
assertThat(GroupElement.p2(curve, ZERO, ZERO, ONE).isOnCurve(curve),
is(false));
assertThat(GroupElement.p2(curve, ONE, ONE, ONE).isOnCurve(curve),
is(false));
assertThat(GroupElement.p2(curve, TEN, ZERO, ONE).isOnCurve(curve),
is(false));
assertThat(GroupElement.p2(curve, ONE, TEN, ONE).isOnCurve(curve),
is(false));
assertThat(GroupElement.p2(curve, PKR[0], PKR[1], ONE).isOnCurve(curve),
is(true));
}
@Test
public void isOnCurveReturnsTrueForPointsOnTheCurve() {
for (int i=0; i<100; i++) {
// Arrange:
final GroupElement g = MathUtils.getRandomGroupElement();
// Assert:
Assert.assertThat(g.isOnCurve(), IsEqual.equalTo(true));
}
}
@Test
public void isOnCurveReturnsFalseForPointsNotOnTheCurve() {
for (int i=0; i<100; i++) {
// Arrange:
final GroupElement g = MathUtils.getRandomGroupElement();
final GroupElement h = GroupElement.p2(curve, g.getX(), g.getY(), g.getZ().multiply(curve.getField().TWO));
// Assert (can only fail for 5*Z^2=1):
Assert.assertThat(h.isOnCurve(), IsEqual.equalTo(false));
}
}
}

View file

@ -1,472 +0,0 @@
package org.xbib.io.sshd.eddsa.math;
import org.xbib.io.sshd.eddsa.Utils;
import org.xbib.io.sshd.eddsa.math.ed25519.Ed25519FieldElement;
import org.xbib.io.sshd.eddsa.math.ed25519.Ed25519LittleEndianEncoding;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveSpec;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveTable;
import org.hamcrest.core.IsEqual;
import org.junit.Assert;
import org.junit.Test;
import java.math.BigInteger;
import java.security.SecureRandom;
/**
* Utility class to help with calculations.
*/
public class MathUtils {
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};
private static final SecureRandom random = new SecureRandom();
private static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
private static final Curve curve = ed25519.getCurve();
private static final BigInteger d = new BigInteger("-121665").multiply(new BigInteger("121666").modInverse(getQ()));
private static final BigInteger groupOrder = BigInteger.ONE.shiftLeft(252).add(new BigInteger("27742317777372353535851937790883648493"));
/**
* Gets q = 2^255 - 19 as BigInteger.
*/
public static BigInteger getQ() {
return new BigInteger("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed", 16);
}
/**
* Gets group order = 2^252 + 27742317777372353535851937790883648493 as BigInteger.
*/
public static BigInteger getGroupOrder() {
return groupOrder;
}
/**
* Gets the underlying finite field with q=2^255 - 19 elements.
*
* @return The finite field.
*/
public static Field getField() {
return new Field(
256, // b
Utils.hexToBytes("edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), // q
new Ed25519LittleEndianEncoding());
}
// region field element
/**
* Converts a 2^25.5 bit representation to a BigInteger.
* <p>
* Value: 2^exponents[0] * t[0] + 2^exponents[1] * t[1] + ... + 2^exponents[9] * t[9]
*
* @param t The 2^25.5 bit representation.
* @return The BigInteger.
*/
public static BigInteger toBigInteger(final int[] t) {
BigInteger b = BigInteger.ZERO;
for (int i=0; i<10; i++) {
b = b.add(BigInteger.ONE.multiply(BigInteger.valueOf(t[i])).shiftLeft(exponents[i]));
}
return b;
}
/**
* Converts a 2^8 bit representation to a BigInteger.
* <p>
* Value: bytes[0] + 2^8 * bytes[1] + ...
*
* @param bytes The 2^8 bit representation.
* @return The BigInteger.
*/
public static BigInteger toBigInteger(final byte[] bytes) {
BigInteger b = BigInteger.ZERO;
for (int i=0; i<bytes.length; i++) {
b = b.add(BigInteger.ONE.multiply(BigInteger.valueOf(bytes[i] & 0xff)).shiftLeft(i * 8));
}
return b;
}
/**
* Converts a field element to a BigInteger.
*
* @param f The field element.
* @return The BigInteger.
*/
public static BigInteger toBigInteger(final FieldElement f) {
return toBigInteger(f.toByteArray());
}
/**
* Converts a BigInteger to a field element.
*
* @param b The BigInteger.
* @return The field element.
*/
public static FieldElement toFieldElement(final BigInteger b) {
return getField().getEncoding().decode(toByteArray(b));
}
/**
* Converts a BigInteger to a little endian 32 byte representation.
*
* @param b The BigInteger.
* @return The 32 byte representation.
*/
public static byte[] toByteArray(final BigInteger b) {
if (b.compareTo(BigInteger.ONE.shiftLeft(256)) >= 0) {
throw new RuntimeException("only numbers < 2^256 are allowed");
}
final byte[] bytes = new byte[32];
final byte[] original = b.toByteArray();
// Although b < 2^256, original can have length > 32 with some bytes set to 0.
final int offset = original.length > 32? original.length - 32 : 0;
for (int i=0; i<original.length - offset; i++) {
bytes[original.length - i - offset - 1] = original[i + offset];
}
return bytes;
}
/**
* Reduces an integer in 2^8 bit representation modulo the group order and returns the result.
*
* @param bytes The integer in 2^8 bit representation.
* @return The mod group order reduced integer.
*/
public static byte[] reduceModGroupOrder(final byte[] bytes) {
final BigInteger b = toBigInteger(bytes).mod(groupOrder);
return toByteArray(b);
}
/**
* Calculates (a * b + c) mod group order and returns the result.
* <p>
* a, b and c are given in 2^8 bit representation.
*
* @param a The first integer.
* @param b The second integer.
* @param c The third integer.
* @return The mod group order reduced result.
*/
public static byte[] multiplyAndAddModGroupOrder(final byte[] a, final byte[] b, final byte[] c) {
final BigInteger result = toBigInteger(a).multiply(toBigInteger(b)).add(toBigInteger(c)).mod(groupOrder);
return toByteArray(result);
}
public static byte[] getRandomByteArray(final int length) {
final byte[] bytes = new byte[length];
random.nextBytes(bytes);
return bytes;
}
/**
* Gets a random field element where |t[i]| <= 2^24 for 0 <= i <= 9.
*
* @return The field element.
*/
public static FieldElement getRandomFieldElement() {
final int[] t = new int[10];
for (int j=0; j<10; j++) {
t[j] = random.nextInt(1 << 25) - (1 << 24);
}
return new Ed25519FieldElement(getField(), t);
}
// endregion
// region group element
/**
* Gets a random group element in P3 representation.
*
* @return The group element.
*/
public static GroupElement getRandomGroupElement() {
final byte[] bytes = new byte[32];
while (true) {
try {
random.nextBytes(bytes);
return new GroupElement(curve, bytes);
} catch (IllegalArgumentException e) {
// Will fail in about 87.5%, so try again.
}
}
}
/**
* Creates a group element from a byte array.
* <p>
* Bit 0 to 254 are the affine y-coordinate, bit 255 is the sign of the affine x-coordinate.
*
* @param bytes the byte array.
* @return The group element.
*/
public static GroupElement toGroupElement(final byte[] bytes) {
final boolean shouldBeNegative = (bytes[31] >> 7) != 0;
bytes[31] &= 0x7f;
final BigInteger y = MathUtils.toBigInteger(bytes);
// x = sign(x) * sqrt((y^2 - 1) / (d * y^2 + 1))
final BigInteger u = y.multiply(y).subtract(BigInteger.ONE).mod(getQ());
final BigInteger v = d.multiply(y).multiply(y).add(BigInteger.ONE).mod(getQ());
final BigInteger tmp = u.multiply(v.pow(7)).modPow(BigInteger.ONE.shiftLeft(252).subtract(new BigInteger("3")), getQ()).mod(getQ());
BigInteger x = tmp.multiply(u).multiply(v.pow(3)).mod(getQ());
if (!v.multiply(x).multiply(x).subtract(u).mod(getQ()).equals(BigInteger.ZERO)) {
if (!v.multiply(x).multiply(x).add(u).mod(getQ()).equals(BigInteger.ZERO)) {
throw new IllegalArgumentException("not a valid GroupElement");
}
x = x.multiply(toBigInteger(curve.getI())).mod(getQ());
}
final boolean isNegative = x.mod(new BigInteger("2")).equals(BigInteger.ONE);
if ((shouldBeNegative && !isNegative) || (!shouldBeNegative && isNegative)) {
x = x.negate().mod(getQ());
}
return GroupElement.p3(curve, toFieldElement(x), toFieldElement(y), getField().ONE, toFieldElement(x.multiply(y).mod(getQ())));
}
/**
* Converts a group element from one representation to another.
* This method is a helper used to test various methods in GroupElement.
*
* @param g The group element.
* @param repr The desired representation.
* @return The same group element in the new representation.
*/
public static GroupElement toRepresentation(final GroupElement g, final GroupElement.Representation repr) {
BigInteger x;
BigInteger y;
final BigInteger gX = toBigInteger(g.getX().toByteArray());
final BigInteger gY = toBigInteger(g.getY().toByteArray());
final BigInteger gZ = toBigInteger(g.getZ().toByteArray());
final BigInteger gT = null == g.getT()? null : toBigInteger(g.getT().toByteArray());
// Switch to affine coordinates.
switch (g.getRepresentation()) {
case P2:
case P3:
x = gX.multiply(gZ.modInverse(getQ())).mod(getQ());
y = gY.multiply(gZ.modInverse(getQ())).mod(getQ());
break;
case P1P1:
x = gX.multiply(gZ.modInverse(getQ())).mod(getQ());
y = gY.multiply(gT.modInverse(getQ())).mod(getQ());
break;
case CACHED:
x = gX.subtract(gY).multiply(gZ.multiply(new BigInteger("2")).modInverse(getQ())).mod(getQ());
y = gX.add(gY).multiply(gZ.multiply(new BigInteger("2")).modInverse(getQ())).mod(getQ());
break;
case PRECOMP:
x = gX.subtract(gY).multiply(new BigInteger("2").modInverse(getQ())).mod(getQ());
y = gX.add(gY).multiply(new BigInteger("2").modInverse(getQ())).mod(getQ());
break;
default:
throw new UnsupportedOperationException();
}
// Now back to the desired representation.
switch (repr) {
case P2:
return GroupElement.p2(
curve,
toFieldElement(x),
toFieldElement(y),
getField().ONE);
case P3:
return GroupElement.p3(
curve,
toFieldElement(x),
toFieldElement(y),
getField().ONE,
toFieldElement(x.multiply(y).mod(getQ())));
case P1P1:
return GroupElement.p1p1(
curve,
toFieldElement(x),
toFieldElement(y),
getField().ONE,
getField().ONE);
case CACHED:
return GroupElement.cached(
curve,
toFieldElement(y.add(x).mod(getQ())),
toFieldElement(y.subtract(x).mod(getQ())),
getField().ONE,
toFieldElement(d.multiply(new BigInteger("2")).multiply(x).multiply(y).mod(getQ())));
case PRECOMP:
return GroupElement.precomp(
curve,
toFieldElement(y.add(x).mod(getQ())),
toFieldElement(y.subtract(x).mod(getQ())),
toFieldElement(d.multiply(new BigInteger("2")).multiply(x).multiply(y).mod(getQ())));
default:
throw new UnsupportedOperationException();
}
}
/**
* Adds two group elements and returns the result in P3 representation.
* It uses BigInteger arithmetic and the affine representation.
* This method is a helper used to test the projective group addition formulas in GroupElement.
*
* @param g1 The first group element.
* @param g2 The second group element.
* @return The result of the addition.
*/
public static GroupElement addGroupElements(final GroupElement g1, final GroupElement g2) {
// Relying on a special representation of the group elements.
if ((g1.getRepresentation() != GroupElement.Representation.P2 && g1.getRepresentation() != GroupElement.Representation.P3) ||
(g2.getRepresentation() != GroupElement.Representation.P2 && g2.getRepresentation() != GroupElement.Representation.P3)) {
throw new IllegalArgumentException("g1 and g2 must have representation P2 or P3");
}
// Projective coordinates
final BigInteger g1X = toBigInteger(g1.getX().toByteArray());
final BigInteger g1Y = toBigInteger(g1.getY().toByteArray());
final BigInteger g1Z = toBigInteger(g1.getZ().toByteArray());
final BigInteger g2X = toBigInteger(g2.getX().toByteArray());
final BigInteger g2Y = toBigInteger(g2.getY().toByteArray());
final BigInteger g2Z = toBigInteger(g2.getZ().toByteArray());
// Affine coordinates
final BigInteger g1x = g1X.multiply(g1Z.modInverse(getQ())).mod(getQ());
final BigInteger g1y = g1Y.multiply(g1Z.modInverse(getQ())).mod(getQ());
final BigInteger g2x = g2X.multiply(g2Z.modInverse(getQ())).mod(getQ());
final BigInteger g2y = g2Y.multiply(g2Z.modInverse(getQ())).mod(getQ());
// Addition formula for affine coordinates. The formula is complete in our case.
//
// (x3, y3) = (x1, y1) + (x2, y2) where
//
// x3 = (x1 * y2 + x2 * y1) / (1 + d * x1 * x2 * y1 * y2) and
// y3 = (x1 * x2 + y1 * y2) / (1 - d * x1 * x2 * y1 * y2) and
// d = -121665/121666
BigInteger dx1x2y1y2 = d.multiply(g1x).multiply(g2x).multiply(g1y).multiply(g2y).mod(getQ());
BigInteger x3 = g1x.multiply(g2y).add(g2x.multiply(g1y))
.multiply(BigInteger.ONE.add(dx1x2y1y2).modInverse(getQ())).mod(getQ());
BigInteger y3 = g1x.multiply(g2x).add(g1y.multiply(g2y))
.multiply(BigInteger.ONE.subtract(dx1x2y1y2).modInverse(getQ())).mod(getQ());
BigInteger t3 = x3.multiply(y3).mod(getQ());
return GroupElement.p3(g1.getCurve(), toFieldElement(x3), toFieldElement(y3), getField().ONE, toFieldElement(t3));
}
/**
* Doubles a group element and returns the result in P3 representation.
* It uses BigInteger arithmetic and the affine representation.
* This method is a helper used to test the projective group doubling formula in GroupElement.
*
* @param g The group element.
* @return g+g.
*/
public static GroupElement doubleGroupElement(final GroupElement g) {
return addGroupElements(g, g);
}
/**
* Scalar multiply the group element by the field element.
*
* @param g The group element.
* @param f The field element.
* @return The resulting group element.
*/
public static GroupElement scalarMultiplyGroupElement(final GroupElement g, final FieldElement f) {
final byte[] bytes = f.toByteArray();
GroupElement h = curve.getZero(GroupElement.Representation.P3);
for (int i=254; i>=0; i--) {
h = doubleGroupElement(h);
if (Utils.bit(bytes, i) == 1) {
h = addGroupElements(h, g);
}
}
return h;
}
/**
* Calculates f1 * g1 + f2 * g2.
*
* @param g1 The first group element.
* @param f1 The first multiplier.
* @param g2 The second group element.
* @param f2 The second multiplier.
* @return The resulting group element.
*/
public static GroupElement doubleScalarMultiplyGroupElements(
final GroupElement g1,
final FieldElement f1,
final GroupElement g2,
final FieldElement f2) {
final GroupElement h1 = scalarMultiplyGroupElement(g1, f1);
final GroupElement h2 = scalarMultiplyGroupElement(g2, f2);
return addGroupElements(h1, h2);
}
/**
* Negates a group element.
*
* @param g The group element.
* @return The negated group element.
*/
public static GroupElement negateGroupElement(final GroupElement g) {
if (g.getRepresentation() != GroupElement.Representation.P3) {
throw new IllegalArgumentException("g must have representation P3");
}
return GroupElement.p3(g.getCurve(), g.getX().negate(), g.getY(), g.getZ(), g.getT().negate());
}
// Start TODO BR: Remove when finished!
@Test
public void mathUtilsWorkAsExpected() {
final GroupElement neutral = GroupElement.p3(curve, curve.getField().ZERO, curve.getField().ONE, curve.getField().ONE, curve.getField().ZERO);
for (int i=0; i<1000; i++) {
final GroupElement g = getRandomGroupElement();
// Act:
final GroupElement h1 = addGroupElements(g, neutral);
final GroupElement h2 = addGroupElements(neutral, g);
// Assert:
Assert.assertThat(g, IsEqual.equalTo(h1));
Assert.assertThat(g, IsEqual.equalTo(h2));
}
for (int i=0; i<1000; i++) {
GroupElement g = getRandomGroupElement();
// P3 -> P2.
GroupElement h = toRepresentation(g, GroupElement.Representation.P2);
Assert.assertThat(h, IsEqual.equalTo(g));
// P3 -> P1P1.
h = toRepresentation(g, GroupElement.Representation.P1P1);
Assert.assertThat(g, IsEqual.equalTo(h));
// P3 -> CACHED.
h = toRepresentation(g, GroupElement.Representation.CACHED);
Assert.assertThat(h, IsEqual.equalTo(g));
// P3 -> P2 -> P3.
g = toRepresentation(g, GroupElement.Representation.P2);
h = toRepresentation(g, GroupElement.Representation.P3);
Assert.assertThat(g, IsEqual.equalTo(h));
// P3 -> P2 -> P1P1.
g = toRepresentation(g, GroupElement.Representation.P2);
h = toRepresentation(g, GroupElement.Representation.P1P1);
Assert.assertThat(g, IsEqual.equalTo(h));
}
for (int i=0; i<10; i++) {
// Arrange:
final GroupElement g = MathUtils.getRandomGroupElement();
// Act:
final GroupElement h = MathUtils.scalarMultiplyGroupElement(g, curve.getField().ZERO);
// Assert:
Assert.assertThat(curve.getZero(GroupElement.Representation.P3), IsEqual.equalTo(h));
}
}
// End TODO BR: Remove when finished!
}

View file

@ -1,103 +0,0 @@
package org.xbib.io.sshd.eddsa.math;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.xbib.io.sshd.eddsa.Utils;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveSpec;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveTable;
/**
*
*/
public class PrecomputationTestVectors {
// Test files were generated using base.py and base2.py from ref10
// (by printing hex(x%q) instead of the radix-255 representation).
static GroupElement[][] testPrecmp = getPrecomputation("basePrecmp");
static GroupElement[] testDblPrecmp = getDoublePrecomputation("baseDblPrecmp");
public static GroupElement[][] getPrecomputation(String fileName) {
EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
Curve curve = ed25519.getCurve();
Field field = curve.getField();
GroupElement[][] precmp = new GroupElement[32][8];
BufferedReader file = null;
int row = 0, col = 0;
try {
InputStream is = PrecomputationTestVectors.class.getResourceAsStream(fileName);
if (is == null)
throw new IOException("Resource not found: " + fileName);
file = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = file.readLine()) != null) {
if (line.equals(" },"))
col += 1;
else if (line.equals("},")) {
col = 0;
row += 1;
} else if (line.startsWith(" { ")) {
String ypxStr = line.substring(4, line.lastIndexOf(' '));
FieldElement ypx = field.fromByteArray(
Utils.hexToBytes(ypxStr));
line = file.readLine();
String ymxStr = line.substring(4, line.lastIndexOf(' '));
FieldElement ymx = field.fromByteArray(
Utils.hexToBytes(ymxStr));
line = file.readLine();
String xy2dStr = line.substring(4, line.lastIndexOf(' '));
FieldElement xy2d = field.fromByteArray(
Utils.hexToBytes(xy2dStr));
precmp[row][col] = GroupElement.precomp(curve,
ypx, ymx, xy2d);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (file != null) try { file.close(); } catch (IOException e) {}
}
return precmp;
}
public static GroupElement[] getDoublePrecomputation(String fileName) {
EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
Curve curve = ed25519.getCurve();
Field field = curve.getField();
GroupElement[] dblPrecmp = new GroupElement[8];
BufferedReader file = null;
int row = 0;
try {
InputStream is = PrecomputationTestVectors.class.getResourceAsStream(fileName);
if (is == null)
throw new IOException("Resource not found: " + fileName);
file = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = file.readLine()) != null) {
if (line.equals(" },")) {
row += 1;
} else if (line.startsWith(" { ")) {
String ypxStr = line.substring(4, line.lastIndexOf(' '));
FieldElement ypx = field.fromByteArray(
Utils.hexToBytes(ypxStr));
line = file.readLine();
String ymxStr = line.substring(4, line.lastIndexOf(' '));
FieldElement ymx = field.fromByteArray(
Utils.hexToBytes(ymxStr));
line = file.readLine();
String xy2dStr = line.substring(4, line.lastIndexOf(' '));
FieldElement xy2d = field.fromByteArray(
Utils.hexToBytes(xy2dStr));
dblPrecmp[row] = GroupElement.precomp(curve,
ypx, ymx, xy2d);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (file != null) try { file.close(); } catch (IOException e) {}
}
return dblPrecmp;
}
}

View file

@ -1,104 +0,0 @@
package org.xbib.io.sshd.eddsa.math.bigint;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.math.BigInteger;
import java.util.Random;
import org.xbib.io.sshd.eddsa.Utils;
import org.xbib.io.sshd.eddsa.math.Field;
import org.xbib.io.sshd.eddsa.math.FieldElement;
import org.xbib.io.sshd.eddsa.math.MathUtils;
import org.xbib.io.sshd.eddsa.math.AbstractFieldElementTest;
import org.junit.Test;
/**
*
*/
public class BigIntegerFieldElementTest extends AbstractFieldElementTest {
static final byte[] BYTES_ZERO = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000");
static final byte[] BYTES_ONE = Utils.hexToBytes("0100000000000000000000000000000000000000000000000000000000000000");
static final byte[] BYTES_TEN = Utils.hexToBytes("0a00000000000000000000000000000000000000000000000000000000000000");
static final Field ed25519Field = new Field(
256, // b
Utils.hexToBytes("edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), // q
new BigIntegerLittleEndianEncoding());
static final FieldElement ZERO = new BigIntegerFieldElement(ed25519Field, BigInteger.ZERO);
static final FieldElement ONE = new BigIntegerFieldElement(ed25519Field, BigInteger.ONE);
static final FieldElement TWO = new BigIntegerFieldElement(ed25519Field, BigInteger.valueOf(2));
protected FieldElement getRandomFieldElement() {
BigInteger r;
Random rnd = new Random();
do {
r = new BigInteger(255, rnd);
} while (r.compareTo(getQ()) >= 0);
return new BigIntegerFieldElement(ed25519Field, r);
}
protected BigInteger toBigInteger(FieldElement f) {
return ((BigIntegerFieldElement)f).bi;
}
protected BigInteger getQ() {
return MathUtils.getQ();
}
protected Field getField() {
return ed25519Field;
}
/**
* Test method for {@link BigIntegerFieldElement#BigIntegerFieldElement(Field, BigInteger)}.
*/
@Test
public void testFieldElementBigInteger() {
assertThat(new BigIntegerFieldElement(ed25519Field, BigInteger.ZERO).bi, is(BigInteger.ZERO));
assertThat(new BigIntegerFieldElement(ed25519Field, BigInteger.ONE).bi, is(BigInteger.ONE));
assertThat(new BigIntegerFieldElement(ed25519Field, BigInteger.valueOf(2)).bi, is(BigInteger.valueOf(2)));
}
/**
* Test method for {@link FieldElement#toByteArray()}.
*/
@Test
public void testToByteArray() {
byte[] zero = ZERO.toByteArray();
assertThat(zero.length, is(equalTo(BYTES_ZERO.length)));
assertThat(zero, is(equalTo(BYTES_ZERO)));
byte[] one = ONE.toByteArray();
assertThat(one.length, is(equalTo(BYTES_ONE.length)));
assertThat(one, is(equalTo(BYTES_ONE)));
byte[] ten = new BigIntegerFieldElement(ed25519Field, BigInteger.TEN).toByteArray();
assertThat(ten.length, is(equalTo(BYTES_TEN.length)));
assertThat(ten, is(equalTo(BYTES_TEN)));
}
// region isNonZero
protected FieldElement getZeroFieldElement() {
return ZERO;
}
protected FieldElement getNonZeroFieldElement() {
return TWO;
}
// endregion
/**
* Test method for {@link FieldElement#equals(Object)}.
*/
@Test
public void testEqualsObject() {
assertThat(new BigIntegerFieldElement(ed25519Field, BigInteger.ZERO), is(equalTo(ZERO)));
assertThat(new BigIntegerFieldElement(ed25519Field, BigInteger.valueOf(1000)), is(equalTo(new BigIntegerFieldElement(ed25519Field, BigInteger.valueOf(1000)))));
assertThat(ONE, is(not(equalTo(TWO))));
}
}

View file

@ -1,61 +0,0 @@
package org.xbib.io.sshd.eddsa.math.bigint;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.math.BigInteger;
import org.xbib.io.sshd.eddsa.Utils;
import org.xbib.io.sshd.eddsa.math.Field;
import org.xbib.io.sshd.eddsa.math.ScalarOps;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveSpec;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveTable;
import org.junit.Test;
/**
*
*/
public class BigIntegerScalarOpsTest {
static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
static final Field ed25519Field = ed25519.getCurve().getField();
/**
* Test method for {@link BigIntegerScalarOps#reduce(byte[])}.
*/
@Test
public void testReduce() {
ScalarOps sc = new BigIntegerScalarOps(ed25519Field,
new BigInteger("5"));
assertThat(sc.reduce(new byte[] {7}),
is(equalTo(Utils.hexToBytes("0200000000000000000000000000000000000000000000000000000000000000"))));
ScalarOps sc2 = new BigIntegerScalarOps(ed25519Field,
new BigInteger("7237005577332262213973186563042994240857116359379907606001950938285454250989"));
// Example from test case 1
byte[] r = Utils.hexToBytes("b6b19cd8e0426f5983fa112d89a143aa97dab8bc5deb8d5b6253c928b65272f4044098c2a990039cde5b6a4818df0bfb6e40dc5dee54248032962323e701352d");
assertThat(sc2.reduce(r), is(equalTo(Utils.hexToBytes("f38907308c893deaf244787db4af53682249107418afc2edc58f75ac58a07404"))));
}
/**
* Test method for {@link BigIntegerScalarOps#multiplyAndAdd(byte[], byte[], byte[])}.
*/
@Test
public void testMultiplyAndAdd() {
ScalarOps sc = new BigIntegerScalarOps(ed25519Field,
new BigInteger("5"));
assertThat(sc.multiplyAndAdd(new byte[] {7}, new byte[] {2}, new byte[] {5}),
is(equalTo(Utils.hexToBytes("0400000000000000000000000000000000000000000000000000000000000000"))));
ScalarOps sc2 = new BigIntegerScalarOps(ed25519Field,
new BigInteger("7237005577332262213973186563042994240857116359379907606001950938285454250989"));
// Example from test case 1
byte[] h = Utils.hexToBytes("86eabc8e4c96193d290504e7c600df6cf8d8256131ec2c138a3e7e162e525404");
byte[] a = Utils.hexToBytes("307c83864f2833cb427a2ef1c00a013cfdff2768d980c0a3a520f006904de94f");
byte[] r = Utils.hexToBytes("f38907308c893deaf244787db4af53682249107418afc2edc58f75ac58a07404");
byte[] S = Utils.hexToBytes("5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b");
assertThat(sc2.multiplyAndAdd(h, a, r), is(equalTo(S)));
}
}

View file

@ -1,78 +0,0 @@
package org.xbib.io.sshd.eddsa.math.ed25519;
import org.xbib.io.sshd.eddsa.math.AbstractFieldElementTest;
import org.xbib.io.sshd.eddsa.math.Field;
import org.xbib.io.sshd.eddsa.math.FieldElement;
import org.xbib.io.sshd.eddsa.math.MathUtils;
import org.hamcrest.core.IsEqual;
import org.junit.Assert;
import org.junit.Test;
import java.math.BigInteger;
/**
* Tests rely on the BigInteger class.
*/
public class Ed25519FieldElementTest extends AbstractFieldElementTest {
protected FieldElement getRandomFieldElement() {
return MathUtils.getRandomFieldElement();
}
protected BigInteger toBigInteger(FieldElement f) {
return MathUtils.toBigInteger(f);
}
protected BigInteger getQ() {
return MathUtils.getQ();
}
protected Field getField() {
return MathUtils.getField();
}
@Test
public void canConstructFieldElementFromArrayWithCorrectLength() {
new Ed25519FieldElement(MathUtils.getField(), new int[10]);
}
@Test (expected = IllegalArgumentException.class)
public void cannotConstructFieldElementFromArrayWithIncorrectLength() {
new Ed25519FieldElement(MathUtils.getField(), new int[9]);
}
@Test (expected = IllegalArgumentException.class)
public void cannotConstructFieldElementWithoutField() {
new Ed25519FieldElement(null, new int[9]);
}
protected FieldElement getZeroFieldElement() {
return new Ed25519FieldElement(MathUtils.getField(), new int[10]);
}
protected FieldElement getNonZeroFieldElement() {
final int[] t = new int[10];
t[0] = 5;
return new Ed25519FieldElement(MathUtils.getField(), t);
}
@Test
public void toStringReturnsCorrectRepresentation() {
final byte[] bytes = new byte[32];
for (int i=0; i<32; i++) {
bytes[i] = (byte)(i+1);
}
final FieldElement f = MathUtils.getField().getEncoding().decode(bytes);
final String fAsString = f.toString();
final StringBuilder builder = new StringBuilder();
builder.append("[Ed25519FieldElement val=");
for (byte b : bytes) {
builder.append(String.format("%02x", b));
}
builder.append("]");
Assert.assertThat(fAsString, IsEqual.equalTo(builder.toString()));
}
}

View file

@ -1,107 +0,0 @@
package org.xbib.io.sshd.eddsa.math.ed25519;
import org.xbib.io.sshd.eddsa.math.FieldElement;
import org.xbib.io.sshd.eddsa.math.MathUtils;
import org.hamcrest.core.IsEqual;
import org.junit.Assert;
import org.junit.Test;
import java.math.BigInteger;
import java.security.SecureRandom;
/**
* Tests rely on the BigInteger class.
*/
public class Ed25519LittleEndianEncodingTest {
private static final SecureRandom random = new SecureRandom();
@Test
public void encodeReturnsCorrectByteArrayForSimpleFieldElements() {
// Arrange:
final int[] t1 = new int[10];
final int[] t2 = new int[10];
t2[0] = 1;
final FieldElement fieldElement1 = new Ed25519FieldElement(MathUtils.getField(), t1);
final FieldElement fieldElement2 = new Ed25519FieldElement(MathUtils.getField(), t2);
// Act:
final byte[] bytes1 = MathUtils.getField().getEncoding().encode(fieldElement1);
final byte[] bytes2 = MathUtils.getField().getEncoding().encode(fieldElement2);
// Assert:
Assert.assertThat(bytes1, IsEqual.equalTo(MathUtils.toByteArray(BigInteger.ZERO)));
Assert.assertThat(bytes2, IsEqual.equalTo(MathUtils.toByteArray(BigInteger.ONE)));
}
@Test
public void encodeReturnsCorrectByteArray() {
for (int i=0; i<10000; i++){
// Arrange:
final int[] t = new int[10];
for (int j=0; j<10; j++) {
t[j] = random.nextInt(1 << 28) - (1 << 27);
}
final FieldElement fieldElement1 = new Ed25519FieldElement(MathUtils.getField(), t);
final BigInteger b = MathUtils.toBigInteger(t);
// Act:
final byte[] bytes = MathUtils.getField().getEncoding().encode(fieldElement1);
// Assert:
Assert.assertThat(bytes, IsEqual.equalTo(MathUtils.toByteArray(b.mod(MathUtils.getQ()))));
}
}
@Test
public void decodeReturnsCorrectFieldElementForSimpleByteArrays() {
// Arrange:
final byte[] bytes1 = new byte[32];
final byte[] bytes2 = new byte[32];
bytes2[0] = 1;
// Act:
final Ed25519FieldElement f1 = (Ed25519FieldElement)MathUtils.getField().getEncoding().decode(bytes1);
final Ed25519FieldElement f2 = (Ed25519FieldElement)MathUtils.getField().getEncoding().decode(bytes2);
final BigInteger b1 = MathUtils.toBigInteger(f1.t);
final BigInteger b2 = MathUtils.toBigInteger(f2.t);
// Assert:
Assert.assertThat(b1, IsEqual.equalTo(BigInteger.ZERO));
Assert.assertThat(b2, IsEqual.equalTo(BigInteger.ONE));
}
@Test
public void decodeReturnsCorrectFieldElement() {
for (int i=0; i<10000; i++) {
// Arrange:
final byte[] bytes = new byte[32];
random.nextBytes(bytes);
bytes[31] = (byte)(bytes[31] & 0x7f);
final BigInteger b1 = MathUtils.toBigInteger(bytes);
// Act:
final Ed25519FieldElement f = (Ed25519FieldElement)MathUtils.getField().getEncoding().decode(bytes);
final BigInteger b2 = MathUtils.toBigInteger(f.t).mod(MathUtils.getQ());
// Assert:
Assert.assertThat(b2, IsEqual.equalTo(b1));
}
}
@Test
public void isNegativeReturnsCorrectResult() {
for (int i=0; i<10000; i++) {
// Arrange:
final int[] t = new int[10];
for (int j=0; j<10; j++) {
t[j] = random.nextInt(1 << 28) - (1 << 27);
}
final boolean isNegative = MathUtils.toBigInteger(t).mod(MathUtils.getQ()).mod(new BigInteger("2")).equals(BigInteger.ONE);
final FieldElement f = new Ed25519FieldElement(MathUtils.getField(), t);
// Assert:
Assert.assertThat(MathUtils.getField().getEncoding().isNegative(f), IsEqual.equalTo(isNegative));
}
}
}

View file

@ -1,72 +0,0 @@
package org.xbib.io.sshd.eddsa.math.ed25519;
import org.xbib.io.sshd.eddsa.Utils;
import org.xbib.io.sshd.eddsa.math.MathUtils;
import org.hamcrest.core.IsEqual;
import org.junit.Assert;
import org.junit.Test;
import java.math.BigInteger;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
/**
* Additional tests by the NEM project team.
*
*/
public class Ed25519ScalarOpsTest {
private static final Ed25519ScalarOps scalarOps = new Ed25519ScalarOps();
/**
* Test method for {@link org.xbib.io.sshd.eddsa.math.bigint.BigIntegerScalarOps#reduce(byte[])}.
*/
@Test
public void testReduce() {
// Example from test case 1
byte[] r = Utils.hexToBytes("b6b19cd8e0426f5983fa112d89a143aa97dab8bc5deb8d5b6253c928b65272f4044098c2a990039cde5b6a4818df0bfb6e40dc5dee54248032962323e701352d");
assertThat(scalarOps.reduce(r), is(equalTo(Utils.hexToBytes("f38907308c893deaf244787db4af53682249107418afc2edc58f75ac58a07404"))));
}
@Test
public void reduceReturnsExpectedResult() {
for (int i=0; i<1000; i++) {
final byte[] bytes = MathUtils.getRandomByteArray(64);
final byte[] reduced1 = scalarOps.reduce(bytes);
final byte[] reduced2 = MathUtils.reduceModGroupOrder(bytes);
Assert.assertThat(MathUtils.toBigInteger(reduced1).compareTo(MathUtils.getGroupOrder()), IsEqual.equalTo(-1));
Assert.assertThat(MathUtils.toBigInteger(reduced1).compareTo(new BigInteger("-1")), IsEqual.equalTo(1));
Assert.assertThat(reduced1, IsEqual.equalTo(reduced2));
}
}
/**
* Test method for {@link org.xbib.io.sshd.eddsa.math.bigint.BigIntegerScalarOps#multiplyAndAdd(byte[], byte[], byte[])}.
*/
@Test
public void testMultiplyAndAdd() {
byte[] h = Utils.hexToBytes("86eabc8e4c96193d290504e7c600df6cf8d8256131ec2c138a3e7e162e525404");
byte[] a = Utils.hexToBytes("307c83864f2833cb427a2ef1c00a013cfdff2768d980c0a3a520f006904de94f");
byte[] r = Utils.hexToBytes("f38907308c893deaf244787db4af53682249107418afc2edc58f75ac58a07404");
byte[] S = Utils.hexToBytes("5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b");
assertThat(scalarOps.multiplyAndAdd(h, a, r), is(equalTo(S)));
}
@Test
public void multiplyAndAddReturnsExpectedResult() {
for (int i=0; i<1000; i++) {
final byte[] bytes1 = MathUtils.getRandomByteArray(32);
final byte[] bytes2 = MathUtils.getRandomByteArray(32);
final byte[] bytes3 = MathUtils.getRandomByteArray(32);
final byte[] result1 = scalarOps.multiplyAndAdd(bytes1, bytes2, bytes3);
final byte[] result2 = MathUtils.multiplyAndAddModGroupOrder(bytes1, bytes2, bytes3);
Assert.assertThat(MathUtils.toBigInteger(result1).compareTo(MathUtils.getGroupOrder()), IsEqual.equalTo(-1));
Assert.assertThat(MathUtils.toBigInteger(result1).compareTo(new BigInteger("-1")), IsEqual.equalTo(1));
Assert.assertThat(result1, IsEqual.equalTo(result2));
}
}
}

View file

@ -1,24 +0,0 @@
package org.xbib.io.sshd.eddsa.spec;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
/**
*
*/
public class EdDSANamedCurveTableTest {
/**
* Ensure curve names are case-inspecific
*/
@Test
public void curveNamesAreCaseInspecific() {
EdDSANamedCurveSpec mixed = EdDSANamedCurveTable.getByName("Ed25519");
EdDSANamedCurveSpec lower = EdDSANamedCurveTable.getByName("ed25519");
EdDSANamedCurveSpec upper = EdDSANamedCurveTable.getByName("ED25519");
assertThat(lower, is(equalTo(mixed)));
assertThat(upper, is(equalTo(mixed)));
}
}

View file

@ -1,59 +0,0 @@
package org.xbib.io.sshd.eddsa.spec;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import org.xbib.io.sshd.eddsa.Utils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/**
*
*/
public class EdDSAPrivateKeySpecTest {
static final byte[] ZERO_SEED = Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000");
static final byte[] ZERO_H = Utils.hexToBytes("5046adc1dba838867b2bbbfdd0c3423e58b57970b5267a90f57960924a87f1960a6a85eaa642dac835424b5d7c8d637c00408c7a73da672b7f498521420b6dd3");
static final byte[] ZERO_PK = Utils.hexToBytes("3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29");
static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
@Rule
public ExpectedException exception = ExpectedException.none();
/**
* Test method for {@link EdDSAPrivateKeySpec#EdDSAPrivateKeySpec(byte[], EdDSAParameterSpec)}.
*/
@Test
public void testEdDSAPrivateKeySpecFromSeed() {
EdDSAPrivateKeySpec key = new EdDSAPrivateKeySpec(ZERO_SEED, ed25519);
assertThat(key.getSeed(), is(equalTo(ZERO_SEED)));
assertThat(key.getH(), is(equalTo(ZERO_H)));
assertThat(key.getA().toByteArray(), is(equalTo(ZERO_PK)));
}
@Test
public void incorrectSeedLengthThrows() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage("seed length is wrong");
EdDSAPrivateKeySpec key = new EdDSAPrivateKeySpec(new byte[2], ed25519);
}
/**
* Test method for {@link EdDSAPrivateKeySpec#EdDSAPrivateKeySpec(EdDSAParameterSpec, byte[])}.
*/
@Test
public void testEdDSAPrivateKeySpecFromH() {
EdDSAPrivateKeySpec key = new EdDSAPrivateKeySpec(ed25519, ZERO_H);
assertThat(key.getSeed(), is(nullValue()));
assertThat(key.getH(), is(equalTo(ZERO_H)));
assertThat(key.getA().toByteArray(), is(equalTo(ZERO_PK)));
}
@Test
public void incorrectHashLengthThrows() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage("hash length is wrong");
EdDSAPrivateKeySpec key = new EdDSAPrivateKeySpec(ed25519, new byte[2]);
}
}

View file

@ -1,40 +0,0 @@
{
{ 853b8cf5c693bc2f190e8cfbc62d93cfc2423d6498480b2765bad4333a9dcf07 },
{ 3e9140d70539109db3be40d1059f39fd098a8f683484c1a56712f898922ffd44 },
{ 68aa7a870512c9ab9ec4aacc23e8d9268c5943ddcb7d1b5aa8650c9f687b116f },
},
{
{ 3097ee4ca8b025af8a4b86e830845a023267019f02501bc1f4f8809a1b4e167a },
{ 65d2fca4e81f61567dbac1e5fd53d33bbdd64b211af3318162da5b558715b92a },
{ 89d8d00d3f93ae1462da351c222394584cdbf28c45e570d1c6b4b912af26285a },
},
{
{ 33bba50844bc12a202ed5ec7c348508d44ecbf5a0ceb1bddeb06e246f1cc4529 },
{ bad647a4c382917fb729274bd11400d587a064b81cf13ce3f3551beb737e4a15 },
{ 85822a81f1dbbbbcfcd1bdd007080e272da7bd1b0b671bb49ab63b6b69beaa43 },
},
{
{ bfa34e94d05c1a6bd2c09db33a357074492e54288252b2717e923c2869ea1b46 },
{ b12132aa9a2c6fbaa723ba3b5321a06c3a2c19924f76ea9de017532e5ddd6e1d },
{ a2b3b801c86d83f19aa43e05475f03b3f3ad7758ba419c52a7900f6a1cbb9f7a },
},
{
{ 2f63a8a68a672e9bc546bc516f9e50a6b5f586c6c933b2ce597fdd8a33edb934 },
{ 64809d037e216ef39b4120f5b681a09844b05ee708c6cb968f9cdcfa515ac049 },
{ 1baf4590bfe8b4062fd219a7e883ffe216cfd49329fcf6aa068b001b0272c173 },
},
{
{ de2a808a8400bf2f272e3002cffed9e50634701771843e11af8f6d54e2aa7542 },
{ 48438649025b5f318183087769b3d63e95eb8d6a5575a0a37fc7d5298059ab18 },
{ e98960fdc52c2bd8a4e48232a1b41e0322861ab59911314448f93db52255c63d },
},
{
{ 6d7f00a222c270bfdbdebcb59ab384bf07ba07fb120e7a5341f246c3eed74f23 },
{ 93bf7f323b016f506b6f779bc9ebfcae6859adaa32b2129da72460172d886702 },
{ 78a32e7319a1605371d48ddfb1e6372433e5a791f837efa2637809aafda67b49 },
},
{
{ a0eacf1303ccce246d249c188dc24886d0d4f2c1fabdbd2d2be72df11729e261 },
{ 0bcf8c4686cd0b04d610992aa49b82d39251b20708300875bf5ed01842cdb543 },
{ 16b5d09b2f769a5deede3f374eaf38eb7042d6937d5a2e0342d8e40a21611d51 },
},

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
dependencies { dependencies {
api project(':files-api') api project(':files-api')
api project(':files-ftp') api project(':files-ftp')
testImplementation "org.mockftpserver:MockFtpServer:${project.property('mockftpserver.version')}" testImplementation libs.mockftpserver
testImplementation "org.junit.jupiter:junit-jupiter-params:${project.property('junit.version')}" testImplementation libs.junit.jupiter.params
testImplementation "org.mockito:mockito-core:${project.property('mockito.version')}" testImplementation libs.mockito.core
testImplementation "org.mockito:mockito-junit-jupiter:${project.property('mockito.version')}" testImplementation libs.mockito.junit.jupiter
} }

View file

@ -1,5 +1,5 @@
dependencies { dependencies {
api project(':files-api') api project(':files-api')
api project(':files-sftp') api project(':files-sftp')
testImplementation "org.bouncycastle:bcpkix-jdk15on:${project.property('bouncycastle.version')}" testImplementation libs.net.security
} }

View file

@ -1,38 +1,24 @@
package org.apache.sshd.fs.test; package org.apache.sshd.fs.test;
import java.nio.file.Files;
import org.apache.sshd.client.ClientBuilder; import org.apache.sshd.client.ClientBuilder;
import org.apache.sshd.client.SshClient; import org.apache.sshd.client.SshClient;
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.fs.SftpFileSystem; import org.apache.sshd.fs.SftpFileSystem;
import org.apache.sshd.fs.SftpFileSystemProvider; import org.apache.sshd.fs.SftpFileSystemProvider;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.io.sshd.eddsa.EdDSASecurityProvider;
import java.net.URI; import java.net.URI;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.Provider;
import java.security.Security;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.ServiceLoader;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.xbib.net.security.PrivateKeyReader;
public class SFTPFileSystemTest { public class SFTPFileSystemTest {
private static final Logger logger = Logger.getLogger(SFTPFileSystemTest.class.getName()); private static final Logger logger = Logger.getLogger(SFTPFileSystemTest.class.getName());
static {
// load bouncy castle provider (and other security providers)
for (Provider p : ServiceLoader.load(Provider.class)) {
if (Security.getProvider(p.getName()) == null) {
Security.addProvider(p);
}
}
}
@Test @Test
public void init() throws Exception { public void init() throws Exception {
Map<String, String> env = new HashMap<>(); Map<String, String> env = new HashMap<>();
@ -40,12 +26,8 @@ public class SFTPFileSystemTest {
URI uri = URI.create("sftp://xbib.org"); URI uri = URI.create("sftp://xbib.org");
SshClient sshClient = ClientBuilder.builder().build(); SshClient sshClient = ClientBuilder.builder().build();
Path privateKey = Paths.get(System.getProperty("user.home") + "/.ssh/id_ed25519"); Path privateKey = Paths.get(System.getProperty("user.home") + "/.ssh/id_ed25519");
KeyPairProvider keyPairProvider = new FileKeyPairProvider(privateKey); PrivateKeyReader privateKeyReader = new PrivateKeyReader();
keyPairProvider.loadKeys(null).forEach(sshClient::addPublicKeyIdentity); KeyPair keyPair = privateKeyReader.generateFrom(Files.newInputStream(privateKey), null);
for (String keyType : keyPairProvider.getKeyTypes(null)) {
logger.info("found key type = " + keyType);
}
KeyPair keyPair = keyPairProvider.loadKey(null, "ssh-ed25519");
sshClient.addPublicKeyIdentity(keyPair); sshClient.addPublicKeyIdentity(keyPair);
sshClient.setNioWorkers(1); sshClient.setNioWorkers(1);
sshClient.start(); sshClient.start();

View file

@ -1,4 +1,3 @@
dependencies { dependencies {
api project(':files-eddsa') implementation libs.net.security
implementation "org.bouncycastle:bcpkix-jdk15on:${project.property('bouncycastle.version')}"
} }

View file

@ -78,10 +78,7 @@ module org.xbib.files.sftp {
exports org.apache.sshd.common.util.net; exports org.apache.sshd.common.util.net;
exports org.apache.sshd.common.util.security; exports org.apache.sshd.common.util.security;
exports org.apache.sshd.common.util.security.eddsa; exports org.apache.sshd.common.util.security.eddsa;
exports org.apache.sshd.common.util.security.bouncycastle;
exports org.apache.sshd.common.util.threads; exports org.apache.sshd.common.util.threads;
requires transitive org.xbib.eddsa; requires org.xbib.net.security;
requires org.bouncycastle.pkix;
requires org.bouncycastle.provider;
requires java.logging; requires java.logging;
} }

View file

@ -125,7 +125,6 @@ public class BaseBuilder<T extends AbstractFactoryManager, S extends BaseBuilder
BuiltinSignatures.nistp521, BuiltinSignatures.nistp521,
BuiltinSignatures.ed25519, BuiltinSignatures.ed25519,
BuiltinSignatures.sk_ecdsa_sha2_nistp256, BuiltinSignatures.sk_ecdsa_sha2_nistp256,
BuiltinSignatures.sk_ssh_ed25519,
BuiltinSignatures.rsaSHA512, BuiltinSignatures.rsaSHA512,
BuiltinSignatures.rsaSHA256, BuiltinSignatures.rsaSHA256,
BuiltinSignatures.rsa)); BuiltinSignatures.rsa));

View file

@ -71,13 +71,11 @@ import org.apache.sshd.common.config.keys.impl.ECDSAPublicKeyEntryDecoder;
import org.apache.sshd.common.config.keys.impl.OpenSSHCertificateDecoder; import org.apache.sshd.common.config.keys.impl.OpenSSHCertificateDecoder;
import org.apache.sshd.common.config.keys.impl.RSAPublicKeyDecoder; import org.apache.sshd.common.config.keys.impl.RSAPublicKeyDecoder;
import org.apache.sshd.common.config.keys.impl.SkECDSAPublicKeyEntryDecoder; import org.apache.sshd.common.config.keys.impl.SkECDSAPublicKeyEntryDecoder;
import org.apache.sshd.common.config.keys.impl.SkED25519PublicKeyEntryDecoder;
import org.apache.sshd.common.digest.BuiltinDigests; import org.apache.sshd.common.digest.BuiltinDigests;
import org.apache.sshd.common.digest.Digest; import org.apache.sshd.common.digest.Digest;
import org.apache.sshd.common.digest.DigestFactory; import org.apache.sshd.common.digest.DigestFactory;
import org.apache.sshd.common.digest.DigestUtils; import org.apache.sshd.common.digest.DigestUtils;
import org.apache.sshd.common.keyprovider.KeyPairProvider; import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.u2f.SkED25519PublicKey;
import org.apache.sshd.common.u2f.SkEcdsaPublicKey; import org.apache.sshd.common.u2f.SkEcdsaPublicKey;
import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder; import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
@ -167,7 +165,6 @@ public final class KeyUtils {
} }
if (SecurityUtils.isEDDSACurveSupported()) { if (SecurityUtils.isEDDSACurveSupported()) {
registerPublicKeyEntryDecoder(SecurityUtils.getEDDSAPublicKeyEntryDecoder()); registerPublicKeyEntryDecoder(SecurityUtils.getEDDSAPublicKeyEntryDecoder());
registerPublicKeyEntryDecoder(SkED25519PublicKeyEntryDecoder.INSTANCE);
} }
} }
@ -1011,8 +1008,6 @@ public final class KeyUtils {
} else if ((k1 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm()) } else if ((k1 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm())
&& (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) { && (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) {
return SecurityUtils.compareEDDSAPPublicKeys(k1, k2); return SecurityUtils.compareEDDSAPPublicKeys(k1, k2);
} else if ((k1 instanceof SkED25519PublicKey) && (k2 instanceof SkED25519PublicKey)) {
return compareSkEd25519Keys(SkED25519PublicKey.class.cast(k1), SkED25519PublicKey.class.cast(k2));
} else { } else {
return false; // either key is null or not of same class return false; // either key is null or not of same class
} }
@ -1023,8 +1018,6 @@ public final class KeyUtils {
return recoverRSAPublicKey((RSAPrivateKey) key); return recoverRSAPublicKey((RSAPrivateKey) key);
} else if (key instanceof DSAPrivateKey) { } else if (key instanceof DSAPrivateKey) {
return recoverDSAPublicKey((DSAPrivateKey) key); return recoverDSAPublicKey((DSAPrivateKey) key);
} else if ((key != null) && SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) {
return SecurityUtils.recoverEDDSAPublicKey(key);
} else { } else {
return null; return null;
} }
@ -1184,16 +1177,4 @@ public final class KeyUtils {
&& compareECKeys(k1.getDelegatePublicKey(), k2.getDelegatePublicKey()); && compareECKeys(k1.getDelegatePublicKey(), k2.getDelegatePublicKey());
} }
} }
public static boolean compareSkEd25519Keys(SkED25519PublicKey k1, SkED25519PublicKey k2) {
if (Objects.equals(k1, k2)) {
return true;
} else if (k1 == null || k2 == null) {
return false; // both null is covered by Objects#equals
} else {
return Objects.equals(k1.getAppName(), k2.getAppName())
&& Objects.equals(k1.isNoTouchRequired(), k2.isNoTouchRequired())
&& SecurityUtils.compareEDDSAPPublicKeys(k1.getDelegatePublicKey(), k2.getDelegatePublicKey());
}
}
} }

View file

@ -1,110 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.config.keys.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import org.apache.sshd.common.config.keys.KeyEntryResolver;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.u2f.SkED25519PublicKey;
import org.apache.sshd.common.util.security.eddsa.Ed25519PublicKeyDecoder;
import org.xbib.io.sshd.eddsa.EdDSAPublicKey;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class SkED25519PublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<SkED25519PublicKey, PrivateKey> {
public static final String KEY_TYPE = "sk-ssh-ed25519@openssh.com";
public static final int MAX_APP_NAME_LENGTH = 1024;
public static final SkED25519PublicKeyEntryDecoder INSTANCE = new SkED25519PublicKeyEntryDecoder();
private static final String NO_TOUCH_REQUIRED_HEADER = "no-touch-required";
public SkED25519PublicKeyEntryDecoder() {
super(SkED25519PublicKey.class, PrivateKey.class, Collections.singleton(KEY_TYPE));
}
@Override
public SkED25519PublicKey decodePublicKey(
SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
throws IOException, GeneralSecurityException {
if (!KEY_TYPE.equals(keyType)) {
throw new InvalidKeySpecException("Invalid keyType: " + keyType);
}
boolean noTouchRequired = parseBooleanHeader(headers, NO_TOUCH_REQUIRED_HEADER, false);
EdDSAPublicKey edDSAPublicKey
= Ed25519PublicKeyDecoder.INSTANCE.decodePublicKey(session, KeyPairProvider.SSH_ED25519, keyData, headers);
String appName = KeyEntryResolver.decodeString(keyData, MAX_APP_NAME_LENGTH);
return new SkED25519PublicKey(appName, noTouchRequired, edDSAPublicKey);
}
@Override
public SkED25519PublicKey clonePublicKey(SkED25519PublicKey key) {
if (key == null) {
return null;
}
return new SkED25519PublicKey(key.getAppName(), key.isNoTouchRequired(), key.getDelegatePublicKey());
}
@Override
public String encodePublicKey(OutputStream s, SkED25519PublicKey key) throws IOException {
Objects.requireNonNull(key, "No public key provided");
KeyEntryResolver.encodeString(s, KEY_TYPE);
byte[] seed = Ed25519PublicKeyDecoder.getSeedValue(key.getDelegatePublicKey());
KeyEntryResolver.writeRLEBytes(s, seed);
KeyEntryResolver.encodeString(s, key.getAppName());
return KEY_TYPE;
}
@Override
public PrivateKey clonePrivateKey(PrivateKey key) {
throw new UnsupportedOperationException("Private key operations are not supported for security keys.");
}
@Override
public KeyFactory getKeyFactoryInstance() {
throw new UnsupportedOperationException("Private key operations are not supported for security keys.");
}
@Override
public KeyPair generateKeyPair(int keySize) {
throw new UnsupportedOperationException("Private key operations are not supported for security keys.");
}
@Override
public KeyPairGenerator getKeyPairGenerator() {
throw new UnsupportedOperationException("Private key operations are not supported for security keys.");
}
}

View file

@ -39,7 +39,6 @@ import org.apache.sshd.common.cipher.ECCurves;
import org.apache.sshd.common.config.NamedFactoriesListParseResult; import org.apache.sshd.common.config.NamedFactoriesListParseResult;
import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.config.keys.impl.SkECDSAPublicKeyEntryDecoder; import org.apache.sshd.common.config.keys.impl.SkECDSAPublicKeyEntryDecoder;
import org.apache.sshd.common.config.keys.impl.SkED25519PublicKeyEntryDecoder;
import org.apache.sshd.common.keyprovider.KeyPairProvider; import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.ValidateUtils;
@ -246,17 +245,6 @@ public enum BuiltinSignatures implements SignatureFactory {
return SecurityUtils.getEDDSASigner(); return SecurityUtils.getEDDSASigner();
} }
@Override
public boolean isSupported() {
return SecurityUtils.isEDDSACurveSupported();
}
},
sk_ssh_ed25519(SkED25519PublicKeyEntryDecoder.KEY_TYPE) {
@Override
public Signature create() {
return new SignatureSkED25519();
}
@Override @Override
public boolean isSupported() { public boolean isSupported() {
return SecurityUtils.isEDDSACurveSupported(); return SecurityUtils.isEDDSACurveSupported();

View file

@ -1,47 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.signature;
import org.apache.sshd.common.config.keys.impl.SkED25519PublicKeyEntryDecoder;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.util.security.SecurityUtils;
public class SignatureSkED25519 extends AbstractSecurityKeySignature {
public static final String ALGORITHM = "ED25519-SK";
public SignatureSkED25519() {
super(SkED25519PublicKeyEntryDecoder.KEY_TYPE);
}
@Override
public String getAlgorithm() {
return ALGORITHM;
}
@Override
protected String getSignatureKeyType() {
return KeyPairProvider.SSH_ED25519;
}
@Override
protected Signature getDelegateSignature() {
return SecurityUtils.getEDDSASigner();
}
}

View file

@ -1,77 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.u2f;
import org.xbib.io.sshd.eddsa.EdDSAPublicKey;
public class SkED25519PublicKey implements SecurityKeyPublicKey<EdDSAPublicKey> {
public static final String ALGORITHM = "ED25519-SK";
private static final long serialVersionUID = 4587115316266869640L;
private final String appName;
private final boolean noTouchRequired;
private final EdDSAPublicKey delegatePublicKey;
public SkED25519PublicKey(String appName, boolean noTouchRequired, EdDSAPublicKey delegatePublicKey) {
this.appName = appName;
this.noTouchRequired = noTouchRequired;
this.delegatePublicKey = delegatePublicKey;
}
@Override
public String getAlgorithm() {
return ALGORITHM;
}
@Override
public String getFormat() {
return null;
}
@Override
public byte[] getEncoded() {
return null;
}
@Override
public String getAppName() {
return appName;
}
@Override
public boolean isNoTouchRequired() {
return noTouchRequired;
}
@Override
public EdDSAPublicKey getDelegatePublicKey() {
return delegatePublicKey;
}
@Override
public String toString() {
return getClass().getSimpleName()
+ "[appName=" + getAppName()
+ ", noTouchRequired=" + isNoTouchRequired()
+ ", delegatePublicKey=" + getDelegatePublicKey()
+ "]";
}
}

View file

@ -60,8 +60,7 @@ public interface BufferPublicKeyParser<PUB extends PublicKey> {
ECBufferPublicKeyParser.INSTANCE, ECBufferPublicKeyParser.INSTANCE,
SkECBufferPublicKeyParser.INSTANCE, SkECBufferPublicKeyParser.INSTANCE,
ED25519BufferPublicKeyParser.INSTANCE, ED25519BufferPublicKeyParser.INSTANCE,
OpenSSHCertPublicKeyParser.INSTANCE, OpenSSHCertPublicKeyParser.INSTANCE));
SkED25519BufferPublicKeyParser.INSTANCE));
/** /**
* @param keyType The key type - e.g., &quot;ssh-rsa&quot, &quot;ssh-dss&quot; * @param keyType The key type - e.g., &quot;ssh-rsa&quot, &quot;ssh-dss&quot;

View file

@ -1,49 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.util.buffer.keys;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import org.apache.sshd.common.config.keys.impl.SkED25519PublicKeyEntryDecoder;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.u2f.SkED25519PublicKey;
import org.apache.sshd.common.util.buffer.Buffer;
import org.xbib.io.sshd.eddsa.EdDSAPublicKey;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class SkED25519BufferPublicKeyParser extends AbstractBufferPublicKeyParser<SkED25519PublicKey> {
public static final SkED25519BufferPublicKeyParser INSTANCE = new SkED25519BufferPublicKeyParser();
public SkED25519BufferPublicKeyParser() {
super(SkED25519PublicKey.class, SkED25519PublicKeyEntryDecoder.KEY_TYPE);
}
@Override
public SkED25519PublicKey getRawPublicKey(String keyType, Buffer buffer) throws GeneralSecurityException {
// The "sk-ssh-ed25519@openssh.com" keytype has the same format as the "ssh-ed25519" keytype with an appname on
// the end
PublicKey publicKey = ED25519BufferPublicKeyParser.INSTANCE.getRawPublicKey(KeyPairProvider.SSH_ED25519, buffer);
String appName = buffer.getString();
return new SkED25519PublicKey(appName, false, (EdDSAPublicKey) publicKey);
}
}

View file

@ -21,7 +21,6 @@ package org.apache.sshd.common.util.security;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.file.Path;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.Key; import java.security.Key;
@ -72,8 +71,6 @@ import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer; import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleKeyPairResourceParser;
import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleRandomFactory;
import org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderUtils; import org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderUtils;
import org.apache.sshd.common.util.threads.ThreadUtils; import org.apache.sshd.common.util.threads.ThreadUtils;
@ -127,17 +124,7 @@ public final class SecurityUtils {
*/ */
public static final String SECURITY_PROVIDER_REGISTRARS = "org.apache.sshd.security.registrars"; public static final String SECURITY_PROVIDER_REGISTRARS = "org.apache.sshd.security.registrars";
public static final List<String> DEFAULT_SECURITY_PROVIDER_REGISTRARS = Collections.unmodifiableList( public static final List<String> DEFAULT_SECURITY_PROVIDER_REGISTRARS = Collections.unmodifiableList(
Arrays.asList( Arrays.asList("org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderRegistrar"));
"org.apache.sshd.common.util.security.bouncycastle.BouncyCastleSecurityProviderRegistrar",
"org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderRegistrar"));
/**
* System property used to control whether to automatically register the {@code Bouncyastle} JCE provider
*
* @deprecated Please use &quot;org.apache.sshd.security.provider.BC.enabled&quot;
*/
@Deprecated
public static final String REGISTER_BOUNCY_CASTLE_PROP = "org.apache.sshd.registerBouncyCastle";
/** /**
* System property used to control whether Elliptic Curves are supported or not. If not set then the support is * System property used to control whether Elliptic Curves are supported or not. If not set then the support is
@ -146,16 +133,6 @@ public final class SecurityUtils {
*/ */
public static final String ECC_SUPPORTED_PROP = "org.apache.sshd.eccSupport"; public static final String ECC_SUPPORTED_PROP = "org.apache.sshd.eccSupport";
/**
* System property used to decide whether EDDSA curves are supported or not (in addition or even in spite of
* {@link #isEDDSACurveSupported()}). If not set or set to {@code true}, then the existence of the optional support
* classes determines the support.
*
* @deprecated Please use &quot;org.apache.sshd.security.provider.EdDSA.enabled&qupt;
*/
@Deprecated
public static final String EDDSA_SUPPORTED_PROP = "org.apache.sshd.eddsaSupport";
public static final String PROP_DEFAULT_SECURITY_PROVIDER = "org.apache.sshd.security.defaultProvider"; public static final String PROP_DEFAULT_SECURITY_PROVIDER = "org.apache.sshd.security.defaultProvider";
private static final AtomicInteger MIN_DHG_KEY_SIZE_HOLDER = new AtomicInteger(0); private static final AtomicInteger MIN_DHG_KEY_SIZE_HOLDER = new AtomicInteger(0);
@ -508,24 +485,12 @@ public final class SecurityUtils {
return ids; return ids;
} }
/* -------------------------------------------------------------------- */
public static KeyPairResourceParser getBouncycastleKeyPairResourceParser() {
ValidateUtils.checkTrue(isBouncyCastleRegistered(), "BouncyCastle not registered");
return BouncyCastleKeyPairResourceParser.INSTANCE;
}
/** /**
* @return If {@link #isBouncyCastleRegistered()} then a {@link BouncyCastleRandomFactory} instance, otherwise a * @return a {@link JceRandomFactory} one
* {@link JceRandomFactory} one
*/ */
public static RandomFactory getRandomFactory() { public static RandomFactory getRandomFactory() {
if (isBouncyCastleRegistered()) {
return BouncyCastleRandomFactory.INSTANCE;
} else {
return JceRandomFactory.INSTANCE; return JceRandomFactory.INSTANCE;
} }
}
///////////////////////////// ED25519 support /////////////////////////////// ///////////////////////////// ED25519 support ///////////////////////////////
@ -545,7 +510,6 @@ public final class SecurityUtils {
if (!isEDDSACurveSupported()) { if (!isEDDSACurveSupported()) {
throw new UnsupportedOperationException(EDDSA + " provider N/A"); throw new UnsupportedOperationException(EDDSA + " provider N/A");
} }
return EdDSASecurityProviderUtils.getEDDSAPublicKeyEntryDecoder(); return EdDSASecurityProviderUtils.getEDDSAPublicKeyEntryDecoder();
} }
@ -585,14 +549,6 @@ public final class SecurityUtils {
return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.compareEDDSAPrivateKeys(k1, k2) : false; return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.compareEDDSAPrivateKeys(k1, k2) : false;
} }
public static PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException {
if (!isEDDSACurveSupported()) {
throw new NoSuchAlgorithmException(EDDSA + " provider not supported");
}
return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(key);
}
public static PublicKey generateEDDSAPublicKey(String keyType, byte[] seed) throws GeneralSecurityException { public static PublicKey generateEDDSAPublicKey(String keyType, byte[] seed) throws GeneralSecurityException {
if (!KeyPairProvider.SSH_ED25519.equals(keyType)) { if (!KeyPairProvider.SSH_ED25519.equals(keyType)) {
throw new InvalidKeyException("Unsupported key type: " + keyType); throw new InvalidKeyException("Unsupported key type: " + keyType);
@ -637,8 +593,6 @@ public final class SecurityUtils {
throw new GeneralSecurityException("Full SSHD-440 implementation N/A"); throw new GeneralSecurityException("Full SSHD-440 implementation N/A");
} }
//////////////////////////////////////////////////////////////////////////
public static KeyPairResourceParser getKeyPairResourceParser() { public static KeyPairResourceParser getKeyPairResourceParser() {
KeyPairResourceParser parser; KeyPairResourceParser parser;
synchronized (KEYPAIRS_PARSER_HODLER) { synchronized (KEYPAIRS_PARSER_HODLER) {

View file

@ -1,176 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.util.security.bouncycastle;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ProtocolException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.NoSuchProviderException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.security.auth.login.CredentialException;
import javax.security.auth.login.FailedLoginException;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.FilePasswordProvider.ResourceDecodeResult;
import org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.common.util.security.SecurityProviderRegistrar;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class BouncyCastleKeyPairResourceParser extends AbstractKeyPairResourceParser {
public static final List<String> BEGINNERS = Collections.unmodifiableList(
Arrays.asList(
"BEGIN RSA PRIVATE KEY",
"BEGIN DSA PRIVATE KEY",
"BEGIN EC PRIVATE KEY"));
public static final List<String> ENDERS = Collections.unmodifiableList(
Arrays.asList(
"END RSA PRIVATE KEY",
"END DSA PRIVATE KEY",
"END EC PRIVATE KEY"));
public static final BouncyCastleKeyPairResourceParser INSTANCE = new BouncyCastleKeyPairResourceParser();
public BouncyCastleKeyPairResourceParser() {
super(BEGINNERS, ENDERS);
}
@Override
public Collection<KeyPair> extractKeyPairs(
SessionContext session, NamedResource resourceKey,
String beginMarker, String endMarker,
FilePasswordProvider passwordProvider,
List<String> lines, Map<String, String> headers)
throws IOException, GeneralSecurityException {
StringBuilder writer = new StringBuilder(beginMarker.length() + endMarker.length() + lines.size() * 80);
writer.append(beginMarker).append(IoUtils.EOL);
lines.forEach(l -> writer.append(l).append(IoUtils.EOL));
writer.append(endMarker).append(IoUtils.EOL);
String data = writer.toString();
byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
try (InputStream bais = new ByteArrayInputStream(dataBytes)) {
return extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, bais, headers);
}
}
@Override
public Collection<KeyPair> extractKeyPairs(
SessionContext session, NamedResource resourceKey,
String beginMarker, String endMarker,
FilePasswordProvider passwordProvider,
InputStream stream, Map<String, String> headers)
throws IOException, GeneralSecurityException {
KeyPair kp = loadKeyPair(session, resourceKey, stream, passwordProvider);
return (kp == null) ? Collections.emptyList() : Collections.singletonList(kp);
}
public static KeyPair loadKeyPair(
SessionContext session, NamedResource resourceKey, InputStream inputStream, FilePasswordProvider provider)
throws IOException, GeneralSecurityException {
try (PEMParser r = new PEMParser(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
Object o = r.readObject();
SecurityProviderRegistrar registrar = SecurityUtils.getRegisteredProvider(SecurityUtils.BOUNCY_CASTLE);
if (registrar == null) {
throw new NoSuchProviderException(SecurityUtils.BOUNCY_CASTLE + " registrar not available");
}
JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter();
if (registrar.isNamedProviderUsed()) {
pemConverter.setProvider(registrar.getName());
} else {
pemConverter.setProvider(registrar.getSecurityProvider());
}
if (o instanceof PEMEncryptedKeyPair) {
if (provider == null) {
throw new CredentialException("Missing password provider for encrypted resource=" + resourceKey);
}
for (int retryIndex = 0;; retryIndex++) {
String password = provider.getPassword(session, resourceKey, retryIndex);
PEMKeyPair decoded;
try {
if (GenericUtils.isEmpty(password)) {
throw new FailedLoginException("No password data for encrypted resource=" + resourceKey);
}
JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder();
PEMDecryptorProvider pemDecryptor = decryptorBuilder.build(password.toCharArray());
decoded = ((PEMEncryptedKeyPair) o).decryptKeyPair(pemDecryptor);
} catch (IOException | GeneralSecurityException | RuntimeException e) {
ResourceDecodeResult result
= provider.handleDecodeAttemptResult(session, resourceKey, retryIndex, password, e);
if (result == null) {
result = ResourceDecodeResult.TERMINATE;
}
switch (result) {
case TERMINATE:
throw e;
case RETRY:
continue;
case IGNORE:
return null;
default:
throw new ProtocolException(
"Unsupported decode attempt result (" + result + ") for " + resourceKey);
}
}
o = decoded;
provider.handleDecodeAttemptResult(session, resourceKey, retryIndex, password, null);
break;
}
}
if (o instanceof PEMKeyPair) {
return pemConverter.getKeyPair((PEMKeyPair) o);
} else if (o instanceof KeyPair) {
return (KeyPair) o;
} else {
throw new IOException("Failed to read " + resourceKey + " - unknown result object: " + o);
}
}
}
}

View file

@ -1,85 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.util.security.bouncycastle;
import java.security.SecureRandom;
import org.apache.sshd.common.random.AbstractRandom;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.bouncycastle.crypto.prng.RandomGenerator;
import org.bouncycastle.crypto.prng.VMPCRandomGenerator;
/**
* BouncyCastle <code>Random</code>. This pseudo random number generator uses the a very fast PRNG from BouncyCastle.
* The JRE random will be used when creating a new generator to add some random data to the seed.
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public final class BouncyCastleRandom extends AbstractRandom {
public static final String NAME = SecurityUtils.BOUNCY_CASTLE;
private final RandomGenerator random;
public BouncyCastleRandom() {
ValidateUtils.checkTrue(SecurityUtils.isBouncyCastleRegistered(), "BouncyCastle not registered");
this.random = new VMPCRandomGenerator();
byte[] seed = new SecureRandom().generateSeed(8);
this.random.addSeedMaterial(seed);
}
@Override
public String getName() {
return NAME;
}
@Override
public void fill(byte[] bytes, int start, int len) {
this.random.nextBytes(bytes, start, len);
}
/**
* Returns a pseudo-random uniformly distributed {@code int} in the half-open range [0, n).
*/
@Override
public int random(int n) {
ValidateUtils.checkTrue(n > 0, "Limit must be positive: %d", n);
if ((n & -n) == n) {
return (int) ((n * (long) next(31)) >> 31);
}
int bits;
int val;
do {
bits = next(31);
val = bits % n;
} while (bits - val + (n - 1) < 0);
return val;
}
private int next(int numBits) {
int bytes = (numBits + 7) / 8;
byte next[] = new byte[bytes];
int ret = 0;
random.nextBytes(next);
for (int i = 0; i < bytes; i++) {
ret = (next[i] & 0xFF) | (ret << 8);
}
return ret >>> (bytes * 8 - numBits);
}
}

View file

@ -1,45 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.util.security.bouncycastle;
import org.apache.sshd.common.random.AbstractRandomFactory;
import org.apache.sshd.common.random.Random;
import org.apache.sshd.common.util.security.SecurityUtils;
/**
* Named factory for the BouncyCastle <code>Random</code>
*/
public final class BouncyCastleRandomFactory extends AbstractRandomFactory {
public static final String NAME = "bouncycastle";
public static final BouncyCastleRandomFactory INSTANCE = new BouncyCastleRandomFactory();
public BouncyCastleRandomFactory() {
super(NAME);
}
@Override
public boolean isSupported() {
return SecurityUtils.isBouncyCastleRegistered();
}
@Override
public Random create() {
return new BouncyCastleRandom();
}
}

View file

@ -1,126 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.util.security.bouncycastle;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.Provider;
import java.security.Signature;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ReflectionUtils;
import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.apache.sshd.common.util.threads.ThreadUtils;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class BouncyCastleSecurityProviderRegistrar extends AbstractSecurityProviderRegistrar {
// We want to use reflection API so as not to require BouncyCastle to be present in the classpath
public static final String PROVIDER_CLASS = "org.bouncycastle.jce.provider.BouncyCastleProvider";
// Do not define a static registrar instance to minimize class loading issues
private final AtomicReference<Boolean> supportHolder = new AtomicReference<>(null);
private final AtomicReference<String> allSupportHolder = new AtomicReference<>();
public BouncyCastleSecurityProviderRegistrar() {
super(SecurityUtils.BOUNCY_CASTLE);
}
@Override
public boolean isEnabled() {
if (!super.isEnabled()) {
return false;
}
// For backward compatibility
return this.getBooleanProperty(SecurityUtils.REGISTER_BOUNCY_CASTLE_PROP, true);
}
@Override
public Provider getSecurityProvider() {
try {
return getOrCreateProvider(PROVIDER_CLASS);
} catch (ReflectiveOperationException t) {
Throwable e = GenericUtils.peelException(t);
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new RuntimeException(e);
}
}
@Override
public String getDefaultSecurityEntitySupportValue(Class<?> entityType) {
String allValue = allSupportHolder.get();
if (GenericUtils.length(allValue) > 0) {
return allValue;
}
String propName = getConfigurationPropertyName("supportAll");
allValue = this.getStringProperty(propName, ALL_OPTIONS_VALUE);
if (GenericUtils.isEmpty(allValue)) {
allValue = NO_OPTIONS_VALUE;
}
allSupportHolder.set(allValue);
return allValue;
}
@Override
public boolean isSecurityEntitySupported(Class<?> entityType, String name) {
if (!isSupported()) {
return false;
}
// Some known values it does not support
if (KeyPairGenerator.class.isAssignableFrom(entityType)
|| KeyFactory.class.isAssignableFrom(entityType)) {
if (Objects.compare(name, SecurityUtils.EDDSA, String.CASE_INSENSITIVE_ORDER) == 0) {
return false;
}
} else if (Signature.class.isAssignableFrom(entityType)) {
if (Objects.compare(name, SecurityUtils.CURVE_ED25519_SHA512, String.CASE_INSENSITIVE_ORDER) == 0) {
return false;
}
}
return super.isSecurityEntitySupported(entityType, name);
}
@Override
public boolean isSupported() {
Boolean supported;
synchronized (supportHolder) {
supported = supportHolder.get();
if (supported != null) {
return supported.booleanValue();
}
ClassLoader cl = ThreadUtils.resolveDefaultClassLoader(getClass());
supported = ReflectionUtils.isClassAvailable(cl, PROVIDER_CLASS);
supportHolder.set(supported);
}
return supported.booleanValue();
}
}

View file

@ -42,12 +42,12 @@ import org.apache.sshd.common.util.io.der.ASN1Object;
import org.apache.sshd.common.util.io.der.ASN1Type; import org.apache.sshd.common.util.io.der.ASN1Type;
import org.apache.sshd.common.util.io.der.DERParser; import org.apache.sshd.common.util.io.der.DERParser;
import org.apache.sshd.common.util.security.SecurityUtils; import org.apache.sshd.common.util.security.SecurityUtils;
import org.xbib.io.sshd.eddsa.EdDSAKey; import org.xbib.net.security.eddsa.EdDSAKey;
import org.xbib.io.sshd.eddsa.EdDSAPrivateKey; import org.xbib.net.security.eddsa.EdDSAPrivateKey;
import org.xbib.io.sshd.eddsa.EdDSAPublicKey; import org.xbib.net.security.eddsa.EdDSAPublicKey;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveTable; import org.xbib.net.security.eddsa.spec.EdDSANamedCurveTable;
import org.xbib.io.sshd.eddsa.spec.EdDSAParameterSpec; import org.xbib.net.security.eddsa.spec.EdDSAParameterSpec;
import org.xbib.io.sshd.eddsa.spec.EdDSAPrivateKeySpec; import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec;
/** /**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@ -55,10 +55,10 @@ import org.xbib.io.sshd.eddsa.spec.EdDSAPrivateKeySpec;
public class Ed25519PEMResourceKeyParser extends AbstractPEMResourceKeyPairParser { public class Ed25519PEMResourceKeyParser extends AbstractPEMResourceKeyPairParser {
// TODO find out how the markers really look like for now provide something // TODO find out how the markers really look like for now provide something
public static final String BEGIN_MARKER = "BEGIN EDDSA PRIVATE KEY"; public static final String BEGIN_MARKER = "BEGIN EDDSA PRIVATE KEY";
public static final List<String> BEGINNERS = Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER)); public static final List<String> BEGINNERS = Collections.singletonList(BEGIN_MARKER);
public static final String END_MARKER = "END EDDSA PRIVATE KEY"; public static final String END_MARKER = "END EDDSA PRIVATE KEY";
public static final List<String> ENDERS = Collections.unmodifiableList(Collections.singletonList(END_MARKER)); public static final List<String> ENDERS = Collections.singletonList(END_MARKER);
/** /**
* @see <A HREF="https://tools.ietf.org/html/rfc8410#section-3">RFC8412 section 3</A> * @see <A HREF="https://tools.ietf.org/html/rfc8410#section-3">RFC8412 section 3</A>
@ -174,7 +174,6 @@ public class Ed25519PEMResourceKeyParser extends AbstractPEMResourceKeyPairParse
if (!SecurityUtils.isEDDSACurveSupported()) { if (!SecurityUtils.isEDDSACurveSupported()) {
throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported"); throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported");
} }
EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(EdDSASecurityProviderUtils.CURVE_ED25519_SHA512); EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(EdDSASecurityProviderUtils.CURVE_ED25519_SHA512);
EdDSAPrivateKeySpec keySpec = new EdDSAPrivateKeySpec(seed, params); EdDSAPrivateKeySpec keySpec = new EdDSAPrivateKeySpec(seed, params);
KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);

View file

@ -33,10 +33,10 @@ import org.apache.sshd.common.config.keys.impl.AbstractPublicKeyEntryDecoder;
import org.apache.sshd.common.keyprovider.KeyPairProvider; import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.session.SessionContext; import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.security.SecurityUtils; import org.apache.sshd.common.util.security.SecurityUtils;
import org.xbib.io.sshd.eddsa.EdDSAPrivateKey; import org.xbib.net.security.eddsa.EdDSAPrivateKey;
import org.xbib.io.sshd.eddsa.EdDSAPublicKey; import org.xbib.net.security.eddsa.EdDSAPublicKey;
import org.xbib.io.sshd.eddsa.spec.EdDSAPrivateKeySpec; import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec;
import org.xbib.io.sshd.eddsa.spec.EdDSAPublicKeySpec; import org.xbib.net.security.eddsa.spec.EdDSAPublicKeySpec;
/** /**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>

View file

@ -35,7 +35,7 @@ import org.apache.sshd.common.util.threads.ThreadUtils;
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/ */
public class EdDSASecurityProviderRegistrar extends AbstractSecurityProviderRegistrar { public class EdDSASecurityProviderRegistrar extends AbstractSecurityProviderRegistrar {
public static final String PROVIDER_CLASS = "org.xbib.io.sshd.eddsa.EdDSASecurityProvider"; public static final String PROVIDER_CLASS = "org.xbib.net.security.eddsa.EdDSASecurityProvider";
// Do not define a static registrar instance to minimize class loading issues // Do not define a static registrar instance to minimize class loading issues
private final AtomicReference<Boolean> supportHolder = new AtomicReference<>(null); private final AtomicReference<Boolean> supportHolder = new AtomicReference<>(null);
@ -45,12 +45,7 @@ public class EdDSASecurityProviderRegistrar extends AbstractSecurityProviderRegi
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
if (!super.isEnabled()) { return super.isEnabled();
return false;
}
// For backward compatibility
return this.getBooleanProperty(SecurityUtils.EDDSA_SUPPORTED_PROP, true);
} }
@Override @Override
@ -93,7 +88,7 @@ public class EdDSASecurityProviderRegistrar extends AbstractSecurityProviderRegi
} }
ClassLoader cl = ThreadUtils.resolveDefaultClassLoader(getClass()); ClassLoader cl = ThreadUtils.resolveDefaultClassLoader(getClass());
supported = ReflectionUtils.isClassAvailable(cl, "org.xbib.io.sshd.eddsa.EdDSAKey"); supported = ReflectionUtils.isClassAvailable(cl, "org.xbib.net.security.eddsa.EdDSAKey");
supportHolder.set(supported); supportHolder.set(supported);
} }

View file

@ -33,14 +33,14 @@ import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer; import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.security.SecurityUtils; import org.apache.sshd.common.util.security.SecurityUtils;
import org.xbib.io.sshd.eddsa.EdDSAEngine; import org.xbib.net.security.eddsa.EdDSAEngine;
import org.xbib.io.sshd.eddsa.EdDSAKey; import org.xbib.net.security.eddsa.EdDSAKey;
import org.xbib.io.sshd.eddsa.EdDSAPrivateKey; import org.xbib.net.security.eddsa.EdDSAPrivateKey;
import org.xbib.io.sshd.eddsa.EdDSAPublicKey; import org.xbib.net.security.eddsa.EdDSAPublicKey;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveTable; import org.xbib.net.security.eddsa.spec.EdDSANamedCurveTable;
import org.xbib.io.sshd.eddsa.spec.EdDSAParameterSpec; import org.xbib.net.security.eddsa.spec.EdDSAParameterSpec;
import org.xbib.io.sshd.eddsa.spec.EdDSAPrivateKeySpec; import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec;
import org.xbib.io.sshd.eddsa.spec.EdDSAPublicKeySpec; import org.xbib.net.security.eddsa.spec.EdDSAPublicKeySpec;
/** /**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>

View file

@ -39,12 +39,12 @@ import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.io.SecureByteArrayOutputStream; import org.apache.sshd.common.util.io.SecureByteArrayOutputStream;
import org.apache.sshd.common.util.security.SecurityUtils; import org.apache.sshd.common.util.security.SecurityUtils;
import org.xbib.io.sshd.eddsa.EdDSAPrivateKey; import org.xbib.net.security.eddsa.EdDSAPrivateKey;
import org.xbib.io.sshd.eddsa.EdDSAPublicKey; import org.xbib.net.security.eddsa.EdDSAPublicKey;
import org.xbib.io.sshd.eddsa.spec.EdDSANamedCurveTable; import org.xbib.net.security.eddsa.spec.EdDSANamedCurveTable;
import org.xbib.io.sshd.eddsa.spec.EdDSAParameterSpec; import org.xbib.net.security.eddsa.spec.EdDSAParameterSpec;
import org.xbib.io.sshd.eddsa.spec.EdDSAPrivateKeySpec; import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec;
import org.xbib.io.sshd.eddsa.spec.EdDSAPublicKeySpec; import org.xbib.net.security.eddsa.spec.EdDSAPublicKeySpec;
/** /**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>

View file

@ -24,7 +24,7 @@ import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.session.SessionContext; import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.signature.AbstractSignature; import org.apache.sshd.common.signature.AbstractSignature;
import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.ValidateUtils;
import org.xbib.io.sshd.eddsa.EdDSAEngine; import org.xbib.net.security.eddsa.EdDSAEngine;
/** /**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@ -37,15 +37,12 @@ public class SignatureEd25519 extends AbstractSignature {
@Override @Override
public boolean verify(SessionContext session, byte[] sig) throws Exception { public boolean verify(SessionContext session, byte[] sig) throws Exception {
byte[] data = sig; byte[] data = sig;
Map.Entry<String, byte[]> encoding Map.Entry<String, byte[]> encoding = extractEncodedSignature(data, KeyPairProvider.SSH_ED25519::equalsIgnoreCase);
= extractEncodedSignature(data, k -> KeyPairProvider.SSH_ED25519.equalsIgnoreCase(k));
if (encoding != null) { if (encoding != null) {
String keyType = encoding.getKey(); String keyType = encoding.getKey();
ValidateUtils.checkTrue( ValidateUtils.checkTrue(KeyPairProvider.SSH_ED25519.equals(keyType), "Mismatched key type: %s", keyType);
KeyPairProvider.SSH_ED25519.equals(keyType), "Mismatched key type: %s", keyType);
data = encoding.getValue(); data = encoding.getValue();
} }
return doVerify(data); return doVerify(data);
} }
} }

View file

@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.util.security.xbib;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
import org.apache.sshd.common.session.SessionContext;
import org.xbib.net.security.PrivateKeyReader;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class XbibKeyPairResourceParser implements KeyPairResourceParser {
public static final XbibKeyPairResourceParser INSTANCE = new XbibKeyPairResourceParser();
public XbibKeyPairResourceParser() {
}
@Override
public boolean canExtractKeyPairs(NamedResource resourceKey, List<String> lines) {
return true;
}
@Override
public Collection<KeyPair> loadKeyPairs(SessionContext session,
NamedResource resourceKey,
FilePasswordProvider passwordProvider,
List<String> lines) throws IOException, GeneralSecurityException {
PrivateKeyReader privateKeyReader = new PrivateKeyReader();
InputStream inputStream = new ByteArrayInputStream(String.join("\n", lines).getBytes(StandardCharsets.US_ASCII));
String password = passwordProvider.getPassword(session, resourceKey, 0);
return Collections.singleton(privateKeyReader.generateFrom(inputStream, password));
}
}

View file

@ -1,12 +1,5 @@
group = org.xbib group = org.xbib
name = files name = files
version = 3.0.0 version = 4.0.0
org.gradle.warning.mode = ALL org.gradle.warning.mode = ALL
gradle.wrapper.version = 7.5.1
bouncycastle.version = 1.69
log4j.version = 2.14.1
mockftpserver.version = 2.7.1
junit.version = 5.8.0
junit4.version = 4.13.2
mockito.version = 3.7.7

View file

@ -6,20 +6,19 @@ java {
} }
compileJava { compileJava {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_17
} }
compileTestJava { compileTestJava {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_17
} }
jar { jar {
manifest { manifest {
attributes('Implementation-Title': project.name) attributes('Implementation-Title': project.name)
attributes('Implementation-Version': project.version) attributes('Implementation-Version': project.version)
attributes('Implementation-Vendor': 'Jörg Prante')
} }
} }
@ -38,9 +37,11 @@ artifacts {
} }
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
options.compilerArgs << '-Xlint:all' options.compilerArgs.add('-Xlint:all,-exports')
options.encoding = 'UTF-8'
} }
javadoc { tasks.withType(Javadoc) {
options.addStringOption('Xdoclint:none', '-quiet') options.addStringOption('Xdoclint:none', '-quiet')
options.encoding = 'UTF-8'
} }

View file

@ -1,17 +1,12 @@
def junitVersion = project.hasProperty('junit.version')?project.property('junit.version'):'5.6.2'
def hamcrestVersion = project.hasProperty('hamcrest.version')?project.property('hamcrest.version'):'2.2'
dependencies { dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}" testImplementation libs.junit.jupiter.api
testImplementation "org.junit.jupiter:junit-jupiter-params:${junitVersion}" testImplementation libs.junit.jupiter.params
testImplementation "org.hamcrest:hamcrest-library:${hamcrestVersion}" testImplementation libs.hamcrest
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" testRuntimeOnly libs.junit.jupiter.engine
} }
test { test {
useJUnitPlatform() useJUnitPlatform()
systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties'
failFast = false failFast = false
testLogging { testLogging {
events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED' events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED'
@ -25,4 +20,5 @@ test {
"${result.skippedTestCount} skipped" "${result.skippedTestCount} skipped"
} }
} }
systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties'
} }

View file

@ -1,5 +1,39 @@
pluginManagement {
repositories {
mavenLocal()
mavenCentral {
metadataSources {
mavenPom()
artifact()
ignoreGradleMetadataRedirection()
}
}
gradlePluginPortal()
}
}
dependencyResolutionManagement {
versionCatalogs {
libs {
version('gradle', '7.5.1')
version('groovy', '3.0.10')
version('junit', '5.9.1')
version('net', '3.0.2')
library('junit-jupiter-api', 'org.junit.jupiter', 'junit-jupiter-api').versionRef('junit')
library('junit-jupiter-params', 'org.junit.jupiter', 'junit-jupiter-params').versionRef('junit')
library('junit-jupiter-engine', 'org.junit.jupiter', 'junit-jupiter-engine').versionRef('junit')
library('junit-vintage-engine', 'org.junit.vintage', 'junit-vintage-engine').versionRef('junit')
library('hamcrest', 'org.hamcrest', 'hamcrest-library').version('2.2')
library('junit4', 'junit', 'junit').version('4.13.2')
library('net-security', 'org.xbib', 'net-security').versionRef('net')
library('mockftpserver', 'org.mockftpserver', 'MockFtpServer').version('2.7.1')
library('mockito-core', 'org.mockito', 'mockito-core').version('3.7.7')
library('mockito-junit-jupiter', 'org.mockito', 'mockito-junit-jupiter').version('3.7.7')
}
}
}
include 'files-api' include 'files-api'
include 'files-eddsa'
include 'files-zlib' include 'files-zlib'
include 'files-ftp' include 'files-ftp'
include 'files-ftp-fs' include 'files-ftp-fs'