Skip to content

Commit 3d56837

Browse files
committed
Reuse a modular power when initializing generic sqrt_ratio constants
Since c2 = 2*c3 + 1, derive c7 and c6 from z^c3 instead of computing two independent powers (RFC 9380 F.2.1.1). Preserve the exact constants and leave the per-call sqrtRatio algorithm unchanged. Cover direct-power equality, zero exponents and negative/unreduced bases, repeated root equations, and the reduced exponentiation count. AI-assisted implementation; benchmark infrastructure is kept in the research fork rather than included in this upstream candidate.
1 parent ab16374 commit 3d56837

3 files changed

Lines changed: 152 additions & 2 deletions

File tree

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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 testInitializationUsesSingleModularExponentiation()
20+
{
21+
CountingBigInteger z = new CountingBigInteger(BigInteger.valueOf(-10));
22+
new GenericSqrtRatioCalculator(new SecP256R1Curve(), z);
23+
assertEquals("constructor modular exponentiations", 1, z.calls);
24+
}
25+
26+
public void testConstantsMatchDirectPowersForSmallFields()
27+
throws Exception
28+
{
29+
// Includes c3 == 0 (3, 5, 17, 257) and several two-adic valuations of q - 1.
30+
int[] primes = { 3, 5, 7, 13, 17, 29, 97, 257 };
31+
for (int i = 0; i < primes.length; ++i)
32+
{
33+
int p = primes[i];
34+
ECCurve curve = smallCurve(BigInteger.valueOf(p));
35+
for (int z = -2 * p; z <= 2 * p; ++z)
36+
{
37+
checkConstants(curve, BigInteger.valueOf(z));
38+
}
39+
}
40+
}
41+
42+
public void testConstantsMatchDirectPowersForLargeFields()
43+
throws Exception
44+
{
45+
ECCurve[] curves = {
46+
new SecP256R1Curve(), new SecP384R1Curve(), new SecP521R1Curve(),
47+
smallCurve(BigInteger.ONE.shiftLeft(255).subtract(BigInteger.valueOf(19))),
48+
smallCurve(BigInteger.ONE.shiftLeft(448).subtract(BigInteger.ONE.shiftLeft(224)).subtract(BigInteger.ONE))
49+
};
50+
Random random = new Random(9380L);
51+
for (int i = 0; i < curves.length; ++i)
52+
{
53+
BigInteger q = curves[i].getField().getCharacteristic();
54+
BigInteger[] edges = {
55+
BigInteger.ZERO, BigInteger.ONE, BigInteger.ONE.negate(),
56+
BigInteger.valueOf(-10), q.subtract(BigInteger.ONE), q,
57+
q.add(BigInteger.ONE), q.shiftLeft(1).add(BigInteger.valueOf(3))
58+
};
59+
for (int j = 0; j < edges.length; ++j)
60+
{
61+
checkConstants(curves[i], edges[j]);
62+
}
63+
for (int j = 0; j < 16; ++j)
64+
{
65+
BigInteger z = new BigInteger(2 * q.bitLength(), random);
66+
checkConstants(curves[i], (j & 1) == 0 ? z : z.negate());
67+
}
68+
}
69+
}
70+
71+
public void testRepeatedRatiosOverSmallFields()
72+
{
73+
int[] primes = { 3, 5, 7, 13, 17, 29 };
74+
for (int i = 0; i < primes.length; ++i)
75+
{
76+
BigInteger q = BigInteger.valueOf(primes[i]);
77+
BigInteger half = q.subtract(BigInteger.ONE).shiftRight(1);
78+
BigInteger z = BigInteger.valueOf(2);
79+
while (z.modPow(half, q).equals(BigInteger.ONE))
80+
{
81+
z = z.add(BigInteger.ONE);
82+
}
83+
GenericSqrtRatioCalculator calculator = new GenericSqrtRatioCalculator(smallCurve(q), z);
84+
for (int u = 1; u < primes[i]; ++u)
85+
{
86+
for (int v = 1; v < primes[i]; ++v)
87+
{
88+
BigInteger numerator = BigInteger.valueOf(u);
89+
BigInteger denominator = BigInteger.valueOf(v);
90+
boolean square = numerator.multiply(denominator.modInverse(q)).mod(q)
91+
.modPow(half, q).equals(BigInteger.ONE);
92+
SqrtRatio result = calculator.sqrtRatio(numerator, denominator);
93+
assertEquals("quadratic-residue flag", square, result.isQR());
94+
BigInteger expected = square ? numerator : numerator.multiply(z).mod(q);
95+
assertEquals("square-root equation", expected,
96+
result.getRatio().multiply(result.getRatio()).multiply(denominator).mod(q));
97+
}
98+
}
99+
}
100+
}
101+
102+
private static ECCurve smallCurve(BigInteger q)
103+
{
104+
// Fixed, known primes only. No point arithmetic is needed for these constant tests.
105+
return new ECCurve.Fp(q, BigInteger.ONE, BigInteger.ONE, null, null, true);
106+
}
107+
108+
private static void checkConstants(ECCurve curve, BigInteger z)
109+
throws Exception
110+
{
111+
BigInteger q = curve.getField().getCharacteristic();
112+
BigInteger oddPart = q.subtract(BigInteger.ONE);
113+
oddPart = oddPart.shiftRight(oddPart.getLowestSetBit());
114+
GenericSqrtRatioCalculator calculator = new GenericSqrtRatioCalculator(curve, z);
115+
// Compare the actual stored constants, not a separate implementation of the rewrite.
116+
assertEquals("c6 for q=" + q + ", z=" + z,
117+
z.modPow(oddPart, q), constant(calculator, "c6"));
118+
assertEquals("c7 for q=" + q + ", z=" + z,
119+
z.modPow(oddPart.add(BigInteger.ONE).shiftRight(1), q), constant(calculator, "c7"));
120+
}
121+
122+
private static BigInteger constant(GenericSqrtRatioCalculator calculator, String name)
123+
throws Exception
124+
{
125+
Field field = GenericSqrtRatioCalculator.class.getDeclaredField(name);
126+
field.setAccessible(true);
127+
return (BigInteger)field.get(calculator);
128+
}
129+
130+
private static class CountingBigInteger
131+
extends BigInteger
132+
{
133+
private int calls;
134+
135+
CountingBigInteger(BigInteger value)
136+
{
137+
super(value.toByteArray());
138+
}
139+
140+
public BigInteger modPow(BigInteger exponent, BigInteger modulus)
141+
{
142+
++calls;
143+
return super.modPow(exponent, modulus);
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)