From 19ff338be9c20886f1200ecad0d9d55cbbb2a5b7 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Mon, 4 May 2026 16:36:23 +0000 Subject: [PATCH] mp_cond_swap_ct: branchless masked XOR --- wolfcrypt/src/integer.c | 41 +++++++++++++++++++++++++++------ wolfcrypt/test/test.c | 50 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/wolfcrypt/src/integer.c b/wolfcrypt/src/integer.c index 91e457cdee..65789c41c7 100644 --- a/wolfcrypt/src/integer.c +++ b/wolfcrypt/src/integer.c @@ -549,21 +549,48 @@ int mp_exch (mp_int * a, mp_int * b) return MP_OKAY; } +/* Constant-time conditional swap: must not branch on m (leaks scalar bit). + * m must be 0 or 1. The t parameter is unused; XOR is performed in place + * with a single-digit stack scratch so callers don't need to clear t->dp. */ int mp_cond_swap_ct_ex (mp_int * a, mp_int * b, int c, int m, mp_int * t) { - (void)c; + int i; + int err; + int imask; + int idiff; + mp_digit mask; + mp_digit d; + (void)t; - if (m == 1) - mp_exch(a, b); + + m &= 1; + imask = -m; + mask = (mp_digit)0 - (mp_digit)m; + + if ((err = mp_grow(a, c)) != MP_OKAY) + return err; + if ((err = mp_grow(b, c)) != MP_OKAY) + return err; + + idiff = (a->used ^ b->used) & imask; + a->used ^= idiff; + b->used ^= idiff; + idiff = (a->sign ^ b->sign) & imask; + a->sign ^= idiff; + b->sign ^= idiff; + + for (i = 0; i < c; i++) { + d = (a->dp[i] ^ b->dp[i]) & mask; + a->dp[i] ^= d; + b->dp[i] ^= d; + } + return MP_OKAY; } int mp_cond_swap_ct (mp_int * a, mp_int * b, int c, int m) { - (void)c; - if (m == 1) - mp_exch(a, b); - return MP_OKAY; + return mp_cond_swap_ct_ex(a, b, c, m, NULL); } diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 361f16d631..28a3975090 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -66406,6 +66406,52 @@ static wc_test_ret_t mp_test_mont(mp_int* a, mp_int* m, mp_int* n, mp_int* r, WC } #endif +/* mp_cond_swap_ct resolves to sp_cond_swap_ct in SP-math builds, which is only + * compiled when HAVE_ECC && ECC_TIMING_RESISTANT && !WC_NO_CACHE_RESISTANT. + * Gate the test to match. */ +#if !defined(WOLFSSL_SP_MATH) && !defined(WOLFSSL_SP_MATH_ALL) + #define MP_TEST_COND_SWAP_AVAILABLE +#elif defined(HAVE_ECC) && defined(ECC_TIMING_RESISTANT) && \ + !defined(WC_NO_CACHE_RESISTANT) + #define MP_TEST_COND_SWAP_AVAILABLE +#endif + +#ifdef MP_TEST_COND_SWAP_AVAILABLE +static wc_test_ret_t mp_test_cond_swap(mp_int* a, mp_int* b) +{ + int ret; + int c; + + mp_zero(a); + mp_zero(b); + ret = mp_set_int(a, 0x12345678); + if (ret != 0) return WC_TEST_RET_ENC_EC(ret); + ret = mp_set_int(b, 0x9abcdef0); + if (ret != 0) return WC_TEST_RET_ENC_EC(ret); + c = (a->used > b->used) ? a->used : b->used; + + /* m == 0: no swap. */ + ret = mp_cond_swap_ct(a, b, c, 0); + if (ret != 0) return WC_TEST_RET_ENC_EC(ret); + if (a->dp[0] != 0x12345678 || b->dp[0] != 0x9abcdef0) + return WC_TEST_RET_ENC_NC; + + /* m == 1: swap. */ + ret = mp_cond_swap_ct(a, b, c, 1); + if (ret != 0) return WC_TEST_RET_ENC_EC(ret); + if (a->dp[0] != 0x9abcdef0 || b->dp[0] != 0x12345678) + return WC_TEST_RET_ENC_NC; + + /* m == 1 again: swap back. */ + ret = mp_cond_swap_ct(a, b, c, 1); + if (ret != 0) return WC_TEST_RET_ENC_EC(ret); + if (a->dp[0] != 0x12345678 || b->dp[0] != 0x9abcdef0) + return WC_TEST_RET_ENC_NC; + + return 0; +} +#endif /* MP_TEST_COND_SWAP_AVAILABLE */ + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t mp_test(void) { WC_RNG rng; @@ -66719,6 +66765,10 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t mp_test(void) if ((ret = mp_test_mont(a, b, r1, r2, &rng)) != 0) goto done; #endif +#ifdef MP_TEST_COND_SWAP_AVAILABLE + if ((ret = mp_test_cond_swap(a, b)) != 0) + goto done; +#endif done: