Use constant-time select in modExp (#1984)

modExp is used in some RSA, DSA and ElGamal operations.
With native bigints, the constant-time select no longer introduces
a considerable slowdown, so we switch to that to avoid secret-dependent branching,
which in principle enables e.g. cache timing attacks
(NB: these are still not relevant for our threat model).

Thanks to Yayu Wang, Kjell Dankert and Aastha Mehta from the
University of British Columbia for reporting the potential security
issue and validating the fix.
This commit is contained in:
larabr
2026-03-12 17:27:08 +01:00
committed by GitHub
parent 0e9eff6272
commit 10e2e4fb65

View File

@@ -22,6 +22,18 @@ export function mod(a: bigint, m: bigint) {
return reduced < _0n ? reduced + m : reduced;
}
/**
* Return either `a` or `b` based on `cond`, in algorithmic constant time.
* @param {BigInt} cond
* @param {BigInt} a
* @param {BigInt} b
* @returns `a` if `cond` is `1n`, `b` otherwise
*/
function selectBigInt(cond: 0n | 1n, a: bigint, b: bigint) {
const mask = -cond; // ~0n === -1n
return (a & mask) | (b & ~mask);
}
/**
* Compute modular exponentiation using square and multiply
* @param {BigInt} a - Base
@@ -40,12 +52,12 @@ export function modExp(b: bigint, e: bigint, n: bigint) {
x %= n;
let r = BigInt(1);
while (exp > _0n) {
const lsb = exp & _1n;
const lsb = (exp & _1n) as 1n | 0n;
exp >>= _1n; // e / 2
// Always compute multiplication step, to reduce timing leakage
const rx = (r * x) % n;
// Update r only if lsb is 1 (odd exponent)
r = lsb ? rx : r;
r = selectBigInt(lsb, rx, r);
x = (x * x) % n; // Square
}
return r;