On Mon, 24 Aug 2026 08:18:13 GMT, Quan Anh Mai <[email protected]> wrote:
>>> The goal is to try to reproduce the same shape that we see in production EC
>>> operations.
>>
>> Maybe I'm misunderstanding something, but here's what I think is problematic:
>>
>> We have an implementation that executes in constant time, but only if a
>> compiler cannot predict a flag. It would take a little improvement in an
>> optimizing compiler to generate two versions of the code, calling either one
>> or the other depending on that flag. If it did,even with a non-constant flag
>> the supposed constant-time implementation would be no such thing. Am I wrong?
>
> I guess what @theRealAph is referring to is the following. Looking at the
> spec of `MutableIntegerModuloP::conditionalSet`, it only accepts the second
> argument being 0 or 1. If, we do profiling for `int` parameters, and see that
> they are the only possible values of `set`, the compiler may emit code like
> this:
>
> void conditionalSet(IntegerModuloP b, int set) {
> if (set == 0) {
> return conditionalSet(b, 0);
> } else if (set == 1) {
> return conditionalSet(b, 1);
> } else {
> trap();
> }
> }
>
> Then, there is a risk of time-based speculation.
@merykitty Your recursive definition of 'conditionalSet'in terms of itself is a
bit confusing. Let's look at the actual code.
`MutableIntegerModuloP` is an interface. Its method `conditionalSet` is
actually provided by class `IntegerPolynomial` (and not overriden by any of its
implementations) as follows:
@Override
public void conditionalSet(IntegerModuloP b, int set) {
assert IntegerPolynomial.this == b.getField();
Element other = (Element) b;
conditionalAssign(set, limbs, other.limbs);
numAdds = other.numAdds;
}
The implementation work is actually done in:
@ForceInline
@IntrinsicCandidate
protected static void conditionalAssign(int set, long[] a, long[] b) {
int maskValue = -set;
for (int i = 0; i < a.length; i++) {
long dummyLimbs = maskValue & (a[i] ^ b[i]);
a[i] = dummyLimbs ^ a[i];
}
}
Neither method distinguishes 0 and 1 as special cases. However, it is expected
that the caller only passes 0 or 1.
So, I agree there is the potential for a speculative optimization based on
profiling of arguments to `conditionalSet` where it can can be transformed as
follows:
void conditionalSet(IntegerModuloP b, int set) {
MutableIntegerModuloP.java
assert IntegerPolynomial.this == b.getField();
Element other = (Element) b;
if (set == 0) {
conditionalAssign(0, limbs, other.limbs);
} else if (set == 1) {
conditionalAssign(1, limbs, other.limbs);
} else {
trap();
}
numAdds = other.numAdds;
Inlining of the call to `conditionalAssign` on this basis could lead to
asymmetrical execution times for the two cases which might allow an attacker to
obtain some side-channel information from a carefully constructed input.
However, I don't think that can happen in practice because of the
`@IntrinsicCandidate` annotation. A transform based on profiling info could
only be used when compiling `conditionalSet` in which case a call to the
intrinsic would override the inlining operation and speculative transformation.
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/32047#discussion_r3842576866