fix eddsa, add tests
This commit is contained in:
parent
48db84305f
commit
3370d61a6c
22 changed files with 5184 additions and 3 deletions
|
@ -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".
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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!
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
Loading…
Reference in a new issue