fix eddsa, add tests

This commit is contained in:
Jörg Prante 2022-11-02 22:52:21 +01:00
parent 48db84305f
commit 3370d61a6c
22 changed files with 5184 additions and 3 deletions

View file

@ -18,9 +18,9 @@ public class EdDSASecurityProvider extends Provider {
protected void setup() { protected void setup() {
// See https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/HowToImplAProvider.html // 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("KeyFactory." + EdDSAKey.KEY_ALGORITHM, "org.xbib.net.security.eddsa.KeyFactory");
put("KeyPairGenerator." + EdDSAKey.KEY_ALGORITHM, "org.xbib.io.sshd.eddsa.KeyPairGenerator"); put("KeyPairGenerator." + EdDSAKey.KEY_ALGORITHM, "org.xbib.net.security.eddsa.KeyPairGenerator");
put("Signature." + EdDSAEngine.SIGNATURE_ALGORITHM, "org.xbib.io.sshd.eddsa.EdDSAEngine"); put("Signature." + EdDSAEngine.SIGNATURE_ALGORITHM, "org.xbib.net.security.eddsa.EdDSAEngine");
// OID Mappings // OID Mappings
// See section "Mapping from OID to name". // See section "Mapping from OID to name".

View file

@ -0,0 +1,54 @@
package org.xbib.net.security.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

@ -0,0 +1,196 @@
package org.xbib.net.security.eddsa;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.xbib.net.security.eddsa.spec.EdDSANamedCurveTable;
import org.xbib.net.security.eddsa.spec.EdDSAParameterSpec;
import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec;
import org.xbib.net.security.eddsa.spec.EdDSAPublicKeySpec;
/**
*
*/
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(StandardCharsets.UTF_8);
static final byte[] TEST_MSG_SIG = Utils.hexToBytes("94825896c7075c31bcb81f06dba2bdcd9dcf16e79288d4b9f87c248215c8468d475f429f3de3b4a2cf67fe17077ae19686020364d6d4fa7a0174bab4a123ba0f");
@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);
Assertions.assertThrowsExactly(SignatureException.class, () -> {
sgr.initVerify(vKey);
sgr.update(TEST_MSG);
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);
Assertions.assertThrowsExactly(SignatureException.class, () -> {
sgr.initSign(sKey);
sgr.setParameter(EdDSAEngine.ONE_SHOT_MODE);
sgr.update(TEST_MSG);
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);
Assertions.assertThrowsExactly(SignatureException.class, () -> {
sgr.initVerify(vKey);
sgr.setParameter(EdDSAEngine.ONE_SHOT_MODE);
sgr.update(TEST_MSG);
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

@ -0,0 +1,81 @@
package org.xbib.net.security.eddsa;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import java.security.spec.PKCS8EncodedKeySpec;
import org.junit.jupiter.api.Test;
import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec;
/**
*
*/
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

@ -0,0 +1,71 @@
package org.xbib.net.security.eddsa;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import java.security.spec.X509EncodedKeySpec;
import org.junit.jupiter.api.Test;
import org.xbib.net.security.eddsa.spec.EdDSAPublicKeySpec;
/**
*
*/
public class EdDSAPublicKeyTest {
/**
* The example public key MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=
* from <a href="https://tools.ietf.org/html/draft-ietf-curdle-pkix-04#section-10.1">https://tools.ietf.org/html/draft-ietf-curdle-pkix-04#section-10.1</a>
*/
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

@ -0,0 +1,31 @@
package org.xbib.net.security.eddsa;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.Signature;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
*
*/
public class EdDSASecurityProviderTest {
@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 {
Assertions.assertThrowsExactly(NoSuchProviderException.class, () -> {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EdDSA", "EdDSA");
});
}
}

View file

@ -0,0 +1,120 @@
package org.xbib.net.security.eddsa;
import org.hamcrest.core.IsEqual;
import java.security.SecureRandom;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
/**
* 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);
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);
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() {
assertThat(Utils.hexToBytes(hex1), IsEqual.equalTo(bytes1));
assertThat(Utils.hexToBytes(hex2), IsEqual.equalTo(bytes2));
assertThat(Utils.hexToBytes(hex3), IsEqual.equalTo(bytes3));
}
@Test
public void bytesToHexReturnsCorrectHexString() {
assertThat(Utils.bytesToHex(bytes1), IsEqual.equalTo(hex1));
assertThat(Utils.bytesToHex(bytes2), IsEqual.equalTo(hex2));
assertThat(Utils.bytesToHex(bytes3), IsEqual.equalTo(hex3));
}
}

View file

@ -0,0 +1,183 @@
package org.xbib.net.security.eddsa.math;
import org.hamcrest.core.IsEqual;
import org.hamcrest.core.IsNot;
import java.math.BigInteger;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* 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();
assertThat(f.isNonZero(), IsEqual.equalTo(false));
}
@Test
public void isNonZeroReturnsTrueIfFieldElementIsNonZero() {
final FieldElement f = getNonZeroFieldElement();
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());
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());
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());
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());
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());
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());
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());
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());
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();
assertThat(zero.cmov(nz, 0), IsEqual.equalTo(zero));
assertThat(zero.cmov(nz, 1), IsEqual.equalTo(nz));
assertThat(f.cmov(nz, 0), IsEqual.equalTo(f));
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();
assertThat(f1, IsEqual.equalTo(f2));
assertThat(f1, IsNot.not(IsEqual.equalTo(f3)));
assertThat(f1, IsNot.not(IsEqual.equalTo(f4)));
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();
assertThat(f1.hashCode(), IsEqual.equalTo(f2.hashCode()));
assertThat(f1.hashCode(), IsNot.not(IsEqual.equalTo(f3.hashCode())));
assertThat(f1.hashCode(), IsNot.not(IsEqual.equalTo(f4.hashCode())));
assertThat(f3.hashCode(), IsNot.not(IsEqual.equalTo(f4.hashCode())));
}
}

View file

@ -0,0 +1,78 @@
package org.xbib.net.security.eddsa.math;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.fail;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.junit.jupiter.api.Test;
import org.xbib.net.security.eddsa.spec.EdDSANamedCurveSpec;
import org.xbib.net.security.eddsa.spec.EdDSANamedCurveTable;
/**
* 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

@ -0,0 +1,879 @@
package org.xbib.net.security.eddsa.math;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.xbib.net.security.eddsa.Ed25519TestVectors;
import org.xbib.net.security.eddsa.Utils;
import org.xbib.net.security.eddsa.spec.EdDSANamedCurveSpec;
import org.xbib.net.security.eddsa.spec.EdDSANamedCurveTable;
import org.hamcrest.core.IsEqual;
import org.hamcrest.core.IsNot;
import java.math.BigInteger;
import java.util.Arrays;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.is;
/**
* 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");
/**
* 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:
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:
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
public void toP2ThrowsIfGroupElementHasPrecompRepresentation() {
Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.PRECOMP);
// Assert:
g.toP2();
});
}
@Test
public void toP2ThrowsIfGroupElementHasCachedRepresentation() {
Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> {
// 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:
assertThat(h, IsEqual.equalTo(g));
assertThat(h.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.P2));
assertThat(h.getX(), IsEqual.equalTo(g.getX()));
assertThat(h.getY(), IsEqual.equalTo(g.getY()));
assertThat(h.getZ(), IsEqual.equalTo(g.getZ()));
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:
assertThat(h1, IsEqual.equalTo(h2));
assertThat(h1.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.P2));
assertThat(h1.getX(), IsEqual.equalTo(g.getX()));
assertThat(h1.getY(), IsEqual.equalTo(g.getY()));
assertThat(h1.getZ(), IsEqual.equalTo(g.getZ()));
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:
assertThat(h1, IsEqual.equalTo(h2));
assertThat(h1.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.P2));
assertThat(h1.getX(), IsEqual.equalTo(g.getX().multiply(g.getT())));
assertThat(h1.getY(), IsEqual.equalTo(g.getY().multiply(g.getZ())));
assertThat(h1.getZ(), IsEqual.equalTo(g.getZ().multiply(g.getT())));
assertThat(h1.getT(), IsEqual.equalTo(null));
}
}
@Test
public void toP3ThrowsIfGroupElementHasP2Representation() {
Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.P2);
// Assert:
g.toP3();
});
}
@Test
public void toP3ThrowsIfGroupElementHasPrecompRepresentation() {
Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.PRECOMP);
// Assert:
g.toP3();
});
}
@Test
public void toP3ThrowsIfGroupElementHasCachedRepresentation() {
Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> {
// 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:
assertThat(h1, IsEqual.equalTo(h2));
assertThat(h1.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.P3));
assertThat(h1.getX(), IsEqual.equalTo(g.getX().multiply(g.getT())));
assertThat(h1.getY(), IsEqual.equalTo(g.getY().multiply(g.getZ())));
assertThat(h1.getZ(), IsEqual.equalTo(g.getZ().multiply(g.getT())));
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:
assertThat(h, IsEqual.equalTo(g));
assertThat(h.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.P3));
assertThat(h, IsEqual.equalTo(g));
assertThat(h.getX(), IsEqual.equalTo(g.getX()));
assertThat(h.getY(), IsEqual.equalTo(g.getY()));
assertThat(h.getZ(), IsEqual.equalTo(g.getZ()));
assertThat(h.getT(), IsEqual.equalTo(g.getT()));
}
}
@Test
public void toCachedThrowsIfGroupElementHasP2Representation() {
Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.P2);
// Assert:
g.toCached();
});
}
@Test
public void toCachedThrowsIfGroupElementHasPrecompRepresentation() {
Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> {
// Arrange:
final GroupElement g = MathUtils.toRepresentation(MathUtils.getRandomGroupElement(), GroupElement.Representation.PRECOMP);
// Assert:
g.toCached();
});
}
@Test
public void toCachedThrowsIfGroupElementHasP1P1Representation() {
Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> {
// 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:
assertThat(h, IsEqual.equalTo(g));
assertThat(h.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.CACHED));
assertThat(h, IsEqual.equalTo(g));
assertThat(h.getX(), IsEqual.equalTo(g.getX()));
assertThat(h.getY(), IsEqual.equalTo(g.getY()));
assertThat(h.getZ(), IsEqual.equalTo(g.getZ()));
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:
assertThat(h1, IsEqual.equalTo(h2));
assertThat(h1.getRepresentation(), IsEqual.equalTo(GroupElement.Representation.CACHED));
assertThat(h1, IsEqual.equalTo(g));
assertThat(h1.getX(), IsEqual.equalTo(g.getY().add(g.getX())));
assertThat(h1.getY(), IsEqual.equalTo(g.getY().subtract(g.getX())));
assertThat(h1.getZ(), IsEqual.equalTo(g.getZ()));
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++) {
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++) {
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:
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:
assertThat(g, IsEqual.equalTo(h1));
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:
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:
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
assertThat(g2, IsEqual.equalTo(g1));
assertThat(g3, IsEqual.equalTo(g1));
assertThat(g1, IsEqual.equalTo(g4));
assertThat(g1, IsNot.not(IsEqual.equalTo(g5)));
assertThat(g2, IsNot.not(IsEqual.equalTo(g5)));
assertThat(g3, IsNot.not(IsEqual.equalTo(g5)));
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
assertThat(g2.hashCode(), IsEqual.equalTo(g1.hashCode()));
assertThat(g3.hashCode(), IsEqual.equalTo(g1.hashCode()));
assertThat(g1.hashCode(), IsNot.not(IsEqual.equalTo(g4.hashCode())));
assertThat(g2.hashCode(), IsNot.not(IsEqual.equalTo(g4.hashCode())));
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:
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:
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:
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:
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:
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):
assertThat(h.isOnCurve(), IsEqual.equalTo(false));
}
}
}

View file

@ -0,0 +1,472 @@
package org.xbib.net.security.eddsa.math;
import org.junit.jupiter.api.Test;
import org.xbib.net.security.eddsa.Utils;
import org.xbib.net.security.eddsa.math.ed25519.Ed25519FieldElement;
import org.xbib.net.security.eddsa.math.ed25519.Ed25519LittleEndianEncoding;
import org.xbib.net.security.eddsa.spec.EdDSANamedCurveSpec;
import org.xbib.net.security.eddsa.spec.EdDSANamedCurveTable;
import org.hamcrest.core.IsEqual;
import java.math.BigInteger;
import java.security.SecureRandom;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* 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:
assertThat(g, IsEqual.equalTo(h1));
assertThat(g, IsEqual.equalTo(h2));
}
for (int i=0; i<1000; i++) {
GroupElement g = getRandomGroupElement();
// P3 -> P2.
GroupElement h = toRepresentation(g, GroupElement.Representation.P2);
assertThat(h, IsEqual.equalTo(g));
// P3 -> P1P1.
h = toRepresentation(g, GroupElement.Representation.P1P1);
assertThat(g, IsEqual.equalTo(h));
// P3 -> CACHED.
h = toRepresentation(g, GroupElement.Representation.CACHED);
assertThat(h, IsEqual.equalTo(g));
// P3 -> P2 -> P3.
g = toRepresentation(g, GroupElement.Representation.P2);
h = toRepresentation(g, GroupElement.Representation.P3);
assertThat(g, IsEqual.equalTo(h));
// P3 -> P2 -> P1P1.
g = toRepresentation(g, GroupElement.Representation.P2);
h = toRepresentation(g, GroupElement.Representation.P1P1);
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:
assertThat(curve.getZero(GroupElement.Representation.P3), IsEqual.equalTo(h));
}
}
// End TODO BR: Remove when finished!
}

View file

@ -0,0 +1,103 @@
package org.xbib.net.security.eddsa.math;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.xbib.net.security.eddsa.Utils;
import org.xbib.net.security.eddsa.spec.EdDSANamedCurveSpec;
import org.xbib.net.security.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

@ -0,0 +1,104 @@
package org.xbib.net.security.eddsa.math.bigint;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import java.math.BigInteger;
import java.util.Random;
import org.junit.jupiter.api.Test;
import org.xbib.net.security.eddsa.Utils;
import org.xbib.net.security.eddsa.math.Field;
import org.xbib.net.security.eddsa.math.FieldElement;
import org.xbib.net.security.eddsa.math.MathUtils;
import org.xbib.net.security.eddsa.math.AbstractFieldElementTest;
/**
*
*/
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

@ -0,0 +1,60 @@
package org.xbib.net.security.eddsa.math.bigint;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import java.math.BigInteger;
import org.junit.jupiter.api.Test;
import org.xbib.net.security.eddsa.Utils;
import org.xbib.net.security.eddsa.math.Field;
import org.xbib.net.security.eddsa.math.ScalarOps;
import org.xbib.net.security.eddsa.spec.EdDSANamedCurveSpec;
import org.xbib.net.security.eddsa.spec.EdDSANamedCurveTable;
/**
*
*/
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

@ -0,0 +1,83 @@
package org.xbib.net.security.eddsa.math.ed25519;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.xbib.net.security.eddsa.math.AbstractFieldElementTest;
import org.xbib.net.security.eddsa.math.Field;
import org.xbib.net.security.eddsa.math.FieldElement;
import org.xbib.net.security.eddsa.math.MathUtils;
import org.hamcrest.core.IsEqual;
import java.math.BigInteger;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* 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
public void cannotConstructFieldElementFromArrayWithIncorrectLength() {
Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> {
new Ed25519FieldElement(MathUtils.getField(), new int[9]);
});
}
@Test
public void cannotConstructFieldElementWithoutField() {
Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> {
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("]");
assertThat(fAsString, IsEqual.equalTo(builder.toString()));
}
}

View file

@ -0,0 +1,107 @@
package org.xbib.net.security.eddsa.math.ed25519;
import org.junit.jupiter.api.Test;
import org.xbib.net.security.eddsa.math.FieldElement;
import org.xbib.net.security.eddsa.math.MathUtils;
import org.hamcrest.core.IsEqual;
import java.math.BigInteger;
import java.security.SecureRandom;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* 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:
assertThat(bytes1, IsEqual.equalTo(MathUtils.toByteArray(BigInteger.ZERO)));
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:
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:
assertThat(b1, IsEqual.equalTo(BigInteger.ZERO));
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:
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:
assertThat(MathUtils.getField().getEncoding().isNegative(f), IsEqual.equalTo(isNegative));
}
}
}

View file

@ -0,0 +1,71 @@
package org.xbib.net.security.eddsa.math.ed25519;
import org.junit.jupiter.api.Test;
import org.xbib.net.security.eddsa.Utils;
import org.xbib.net.security.eddsa.math.MathUtils;
import org.hamcrest.core.IsEqual;
import java.math.BigInteger;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
/**
* Additional tests by the NEM project team.
*
*/
public class Ed25519ScalarOpsTest {
private static final Ed25519ScalarOps scalarOps = new Ed25519ScalarOps();
/**
* Test method for {@link org.xbib.net.security.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);
assertThat(MathUtils.toBigInteger(reduced1).compareTo(MathUtils.getGroupOrder()), IsEqual.equalTo(-1));
assertThat(MathUtils.toBigInteger(reduced1).compareTo(new BigInteger("-1")), IsEqual.equalTo(1));
assertThat(reduced1, IsEqual.equalTo(reduced2));
}
}
/**
* Test method for {@link org.xbib.net.security.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);
assertThat(MathUtils.toBigInteger(result1).compareTo(MathUtils.getGroupOrder()), IsEqual.equalTo(-1));
assertThat(MathUtils.toBigInteger(result1).compareTo(new BigInteger("-1")), IsEqual.equalTo(1));
assertThat(result1, IsEqual.equalTo(result2));
}
}
}

View file

@ -0,0 +1,23 @@
package org.xbib.net.security.eddsa.spec;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
/**
*
*/
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

@ -0,0 +1,57 @@
package org.xbib.net.security.eddsa.spec;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.xbib.net.security.eddsa.Utils;
/**
*
*/
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");
/**
* 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() {
Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> {
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() {
Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> {
EdDSAPrivateKeySpec key = new EdDSAPrivateKeySpec(ed25519, new byte[2]);
});
}
}

View file

@ -0,0 +1,40 @@
{
{ 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

File diff suppressed because it is too large Load diff