diff --git a/core/src/main/java/org/bouncycastle/crypto/hash2curve/impl/GenericSqrtRatioCalculator.java b/core/src/main/java/org/bouncycastle/crypto/hash2curve/impl/GenericSqrtRatioCalculator.java index acf5666f51..693f65df02 100644 --- a/core/src/main/java/org/bouncycastle/crypto/hash2curve/impl/GenericSqrtRatioCalculator.java +++ b/core/src/main/java/org/bouncycastle/crypto/hash2curve/impl/GenericSqrtRatioCalculator.java @@ -62,8 +62,10 @@ public GenericSqrtRatioCalculator(final ECCurve curve, final BigInteger z) this.c3 = this.c2.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2)); this.c4 = BigInteger.valueOf(2).pow(this.c1).subtract(BigInteger.ONE); this.c5 = BigInteger.valueOf(2).pow(this.c1 - 1); - this.c6 = z.modPow(this.c2, this.q); - this.c7 = z.modPow(this.c2.add(BigInteger.ONE).divide(BigInteger.valueOf(2)), q); + // c2 = 2*c3 + 1: share z^c3 between the two constants (RFC 9380, F.2.1.1). + BigInteger zToC3 = z.modPow(this.c3, this.q); + this.c7 = zToC3.multiply(z).mod(this.q); + this.c6 = zToC3.multiply(this.c7).mod(this.q); } private int calculateC1() diff --git a/core/src/test/java/org/bouncycastle/crypto/hash2curve/test/AllTests.java b/core/src/test/java/org/bouncycastle/crypto/hash2curve/test/AllTests.java index a16990b020..285530eae7 100644 --- a/core/src/test/java/org/bouncycastle/crypto/hash2curve/test/AllTests.java +++ b/core/src/test/java/org/bouncycastle/crypto/hash2curve/test/AllTests.java @@ -5,6 +5,7 @@ import junit.framework.TestCase; import junit.framework.TestSuite; import org.bouncycastle.crypto.hash2curve.test.impl.GenericSqrtRatioCalculatorTest; +import org.bouncycastle.crypto.hash2curve.test.impl.GenericSqrtRatioConstantsTest; import org.bouncycastle.crypto.hash2curve.test.impl.SimplifiedShallueVanDeWoestijneMapToCurveTest; import org.bouncycastle.test.PrintTestResult; @@ -23,6 +24,7 @@ public static Test suite() suite.addTestSuite(HashToFieldTest.class); suite.addTestSuite(OPRFHashToScalarTest.class); suite.addTestSuite(GenericSqrtRatioCalculatorTest.class); + suite.addTestSuite(GenericSqrtRatioConstantsTest.class); suite.addTestSuite(SimplifiedShallueVanDeWoestijneMapToCurveTest.class); suite.addTestSuite(H2cUtilsTest.class); diff --git a/core/src/test/java/org/bouncycastle/crypto/hash2curve/test/impl/GenericSqrtRatioConstantsTest.java b/core/src/test/java/org/bouncycastle/crypto/hash2curve/test/impl/GenericSqrtRatioConstantsTest.java new file mode 100644 index 0000000000..a79833c5fe --- /dev/null +++ b/core/src/test/java/org/bouncycastle/crypto/hash2curve/test/impl/GenericSqrtRatioConstantsTest.java @@ -0,0 +1,146 @@ +package org.bouncycastle.crypto.hash2curve.test.impl; + +import java.lang.reflect.Field; +import java.math.BigInteger; +import java.util.Random; + +import junit.framework.TestCase; + +import org.bouncycastle.crypto.hash2curve.impl.GenericSqrtRatioCalculator; +import org.bouncycastle.crypto.hash2curve.impl.SqrtRatio; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.custom.sec.SecP256R1Curve; +import org.bouncycastle.math.ec.custom.sec.SecP384R1Curve; +import org.bouncycastle.math.ec.custom.sec.SecP521R1Curve; + +public class GenericSqrtRatioConstantsTest + extends TestCase +{ + public void testInitializationUsesSingleModularExponentiation() + { + CountingBigInteger z = new CountingBigInteger(BigInteger.valueOf(-10)); + new GenericSqrtRatioCalculator(new SecP256R1Curve(), z); + assertEquals("constructor modular exponentiations", 1, z.calls); + } + + public void testConstantsMatchDirectPowersForSmallFields() + throws Exception + { + // Includes c3 == 0 (3, 5, 17, 257) and several two-adic valuations of q - 1. + int[] primes = { 3, 5, 7, 13, 17, 29, 97, 257 }; + for (int i = 0; i < primes.length; ++i) + { + int p = primes[i]; + ECCurve curve = smallCurve(BigInteger.valueOf(p)); + for (int z = -2 * p; z <= 2 * p; ++z) + { + checkConstants(curve, BigInteger.valueOf(z)); + } + } + } + + public void testConstantsMatchDirectPowersForLargeFields() + throws Exception + { + ECCurve[] curves = { + new SecP256R1Curve(), new SecP384R1Curve(), new SecP521R1Curve(), + smallCurve(BigInteger.ONE.shiftLeft(255).subtract(BigInteger.valueOf(19))), + smallCurve(BigInteger.ONE.shiftLeft(448).subtract(BigInteger.ONE.shiftLeft(224)).subtract(BigInteger.ONE)) + }; + Random random = new Random(9380L); + for (int i = 0; i < curves.length; ++i) + { + BigInteger q = curves[i].getField().getCharacteristic(); + BigInteger[] edges = { + BigInteger.ZERO, BigInteger.ONE, BigInteger.ONE.negate(), + BigInteger.valueOf(-10), q.subtract(BigInteger.ONE), q, + q.add(BigInteger.ONE), q.shiftLeft(1).add(BigInteger.valueOf(3)) + }; + for (int j = 0; j < edges.length; ++j) + { + checkConstants(curves[i], edges[j]); + } + for (int j = 0; j < 16; ++j) + { + BigInteger z = new BigInteger(2 * q.bitLength(), random); + checkConstants(curves[i], (j & 1) == 0 ? z : z.negate()); + } + } + } + + public void testRepeatedRatiosOverSmallFields() + { + int[] primes = { 3, 5, 7, 13, 17, 29 }; + for (int i = 0; i < primes.length; ++i) + { + BigInteger q = BigInteger.valueOf(primes[i]); + BigInteger half = q.subtract(BigInteger.ONE).shiftRight(1); + BigInteger z = BigInteger.valueOf(2); + while (z.modPow(half, q).equals(BigInteger.ONE)) + { + z = z.add(BigInteger.ONE); + } + GenericSqrtRatioCalculator calculator = new GenericSqrtRatioCalculator(smallCurve(q), z); + for (int u = 1; u < primes[i]; ++u) + { + for (int v = 1; v < primes[i]; ++v) + { + BigInteger numerator = BigInteger.valueOf(u); + BigInteger denominator = BigInteger.valueOf(v); + boolean square = numerator.multiply(denominator.modInverse(q)).mod(q) + .modPow(half, q).equals(BigInteger.ONE); + SqrtRatio result = calculator.sqrtRatio(numerator, denominator); + assertEquals("quadratic-residue flag", square, result.isQR()); + BigInteger expected = square ? numerator : numerator.multiply(z).mod(q); + assertEquals("square-root equation", expected, + result.getRatio().multiply(result.getRatio()).multiply(denominator).mod(q)); + } + } + } + } + + private static ECCurve smallCurve(BigInteger q) + { + // Fixed, known primes only. No point arithmetic is needed for these constant tests. + return new ECCurve.Fp(q, BigInteger.ONE, BigInteger.ONE, null, null, true); + } + + private static void checkConstants(ECCurve curve, BigInteger z) + throws Exception + { + BigInteger q = curve.getField().getCharacteristic(); + BigInteger oddPart = q.subtract(BigInteger.ONE); + oddPart = oddPart.shiftRight(oddPart.getLowestSetBit()); + GenericSqrtRatioCalculator calculator = new GenericSqrtRatioCalculator(curve, z); + // Compare the actual stored constants, not a separate implementation of the rewrite. + assertEquals("c6 for q=" + q + ", z=" + z, + z.modPow(oddPart, q), constant(calculator, "c6")); + assertEquals("c7 for q=" + q + ", z=" + z, + z.modPow(oddPart.add(BigInteger.ONE).shiftRight(1), q), constant(calculator, "c7")); + } + + private static BigInteger constant(GenericSqrtRatioCalculator calculator, String name) + throws Exception + { + Field field = GenericSqrtRatioCalculator.class.getDeclaredField(name); + field.setAccessible(true); + return (BigInteger)field.get(calculator); + } + + private static class CountingBigInteger + extends BigInteger + { + private int calls; + + CountingBigInteger(BigInteger value) + { + super(value.toByteArray()); + } + + public BigInteger modPow(BigInteger exponent, BigInteger modulus) + { + ++calls; + return super.modPow(exponent, modulus); + } + } +}