package org.bouncycastle.math.ec.test;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Enumeration;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECPoint;
/**
* Test class for {@link org.bouncycastle.math.ec.ECPoint ECPoint}. All
* literature values are taken from "Guide to elliptic curve cryptography",
* Darrel Hankerson, Alfred J. Menezes, Scott Vanstone, 2004, Springer-Verlag
* New York, Inc.
*/
public class ECPointTest extends TestCase
{
/**
* Random source used to generate random points
*/
private SecureRandom secRand = new SecureRandom();
private ECPointTest.Fp fp = null;
private ECPointTest.F2m f2m = null;
/**
* Nested class containing sample literature values for <code>Fp</code>.
*/
public static class Fp
{
private final BigInteger q = new BigInteger("29");
private final BigInteger a = new BigInteger("4");
private final BigInteger b = new BigInteger("20");
private final ECCurve.Fp curve = new ECCurve.Fp(q, a, b);
private final ECPoint.Fp infinity = (ECPoint.Fp) curve.getInfinity();
private final int[] pointSource = { 5, 22, 16, 27, 13, 6, 14, 6 };
private ECPoint.Fp[] p = new ECPoint.Fp[pointSource.length / 2];
/**
* Creates the points on the curve with literature values.
*/
private void createPoints()
{
for (int i = 0; i < pointSource.length / 2; i++)
{
ECFieldElement.Fp x = new ECFieldElement.Fp(q, new BigInteger(
Integer.toString(pointSource[2 * i])));
ECFieldElement.Fp y = new ECFieldElement.Fp(q, new BigInteger(
Integer.toString(pointSource[2 * i + 1])));
p[i] = new ECPoint.Fp(curve, x, y);
}
}
}
/**
* Nested class containing sample literature values for <code>F2m</code>.
*/
public static class F2m
{
// Irreducible polynomial for TPB z^4 + z + 1
private final int m = 4;
private final int k1 = 1;
// a = z^3
private final ECFieldElement.F2m aTpb = new ECFieldElement.F2m(m, k1,
new BigInteger("1000", 2));
// b = z^3 + 1
private final ECFieldElement.F2m bTpb = new ECFieldElement.F2m(m, k1,
new BigInteger("1001", 2));
private final ECCurve.F2m curve = new ECCurve.F2m(m, k1, aTpb
.toBigInteger(), bTpb.toBigInteger());
private final ECPoint.F2m infinity = (ECPoint.F2m) curve.getInfinity();
private final String[] pointSource = { "0010", "1111", "1100", "1100",
"0001", "0001", "1011", "0010" };
private ECPoint.F2m[] p = new ECPoint.F2m[pointSource.length / 2];
/**
* Creates the points on the curve with literature values.
*/
private void createPoints()
{
for (int i = 0; i < pointSource.length / 2; i++)
{
ECFieldElement.F2m x = new ECFieldElement.F2m(m, k1,
new BigInteger(pointSource[2 * i], 2));
ECFieldElement.F2m y = new ECFieldElement.F2m(m, k1,
new BigInteger(pointSource[2 * i + 1], 2));
p[i] = new ECPoint.F2m(curve, x, y);
}
}
}
public void setUp()
{
fp = new ECPointTest.Fp();
fp.createPoints();
f2m = new ECPointTest.F2m();
f2m.createPoints();
}
/**
* Tests, if inconsistent points can be created, i.e. points with exactly
* one null coordinate (not permitted).
*/
public void testPointCreationConsistency()
{
try
{
ECPoint.Fp bad = new ECPoint.Fp(fp.curve, new ECFieldElement.Fp(
fp.q, new BigInteger("12")), null);
fail();
}
catch (IllegalArgumentException expected)
{
}
try
{
ECPoint.Fp bad = new ECPoint.Fp(fp.curve, null,
new ECFieldElement.Fp(fp.q, new BigInteger("12")));
fail();
}
catch (IllegalArgumentException expected)
{
}
try
{
ECPoint.F2m bad = new ECPoint.F2m(f2m.curve, new ECFieldElement.F2m(
f2m.m, f2m.k1, new BigInteger("1011")), null);
fail();
}
catch (IllegalArgumentException expected)
{
}
try
{
ECPoint.F2m bad = new ECPoint.F2m(f2m.curve, null,
new ECFieldElement.F2m(f2m.m, f2m.k1,
new BigInteger("1011")));
fail();
}
catch (IllegalArgumentException expected)
{
}
}
/**
* Tests <code>ECPoint.add()</code> against literature values.
*
* @param p
* The array of literature values.
* @param infinity
* The point at infinity on the respective curve.
*/
private void implTestAdd(ECPoint[] p, ECPoint infinity)
{
assertEquals("p0 plus p1 does not equal p2", p[2], p[0].add(p[1]));
assertEquals("p1 plus p0 does not equal p2", p[2], p[1].add(p[0]));
for (int i = 0; i < p.length; i++)
{
assertEquals("Adding infinity failed", p[i], p[i].add(infinity));
assertEquals("Adding to infinity failed", p[i], infinity.add(p[i]));
}
}
/**
* Calls <code>implTestAdd()</code> for <code>Fp</code> and
* <code>F2m</code>.
*/
public void testAdd()
{
implTestAdd(fp.p, fp.infinity);
implTestAdd(f2m.p, f2m.infinity);
}
/**
* Tests <code>ECPoint.twice()</code> against literature values.
*
* @param p
* The array of literature values.
*/
private void implTestTwice(ECPoint[] p)
{
assertEquals("Twice incorrect", p[3], p[0].twice());
assertEquals("Add same point incorrect", p[3], p[0].add(p[0]));
}
/**
* Calls <code>implTestTwice()</code> for <code>Fp</code> and
* <code>F2m</code>.
*/
public void testTwice()
{
implTestTwice(fp.p);
implTestTwice(f2m.p);
}
/**
* Goes through all points on an elliptic curve and checks, if adding a
* point <code>k</code>-times is the same as multiplying the point by
* <code>k</code>, for all <code>k</code>. Should be called for points
* on very small elliptic curves only.
*
* @param p
* The base point on the elliptic curve.
* @param infinity
* The point at infinity on the elliptic curve.
*/
private void implTestAllPoints(ECPoint p, ECPoint infinity)
{
ECPoint adder = infinity;
ECPoint multiplier = infinity;
int i = 1;
do
{
adder = adder.add(p);
multiplier = p.multiply(new BigInteger(Integer.toString(i)));
assertEquals("Results of add() and multiply() are inconsistent "
+ i, adder, multiplier);
i++;
}
while (!(adder.equals(infinity)));
}
/**
* Calls <code>implTestAllPoints()</code> for the small literature curves,
* both for <code>Fp</code> and <code>F2m</code>.
*/
public void testAllPoints()
{
for (int i = 0; i < fp.p.length; i++)
{
implTestAllPoints(fp.p[0], fp.infinity);
}
for (int i = 0; i < f2m.p.length; i++)
{
implTestAllPoints(f2m.p[0], f2m.infinity);
}
}
/**
* Simple shift-and-add multiplication. Serves as reference implementation
* to verify (possibly faster) implementations in
* {@link org.bouncycastle.math.ec.ECPoint ECPoint}.
*
* @param p
* The point to multiply.
* @param k
* The multiplier.
* @return The result of the point multiplication <code>kP</code>.
*/
private ECPoint multiply(ECPoint p, BigInteger k)
{
ECPoint q = p.getCurve().getInfinity();
int t = k.bitLength();
for (int i = 0; i < t; i++)
{
if (k.testBit(i))
{
q = q.add(p);
}
p = p.twice();
}
return q;
}
/**
* Checks, if the point multiplication algorithm of the given point yields
* the same result as point multiplication done by the reference
* implementation given in <code>multiply()</code>. This method chooses a
* random number by which the given point <code>p</code> is multiplied.
*
* @param p
* The point to be multiplied.
* @param numBits
* The bitlength of the random number by which <code>p</code>
* is multiplied.
*/
private void implTestMultiply(ECPoint p, int numBits)
{
BigInteger k = new BigInteger(numBits, secRand);
ECPoint ref = multiply(p, k);
ECPoint q = p.multiply(k);
assertEquals("ECPoint.multiply is incorrect", ref, q);
}
/**
* Checks, if the point multiplication algorithm of the given point yields
* the same result as point multiplication done by the reference
* implementation given in <code>multiply()</code>. This method tests
* multiplication of <code>p</code> by every number of bitlength
* <code>numBits</code> or less.
*
* @param p
* The point to be multiplied.
* @param numBits
* Try every multiplier up to this bitlength
*/
private void implTestMultiplyAll(ECPoint p, int numBits)
{
BigInteger bound = BigInteger.valueOf(2).pow(numBits);
BigInteger k = BigInteger.ZERO;
do
{
ECPoint ref = multiply(p, k);
ECPoint q = p.multiply(k);
assertEquals("ECPoint.multiply is incorrect", ref, q);
k = k.add(BigInteger.ONE);
}
while (k.compareTo(bound) < 0);
}
/**
* Tests <code>ECPoint.add()</code> and <code>ECPoint.subtract()</code>
* for the given point and the given point at infinity.
*
* @param p
* The point on which the tests are performed.
* @param infinity
* The point at infinity on the same curve as <code>p</code>.
*/
private void implTestAddSubtract(ECPoint p, ECPoint infinity)
{
assertEquals("Twice and Add inconsistent", p.twice(), p.add(p));
assertEquals("Twice p - p is not p", p, p.twice().subtract(p));
assertEquals("p - p is not infinity", infinity, p.subtract(p));
assertEquals("p plus infinity is not p", p, p.add(infinity));
assertEquals("infinity plus p is not p", p, infinity.add(p));
assertEquals("infinity plus infinity is not infinity ", infinity,
infinity.add(infinity));
}
/**
* Calls <code>implTestAddSubtract()</code> for literature values, both
* for <code>Fp</code> and <code>F2m</code>.
*/
public void testAddSubtractMultiplySimple()
{
for (int iFp = 0; iFp < fp.pointSource.length / 2; iFp++)
{
implTestAddSubtract(fp.p[iFp], fp.infinity);
// Could be any numBits, 6 is chosen at will
implTestMultiplyAll(fp.p[iFp], 6);
implTestMultiplyAll(fp.infinity, 6);
}
for (int iF2m = 0; iF2m < f2m.pointSource.length / 2; iF2m++)
{
implTestAddSubtract(f2m.p[iF2m], f2m.infinity);
// Could be any numBits, 6 is chosen at will
implTestMultiplyAll(f2m.p[iF2m], 6);
implTestMultiplyAll(f2m.infinity, 6);
}
}
/**
* Test encoding with and without point compression.
*
* @param p
* The point to be encoded and decoded.
*/
private void implTestEncoding(ECPoint p)
{
// Not Point Compression
ECPoint unCompP;
// Point compression
ECPoint compP;
if (p instanceof ECPoint.Fp)
{
unCompP = new ECPoint.Fp(p.getCurve(), p.getX(), p.getY(), false);
compP = new ECPoint.Fp(p.getCurve(), p.getX(), p.getY(), true);
}
else
{
unCompP = new ECPoint.F2m(p.getCurve(), p.getX(), p.getY(), false);
compP = new ECPoint.F2m(p.getCurve(), p.getX(), p.getY(), true);
}
byte[] unCompBarr = unCompP.getEncoded();
ECPoint decUnComp = p.getCurve().decodePoint(unCompBarr);
assertEquals("Error decoding uncompressed point", p, decUnComp);
byte[] compBarr = compP.getEncoded();
ECPoint decComp = p.getCurve().decodePoint(compBarr);
assertEquals("Error decoding compressed point", p, decComp);
}
/**
* Calls <code>implTestAddSubtract()</code>,
* <code>implTestMultiply</code> and <code>implTestEncoding</code> for
* the standard elliptic curves as given in <code>SECNamedCurves</code>.
*/
public void testAddSubtractMultiplyTwiceEncoding()
{
Enumeration curveEnum = SECNamedCurves.getNames();
while (curveEnum.hasMoreElements())
{
String name = (String) curveEnum.nextElement();
X9ECParameters x9ECParameters = SECNamedCurves.getByName(name);
BigInteger n = x9ECParameters.getN();
// The generator is multiplied by random b to get random q
BigInteger b = new BigInteger(n.bitLength(), secRand);
ECPoint g = x9ECParameters.getG();
ECPoint q = g.multiply(b);
// Get point at infinity on the curve
ECPoint infinity = x9ECParameters.getCurve().getInfinity();
implTestAddSubtract(q, infinity);
implTestMultiply(q, n.bitLength());
implTestMultiply(infinity, n.bitLength());
implTestEncoding(q);
}
}
public static Test suite()
{
return new TestSuite(ECPointTest.class);
}
}