Changes:
- removes unwanted
fe_normalize_weak
calls to the second argument offe_equal
- removes
fe_equal_var
_fe_equal_var
, and unwanted _fe_normalize_weak
calls (in tests)
#1062
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
0/** 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.
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_sqrt
to 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_equal
only requires the magnitude ofa
to 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).
_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.
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 |
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_var
entirely and maybe document infe_equal
that 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).
fe_equal_var
in a new commit (this PR).
fe_equal_var
and documented the reason in fe_equal
fe_equal_var
calls with fe_equal
63@@ -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
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;
r2
variable isn’t needed anymore; you can just call return secp256k1_fe_equal_var(&r, &a->x);
directly.
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_verify
checks 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).
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