Potential ECC signing optimization: optional blinding in ecc_sign_hash_internal #798

Open
opened 2026-08-26 09:03:07 -05:00 by headscott · 11 comments
headscott commented 2026-08-26 09:03:07 -05:00 (Migrated from github.com)

Description

While reviewing ecc_sign_hash_internal, I noticed that the calculation of s = (e + xr) / k performs several additional modular multiplications because a random blinding value b is applied to k.

The current implementation contains:

         /* find s = (e + xr)/k */
         if ((err = ltc_mp_mulmod(pubkey.k, b, p, pubkey.k)) != CRYPT_OK)      { goto error; } /* k = kb */
         if ((err = ltc_mp_invmod(pubkey.k, p, pubkey.k)) != CRYPT_OK)         { goto error; } /* k = 1/kb */
         if ((err = ltc_mp_mulmod(key->k, r, p, s)) != CRYPT_OK)               { goto error; } /* s = xr */
         if ((err = ltc_mp_mulmod(pubkey.k, s, p, s)) != CRYPT_OK)             { goto error; } /* s = xr/kb */
         if ((err = ltc_mp_mulmod(pubkey.k, e, p, e)) != CRYPT_OK)             { goto error; } /* e = e/kb */
         if ((err = ltc_mp_add(e, s, s)) != CRYPT_OK)                          { goto error; } /* s = e/kb + xr/kb */
         if ((err = ltc_mp_mulmod(s, b, p, s)) != CRYPT_OK)                    { goto error; } /* s = b(e/kb + xr/kb) = (e + xr)/k */

Algebraically, the blinding factor cancels out:

kb = k * b

1 / kb = 1 / (k * b)

b * ((e / kb) + (xr / kb))
    = b * ((e + xr) / (k * b))
    = (e + xr) / k

Without blinding, the calculation could therefore be reduced to:

         /* find s = (e + xr)/k */
         if ((err = ltc_mp_invmod(pubkey.k, p, pubkey.k)) != CRYPT_OK) { goto error; } /* k = 1/k */
         if ((err = ltc_mp_mulmod(key->k, r, p, s)) != CRYPT_OK)       { goto error; }
         if ((err = ltc_mp_add(e, s, s)) != CRYPT_OK)                  { goto error; } /* s = e +  xr */
         if ((err = ltc_mp_mulmod(s, pubkey.k, p, s)) != CRYPT_OK)     { goto error; } /* s = (e + xr)/k */

This reduces the number of ltc_mp_mulmod() calls from five to two.

However, since b is used as a blinding value, simply removing it would also cause ltc_mp_invmod() to operate directly on the secret ephemeral scalar k. Therefore, I would not suggest removing the blinding unconditionally.

Instead, would it make sense to introduce an optional LTC_ECC_BLINDING configuration define, similar in concept to the existing configurable LTC_RSA_BLINDING support?

For example:

#ifdef LTC_ECC_BLINDING

/* existing blinded implementation */

#else

/* simplified implementation without blinding */

#endif

This could allow applications that require the additional side-channel protection to keep ECC blinding enabled, while applications where this protection is not required could avoid the additional modular multiplications.

Question

Could someone confirm whether the simplified calculation is mathematically equivalent for all supported ECC curves and whether introducing an LTC_ECC_BLINDING configuration option would be a reasonable approach?

If so, it may be possible to reduce the computational cost of ecc_sign_hash_internal when ECC blinding is disabled, while retaining the current blinded implementation for applications that require the additional side-channel protection.

## Description While reviewing `ecc_sign_hash_internal`, I noticed that the calculation of `s = (e + xr) / k` performs several additional modular multiplications because a random blinding value `b` is applied to `k`. The current implementation contains: ```c /* find s = (e + xr)/k */ if ((err = ltc_mp_mulmod(pubkey.k, b, p, pubkey.k)) != CRYPT_OK) { goto error; } /* k = kb */ if ((err = ltc_mp_invmod(pubkey.k, p, pubkey.k)) != CRYPT_OK) { goto error; } /* k = 1/kb */ if ((err = ltc_mp_mulmod(key->k, r, p, s)) != CRYPT_OK) { goto error; } /* s = xr */ if ((err = ltc_mp_mulmod(pubkey.k, s, p, s)) != CRYPT_OK) { goto error; } /* s = xr/kb */ if ((err = ltc_mp_mulmod(pubkey.k, e, p, e)) != CRYPT_OK) { goto error; } /* e = e/kb */ if ((err = ltc_mp_add(e, s, s)) != CRYPT_OK) { goto error; } /* s = e/kb + xr/kb */ if ((err = ltc_mp_mulmod(s, b, p, s)) != CRYPT_OK) { goto error; } /* s = b(e/kb + xr/kb) = (e + xr)/k */ ``` Algebraically, the blinding factor cancels out: ```text kb = k * b 1 / kb = 1 / (k * b) b * ((e / kb) + (xr / kb)) = b * ((e + xr) / (k * b)) = (e + xr) / k ``` Without blinding, the calculation could therefore be reduced to: ```c /* find s = (e + xr)/k */ if ((err = ltc_mp_invmod(pubkey.k, p, pubkey.k)) != CRYPT_OK) { goto error; } /* k = 1/k */ if ((err = ltc_mp_mulmod(key->k, r, p, s)) != CRYPT_OK) { goto error; } if ((err = ltc_mp_add(e, s, s)) != CRYPT_OK) { goto error; } /* s = e + xr */ if ((err = ltc_mp_mulmod(s, pubkey.k, p, s)) != CRYPT_OK) { goto error; } /* s = (e + xr)/k */ ``` This reduces the number of `ltc_mp_mulmod()` calls from **five to two**. However, since `b` is used as a blinding value, simply removing it would also cause `ltc_mp_invmod()` to operate directly on the secret ephemeral scalar `k`. Therefore, I would not suggest removing the blinding unconditionally. Instead, would it make sense to introduce an optional `LTC_ECC_BLINDING` configuration define, similar in concept to the existing configurable `LTC_RSA_BLINDING` support? For example: ```c #ifdef LTC_ECC_BLINDING /* existing blinded implementation */ #else /* simplified implementation without blinding */ #endif ``` This could allow applications that require the additional side-channel protection to keep ECC blinding enabled, while applications where this protection is not required could avoid the additional modular multiplications. ## Question Could someone confirm whether the simplified calculation is mathematically equivalent for all supported ECC curves and whether introducing an `LTC_ECC_BLINDING` configuration option would be a reasonable approach? If so, it may be possible to reduce the computational cost of `ecc_sign_hash_internal` when ECC blinding is disabled, while retaining the current blinded implementation for applications that require the additional side-channel protection.
karel-m commented 2026-08-31 04:26:50 -05:00 (Migrated from github.com)

Thanks for your report, but ECC implementations w/o blinding are generally considered vulnerable to side-channel attacks nowadays.

So I don't think disabling it should be supported.

Thanks for your report, but ECC implementations w/o blinding are generally considered vulnerable to side-channel attacks nowadays. So I don't think disabling it should be supported.
sjaeckel commented 2026-09-01 07:25:06 -05:00 (Migrated from github.com)

We still provide a vulnerable ltc_ecc_mulmod() in src/pk/ecc/ltc_ecc_mulmod.c which is disabled by default ... RSA blinding can also be disabled ... so I kind of see the point of @headscott but TBH I'm not sure whether we should really have all those knobs ...

We still provide a vulnerable `ltc_ecc_mulmod()` in `src/pk/ecc/ltc_ecc_mulmod.c` which is disabled by default ... RSA blinding can also be disabled ... so I kind of see the point of @headscott but TBH I'm not sure whether we should really have all those knobs ...
headscott commented 2026-09-01 09:00:58 -05:00 (Migrated from github.com)

I agree that having too many knobs can be undesirable, but I think this one is worth having.

The main argument for me is that this is an explicit security/performance trade-off, not just a tuning option. Blinding provides additional side-channel protection, while disabling it can significantly reduce the cost of ECC signing. Since RSA already exposes the same kind of choice, having an equivalent ECC option also makes the behavior more consistent.

I’d keep blinding enabled by default, so there’s no security regression for existing users, and make the opt-out an explicit build-time choice for applications where the side-channel threat model is different.

So IMO LTC_ECC_BLINDING is a reasonable knob, whereas I’d agree that adding knobs for purely micro-optimization would probably be too much.

I agree that having too many knobs can be undesirable, but I think this one is worth having. The main argument for me is that this is an explicit security/performance trade-off, not just a tuning option. Blinding provides additional side-channel protection, while disabling it can significantly reduce the cost of ECC signing. Since RSA already exposes the same kind of choice, having an equivalent ECC option also makes the behavior more consistent. I’d keep blinding enabled by default, so there’s no security regression for existing users, and make the opt-out an explicit build-time choice for applications where the side-channel threat model is different. So IMO `LTC_ECC_BLINDING` is a reasonable knob, whereas I’d agree that adding knobs for purely micro-optimization would probably be too much.
mkj commented 2026-09-01 20:36:15 -05:00 (Migrated from github.com)

@headscott what's the application where signing is too slow currently?

@headscott what's the application where signing is too slow currently?
headscott commented 2026-09-02 01:25:40 -05:00 (Migrated from github.com)

I think there are a few practical use cases where having the option to disable ECC blinding could be useful.

For example, applications that generate a large number of signatures may benefit from the reduced computational cost, especially when signing performance is relevant and the additional side-channel protection provided by blinding is not required in their threat model.

I also see value in having the option for benchmarking and comparative analysis. Being able to run the same implementation with and without blinding makes it easier to quantify the actual performance overhead and compare it with other ECC implementations.

From a development and debugging perspective, having the two modes available could also be useful when investigating performance regressions or trying to isolate where the computational overhead comes from.

Of course, I would consider ECC blinding the safer default. The option to disable it would mainly be useful for applications where the threat model permits it, or for controlled benchmarking/testing environments.

I think there are a few practical use cases where having the option to disable ECC blinding could be useful. For example, applications that generate a large number of signatures may benefit from the reduced computational cost, especially when signing performance is relevant and the additional side-channel protection provided by blinding is not required in their threat model. I also see value in having the option for benchmarking and comparative analysis. Being able to run the same implementation with and without blinding makes it easier to quantify the actual performance overhead and compare it with other ECC implementations. From a development and debugging perspective, having the two modes available could also be useful when investigating performance regressions or trying to isolate where the computational overhead comes from. Of course, I would consider ECC blinding the safer default. The option to disable it would mainly be useful for applications where the threat model permits it, or for controlled benchmarking/testing environments.
mkj commented 2026-09-02 01:44:14 -05:00 (Migrated from github.com)

Premptively reducing security "in case someone needs to go faster" doesn't seem like a good way to design a crypto library!

Premptively reducing security "in case someone needs to go faster" doesn't seem like a good way to design a crypto library!
sjaeckel commented 2026-09-02 03:55:48 -05:00 (Migrated from github.com)

Additionally I guess that this would be another case of YAGNI.

Still I can see the reasoning behind what @headscott is saying.

IMO we should first add the option LTC_ECC_BLINDING and in a separate PR remove all those knobs which would reduce security altogether, so whoever may need this can fork the library from before that change and maintain their own version. wdyt?

Additionally I guess that this would be another case of YAGNI. Still I can see the reasoning behind what @headscott is saying. IMO we should first add the option `LTC_ECC_BLINDING` and in a separate PR remove all those knobs which would reduce security altogether, so whoever may need this can fork the library from before that change and maintain their own version. wdyt?
headscott commented 2026-09-02 04:10:53 -05:00 (Migrated from github.com)

That's a good idea. This should be well documented then.

That's a good idea. This should be well documented then.
karel-m commented 2026-09-02 07:15:17 -05:00 (Migrated from github.com)

In that case please give it more discouraging name like LTC_ECC_DANGEROUS_INSECURE_UNBLINDED

In that case please give it more discouraging name like `LTC_ECC_DANGEROUS_INSECURE_UNBLINDED`
sjaeckel commented 2026-09-02 08:39:09 -05:00 (Migrated from github.com)

Since we already have LTC_RSA_BLINDING and we're going to remove the option shortly after introducing it, I don't see a reason why we shouldn't call it LTC_ECC_BLINDING... or we also rename the RSA option to LTC_RSA_DANGEROUS_INSECURE_UNBLINDED.

Since we already have `LTC_RSA_BLINDING` **and** we're going to remove the option shortly after introducing it, I don't see a reason why we shouldn't call it `LTC_ECC_BLINDING`... or we also rename the RSA option to `LTC_RSA_DANGEROUS_INSECURE_UNBLINDED`.
karel-m commented 2026-09-02 10:23:37 -05:00 (Migrated from github.com)

If we really do want to suppot these I am for LTC_ECC_DANGEROUS_INSECURE_UNBLINDED + LTC_RSA_DANGEROUS_INSECURE_UNBLINDED

If we really do want to suppot these I am for `LTC_ECC_DANGEROUS_INSECURE_UNBLINDED` + `LTC_RSA_DANGEROUS_INSECURE_UNBLINDED`
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-libtom/libtomcrypt#798
No description provided.