Changes:
- removes unwanted
fe_normalize_weakcalls to the second argument offe_equal - removes
fe_equal_var
63 | @@ -64,10 +64,13 @@ static int secp256k1_fe_is_zero(const secp256k1_fe *a); 64 | /** Check the "oddness" of a field element. Requires the input to be normalized. */ 65 | static int secp256k1_fe_is_odd(const secp256k1_fe *a); 66 | 67 | -/** Compare two field elements. Requires magnitude-1 inputs. */ 68 | +/** Compare two field elements. Requires both the inputs to have magnitude = 1. 69 | + * i.e. a->magnitude = 1 and b->magnitude = 1 */
Just a nit but why not just
/** Compare two field elements. Requires both the inputs to have magnitude 1. */
(Hm, in fact it's <= 1 even but that should go away when #1001 is solved).
I don't think it's necessary to phrase it twice (and x->magnitude is only there #ifdef VERIFY -- but ok people would understands what is meant. ;))
Same below.
Sure, I will remove this additional comment. I wanted to be more explicit, but I forgot that x->magnitude is used only in the VERIFY build.
Concept ACK
I think then we should add (#ifdef VERIFY) magnitude checks to secp256k1_fe_equal_var and secp256k1_fe_equal.
If you're up to it, I think the other field function could need an audit for similar checks.
In fe_sqrt:
https://github.com/bitcoin-core/secp256k1/blob/a1102b12196ea27f44d6201de4d25926a2ae9640/src/field_impl.h#L132-L135
Here, the secp256k1_fe_sqr function ensures that t1->magnitude = 1 but there is no guarantee for the magnitude of a to be one since it is passed into the function. Hence, some fe_sqrt tests are failing after adding magnitude checks inside fe_equal.
We could do secp256k1_fe_normalize_weak(a) but this changes the value of a and we don't want that right? Alternatively, we could call fe_equal_var instead of fe_equal. Is it necessary for secp256k1_fe_sqrt to be of constant time?
@siv2r Regarding _fe_sqrt, there is an implicit magnitude restriction on a, since it is used as the input to _fe_mul and _fe_sqr at the top of the method. That magnitude restriction is <= 8. 8 is also a kind of global implicit limit for inputs where not otherwise stated (but we should definitely be working towards fully documenting and validating all such assumptions).
I think it might be better to just say that both inputs to the equals methods can be magnitude <= 8 too (indeed tied to the mul/sqr limit whatever it is or changes to). There's no real obstacle to the implementation(s) (just change the third argument to _fe_negate to 8) and no performance hit AFAICT.
Is it necessary for
secp256k1_fe_sqrtto be of constant time?
It's only used in a single var-time caller, so technically it could be changed to sqrt_var, but preferably not.
Apparently secp256k1_fe_equal and secp256k1_fe_equal_var are identical up to the final call to normalizes_to_zero or normalizes_to_zero_var and so they both only require that a has magnitude 1. So we should just document that secp256k1_fe_equal only requires the magnitude of a to be 1.
Edited because I first thought secp256k1_fe_equal and secp256k1_fe_equal_var are exactly identical.
It's only used in a single var-time caller
Oh, Okay. Then, creating a new function for secp256k1_fe_sqrt_var (while renaming the old one to _fe_sqrt_const) might be overkill.
we should just document that
secp256k1_fe_equalonly requires the magnitude ofato be 1.
Yes, I think this might be a better approach since it keeps the functionality of both fe_equal_var and fe_equal similar.
After this, I felt we needed to remove the _fe_normalize_weak calls before _fe_equal, but surprisingly _fe_equal is used rarely in the code. The only other place where it is used is:
https://github.com/bitcoin-core/secp256k1/blob/a1102b12196ea27f44d6201de4d25926a2ae9640/src/modules/extrakeys/tests_impl.h#L75-L77
Even here, the magnitude of y is two, so modifying the _fe_sqrt function (which I suggested) is a bad idea.
Hehe ok, and here it's basically an oversight. This is just test code, so fe_equal_var could be used instead. I mean, not that it matters.
fe_equal_var is really a bit dubious. It only has a fast path when the inputs are not equal, and the only callers are for public key parsing and (ECDSA) verification, where that would mean an error condition and is probably uncommon. If the fast path is not hit, it actually costs a couple of cycles more. (Not to mention that the performance difference is tiny relative to those operations anyway).
While I'm on the subject, the other callers to _fe_normalizes_to_zero_var expect a false result and so will almost always hit the fast path. Depending on the level of inlining, _fe_equal_var could be "spoiling" the branch prediction for that fast path check for the other callers.
addressed #1062 (review) and #1062#pullrequestreview-844742940 (detailed explanation in 08186e8).
I also benchmarked the tests.c file (using time ./tests) since a lot of secp256k1_fe_verify calls were added in this PR. These are the results (on 64-bit, i7-8750H) for three iterations:
| branch | iter1 | iter2 | iter3 |
|---|---|---|---|
| master | 2min 26.21sec | 2min 26.44sec | 2min 26.68sec |
| this PR | 2min 26.99sec | 2min 26.91sec | 2min 26.83sec |
@peterdettman Good points. Do you think we should remove fe_equal_var entirely and maybe document in fe_equal that this is intentional?
@peterdettman Good points. Do you think we should remove
fe_equal_varentirely and maybe document infe_equalthat this is intentional?
Yeah, pretty much. Until there's a performance-sensitive caller for whom a != b is the expected result, I don't see any value in _fe_equal_var. If such a caller does turn up, then there should be a fast path for inequality that doesn't even need the subtraction, so we'd rewrite it anyway.
@siv2r Do you plan to also remove the fe_equal_var in a further commit? Or should we do this in a separate PR?
(If you want to add it here, I think it's really ok to add it on top of the existing commits, no need to rewrite the existing commits).
Okay, I will remove fe_equal_var in a new commit (this PR).
fe_equal_var and documented the reason in fe_equalfe_equal_var calls with fe_equal63 | @@ -64,12 +64,12 @@ static int secp256k1_fe_is_zero(const secp256k1_fe *a); 64 | /** Check the "oddness" of a field element. Requires the input to be normalized. */ 65 | static int secp256k1_fe_is_odd(const secp256k1_fe *a); 66 | 67 | -/** Compare two field elements. Requires magnitude-1 inputs. */ 68 | +/** Compare two field elements. Requires the first input to have magnitude equal to 1. 69 | + * The variable time counterpart of this function `secp256k1_fe_equal_var` was removed 70 | + * since it hits fast path only when the inputs are unequal. This unequal inputs
I think this is something you can document in the commit comment; it's weird to have it in the codebase. As in: there are many other things that aren't implemented because they're not worth it; they aren't exhaustively listed in the source code.
240 | @@ -241,7 +241,7 @@ static int secp256k1_gej_eq_x_var(const secp256k1_fe *x, const secp256k1_gej *a) 241 | secp256k1_fe r, r2; 242 | VERIFY_CHECK(!a->infinity); 243 | secp256k1_fe_sqr(&r, &a->z); secp256k1_fe_mul(&r, &r, x); 244 | - r2 = a->x; secp256k1_fe_normalize_weak(&r2); 245 | + r2 = a->x;
The r2 variable isn't needed anymore; you can just call return secp256k1_fe_equal_var(&r, &a->x); directly.
Nice catch, Thanks!
I missed the "field: add magnitude check and _fe_verify check for internal field APIs" commit being added here before writing #1066 (which duplicates some of its efforts, but also goes a lot further in a number of ways). Feel free to keep it in (and I'll rebase), but if you think #1066 covers everything you're doing here, perhaps it's easier to leave that for that PR.
No worries, I will rebase this PR.
No worries, I will rebase this PR.
Ok, merging #1066 took a while.... This PR here needs rebase now. :)
2966 | @@ -2967,7 +2967,6 @@ static int check_fe_equal(const secp256k1_fe *a, const secp256k1_fe *b) { 2967 | secp256k1_fe an = *a; 2968 | secp256k1_fe bn = *b; 2969 | secp256k1_fe_normalize_weak(&an); 2970 | - secp256k1_fe_normalize_var(&bn);
Not sure why normalize_var was used on bn. I assumed it intended to set bn's magnitude to 1 for the fe_equal call. So, I removed it. Please let me know if that's not the case, and I'll revert this.
All test cases passed with this change.
Nice. :)
#1066 took care of all the magnitude and
fe_verifychecks so, this PR currently does:
Can you update the PR title and the initial comment, so that they reflect the current status?
Needs rebase again, sorry, but should be trivial. :)
Thanks for updating the PR title. Can you also update the initial post/PR description (e.g., replace the contents with #1062 (comment) )? It's just cosmetic, but the text of will be added to the merge commit, so it makes sense to keep this updated.
It is not neccessary for the second argument in `secp256k1_fe_equal_var`
(or `secp256k1_fe_equal`) to have magnitude = 1.
Hence, removed the `secp256k1_fe_normalize_weak` call for those argument.
`fe_equal_var` hits a fast path only when the inputs are unequal, which is
uncommon among its callers (public key parsing, ECDSA verify).
Rebased and updated the description. I forgot about the merge commit including the description too. Thanks!
utACK 54058d16feaa431520029335e2d56252859d3260
Until there's a performance-sensitive caller for whom a != b is the expected result, I don't see any value in _fe_equal_var.
I don't think we know what the expected result of the caller is. The difference seems to be negligible though.
ACK 54058d16feaa431520029335e2d56252859d3260