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

Reply via email to