Skip to content

Commit 4ee1a79

Browse files
carstenarturpeterdettman
authored andcommitted
GenericSqrtRatioCalculator: Since c2 = 2*c3 + 1, derive c7 and c6 from z^c3 instead of computing
two independent powers (RFC 9380 F.2.1.1), relates to github PR #2455.
1 parent a0293fd commit 4ee1a79

4 files changed

Lines changed: 131 additions & 4 deletions

File tree

‎CONTRIBUTORS.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,3 +548,4 @@ We also wish to acknowledge financial and collaborative support from [CISCO](htt
548548
- vladhuma \<https://github.com/vladhuma\> - initial implementation of server-side OCSP stapling for the BCJSSE provider, on behalf of Thales Group (PR #1740).
549549
- Bhargava Shastry \<bshastry&#064;posteo.de\> - reporting, with a self-checking reproducer, that the C509 validity fields were read as unbounded seconds and then given two meanings: displacing a type-3 certificate's validityNotAfter by 2^61 seconds left the reconstructed DER TBSCertificate and the issuer signature untouched, while C509CertificateHolder.isValidOn took the raw value and read an expired certificate as valid.
550550
- Rob Augustinus \<rob&#064;opensolutions.nl\> - a constant-time analysis of the elliptic-curve and AES code paths, with proof-of-concept code, which independently reached the variable-point scalar multiplication exposure addressed in 1.86.
551+
- Carsten Hammer \<https://github.com/carstenartur\> - deriving the c6 and c7 constants of the generic RFC 9380 sqrt_ratio calculator from a shared z^c3, saving a modular exponentiation per instance, with tests comparing the stored constants against the direct powers (PR #2455).

‎core/src/main/java/org/bouncycastle/crypto/hash2curve/impl/GenericSqrtRatioCalculator.java‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@
2020
* RFC 9380 defines optimized sqrt_ratio formulas for certain curves where the field prime p
2121
* satisfies special congruences (e.g. p ≡ 3 mod 4 or p ≡ 5 mod 8). However, those optimizations are
2222
* curve-specific and do not apply to all hash-to-curve suites. This implementation instead follows
23-
* the fully generic algorithm from Section 5.6.3 of RFC 9380, which is valid for any elliptic curve
23+
* the fully generic algorithm from Appendix F.2.1.1 of RFC 9380, which is valid for any elliptic curve
2424
* defined over a prime field Fp.
2525
* </p>
2626
*
2727
* <p>
28-
* This generic version supports all curves used in the RFC 9830 test vectors, including the NIST
28+
* This generic version supports all curves used in the RFC 9380 test vectors, including the NIST
2929
* P-256 / P-384 / P-521 curves, Curve25519, Edwards25519 (Ristretto255), Curve448, and Edwards448
3030
* (Decaf448). It provides a single uniform implementation suitable for all supported hash-to-curve
3131
* suites.
@@ -62,8 +62,10 @@ public GenericSqrtRatioCalculator(final ECCurve curve, final BigInteger z)
6262
this.c3 = this.c2.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2));
6363
this.c4 = BigInteger.valueOf(2).pow(this.c1).subtract(BigInteger.ONE);
6464
this.c5 = BigInteger.valueOf(2).pow(this.c1 - 1);
65-
this.c6 = z.modPow(this.c2, this.q);
66-
this.c7 = z.modPow(this.c2.add(BigInteger.ONE).divide(BigInteger.valueOf(2)), q);
65+
// c2 = 2*c3 + 1: share z^c3 between the two constants (RFC 9380, F.2.1.1).
66+
BigInteger zToC3 = z.modPow(this.c3, this.q);
67+
this.c7 = zToC3.multiply(z).mod(this.q);
68+
this.c6 = zToC3.multiply(this.c7).mod(this.q);
6769
}
6870

6971
private int calculateC1()

‎core/src/test/java/org/bouncycastle/crypto/hash2curve/test/AllTests.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import junit.framework.TestCase;
66
import junit.framework.TestSuite;
77
import org.bouncycastle.crypto.hash2curve.test.impl.GenericSqrtRatioCalculatorTest;
8+
import org.bouncycastle.crypto.hash2curve.test.impl.GenericSqrtRatioConstantsTest;
89
import org.bouncycastle.crypto.hash2curve.test.impl.SimplifiedShallueVanDeWoestijneMapToCurveTest;
910
import org.bouncycastle.test.PrintTestResult;
1011

@@ -23,6 +24,7 @@ public static Test suite()
2324
suite.addTestSuite(HashToFieldTest.class);
2425
suite.addTestSuite(OPRFHashToScalarTest.class);
2526
suite.addTestSuite(GenericSqrtRatioCalculatorTest.class);
27+
suite.addTestSuite(GenericSqrtRatioConstantsTest.class);
2628

2729
suite.addTestSuite(SimplifiedShallueVanDeWoestijneMapToCurveTest.class);
2830
suite.addTestSuite(H2cUtilsTest.class);
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package org.bouncycastle.crypto.hash2curve.test.impl;
2+
3+
import java.lang.reflect.Field;
4+
import java.math.BigInteger;
5+
import java.util.Random;
6+
7+
import junit.framework.TestCase;
8+
9+
import org.bouncycastle.crypto.hash2curve.impl.GenericSqrtRatioCalculator;
10+
import org.bouncycastle.crypto.hash2curve.impl.SqrtRatio;
11+
import org.bouncycastle.math.ec.ECCurve;
12+
import org.bouncycastle.math.ec.custom.sec.SecP256R1Curve;
13+
import org.bouncycastle.math.ec.custom.sec.SecP384R1Curve;
14+
import org.bouncycastle.math.ec.custom.sec.SecP521R1Curve;
15+
16+
public class GenericSqrtRatioConstantsTest
17+
extends TestCase
18+
{
19+
public void testConstantsMatchDirectPowersForSmallFields()
20+
throws Exception
21+
{
22+
// Includes c3 == 0 (3, 5, 17, 257) and several two-adic valuations of q - 1.
23+
int[] primes = { 3, 5, 7, 13, 17, 29, 97, 257 };
24+
for (int i = 0; i < primes.length; ++i)
25+
{
26+
int p = primes[i];
27+
ECCurve curve = smallCurve(BigInteger.valueOf(p));
28+
for (int z = -2 * p; z <= 2 * p; ++z)
29+
{
30+
checkConstants(curve, BigInteger.valueOf(z));
31+
}
32+
}
33+
}
34+
35+
public void testConstantsMatchDirectPowersForLargeFields()
36+
throws Exception
37+
{
38+
ECCurve[] curves = {
39+
new SecP256R1Curve(), new SecP384R1Curve(), new SecP521R1Curve(),
40+
smallCurve(BigInteger.ONE.shiftLeft(255).subtract(BigInteger.valueOf(19))),
41+
smallCurve(BigInteger.ONE.shiftLeft(448).subtract(BigInteger.ONE.shiftLeft(224)).subtract(BigInteger.ONE))
42+
};
43+
Random random = new Random(9380L);
44+
for (int i = 0; i < curves.length; ++i)
45+
{
46+
BigInteger q = curves[i].getField().getCharacteristic();
47+
BigInteger[] edges = {
48+
BigInteger.ZERO, BigInteger.ONE, BigInteger.ONE.negate(),
49+
BigInteger.valueOf(-10), q.subtract(BigInteger.ONE), q,
50+
q.add(BigInteger.ONE), q.shiftLeft(1).add(BigInteger.valueOf(3))
51+
};
52+
for (int j = 0; j < edges.length; ++j)
53+
{
54+
checkConstants(curves[i], edges[j]);
55+
}
56+
for (int j = 0; j < 16; ++j)
57+
{
58+
BigInteger z = new BigInteger(2 * q.bitLength(), random);
59+
checkConstants(curves[i], (j & 1) == 0 ? z : z.negate());
60+
}
61+
}
62+
}
63+
64+
public void testRepeatedRatiosOverSmallFields()
65+
{
66+
int[] primes = { 3, 5, 7, 13, 17, 29 };
67+
for (int i = 0; i < primes.length; ++i)
68+
{
69+
BigInteger q = BigInteger.valueOf(primes[i]);
70+
BigInteger half = q.subtract(BigInteger.ONE).shiftRight(1);
71+
BigInteger z = BigInteger.valueOf(2);
72+
while (z.modPow(half, q).equals(BigInteger.ONE))
73+
{
74+
z = z.add(BigInteger.ONE);
75+
}
76+
GenericSqrtRatioCalculator calculator = new GenericSqrtRatioCalculator(smallCurve(q), z);
77+
for (int u = 1; u < primes[i]; ++u)
78+
{
79+
for (int v = 1; v < primes[i]; ++v)
80+
{
81+
BigInteger numerator = BigInteger.valueOf(u);
82+
BigInteger denominator = BigInteger.valueOf(v);
83+
boolean square = numerator.multiply(denominator.modInverse(q)).mod(q)
84+
.modPow(half, q).equals(BigInteger.ONE);
85+
SqrtRatio result = calculator.sqrtRatio(numerator, denominator);
86+
assertEquals("quadratic-residue flag", square, result.isQR());
87+
BigInteger expected = square ? numerator : numerator.multiply(z).mod(q);
88+
assertEquals("square-root equation", expected,
89+
result.getRatio().multiply(result.getRatio()).multiply(denominator).mod(q));
90+
}
91+
}
92+
}
93+
}
94+
95+
private static ECCurve smallCurve(BigInteger q)
96+
{
97+
// Fixed, known primes only. No point arithmetic is needed for these constant tests.
98+
return new ECCurve.Fp(q, BigInteger.ONE, BigInteger.ONE, null, null, true);
99+
}
100+
101+
private static void checkConstants(ECCurve curve, BigInteger z)
102+
throws Exception
103+
{
104+
BigInteger q = curve.getField().getCharacteristic();
105+
BigInteger oddPart = q.subtract(BigInteger.ONE);
106+
oddPart = oddPart.shiftRight(oddPart.getLowestSetBit());
107+
GenericSqrtRatioCalculator calculator = new GenericSqrtRatioCalculator(curve, z);
108+
// Compare the actual stored constants, not a separate implementation of the rewrite.
109+
assertEquals("c6 for q=" + q + ", z=" + z,
110+
z.modPow(oddPart, q), constant(calculator, "c6"));
111+
assertEquals("c7 for q=" + q + ", z=" + z,
112+
z.modPow(oddPart.add(BigInteger.ONE).shiftRight(1), q), constant(calculator, "c7"));
113+
}
114+
115+
private static BigInteger constant(GenericSqrtRatioCalculator calculator, String name)
116+
throws Exception
117+
{
118+
Field field = GenericSqrtRatioCalculator.class.getDeclaredField(name);
119+
field.setAccessible(true);
120+
return (BigInteger)field.get(calculator);
121+
}
122+
}

0 commit comments

Comments
 (0)