Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make fe magnitude implied statically #1001

Open
real-or-random opened this issue Oct 28, 2021 · 20 comments
Open

Make fe magnitude implied statically #1001

real-or-random opened this issue Oct 28, 2021 · 20 comments

Comments

@real-or-random
Copy link
Contributor

See the discussion starting here: #943 (comment)

@roconnor-blockstream
Copy link
Contributor

To be clear, all the instances of magnitude are currently statically implied because all those "variables" that are dynamic are currently only ever passed integer literal values.

@peterdettman
Copy link
Contributor

@roconnor-blockstream Surely not, e.g. what about _fe_cmov?

@roconnor-blockstream
Copy link
Contributor

I wasn't aware of that one. (I'm less familiar with the non-verification related code.)

Make sense to patch that case by always setting the magnitude to the max of the two values?

@peterdettman
Copy link
Contributor

Yes, which was already done, then patched out, so this time with more comments I think!

@real-or-random
Copy link
Contributor Author

@peterdettman Can you elaborate a little bit more on the reasoning behind this suggestion?

I fully agree it's conceptually nicer. But as long as we don't have automated checks that prove that the magnitude is always low enough, does it really matter?

@roconnor-blockstream
Copy link
Contributor

roconnor-blockstream commented Nov 3, 2021

The whole point of the magnitude field is to have a check that the tested code paths would not overflow even if different values were used. It is a poor man's abstract interpretation.

But when we, say, set the magnitude of the output of cmov, based on the flag value, and the value of that flag value is dependent on the specific initial value we are testing, then our "abstract interpretation" ends up being limited to only those value that would produce the same flag value at that call site.

I respect that our poor man's abstract interpretation is imperfect. There are places were we explicitly branch on data (especially in the _var functions). But we can at least strive to make our abstraction as abstract as reasonably possible.

(For illustration: the opposite course of action would be to make the magnitude based more and more on concrete values, but there is no point in doing that. We already have a prefect measure of the a concrete magnitude: using the actual value itself!).

@peterdettman
Copy link
Contributor

I thought of the magnitude verification AS the automated checks. It's inherently worst-case, basically a static bounds check. That plus code coverage of all branches (and careful analysis of the very few loops) means there's no overflows in the field arithmetic. Without that property you need to be sure you have test cases that exercise every code path at the maximum possible magnitude, which is just a harder-to-prove/understand version of the same thing.

@peterdettman
Copy link
Contributor

We already have a prefect measure of the a concrete magnitude: using the actual value itself!

Bingo.

@real-or-random
Copy link
Contributor Author

real-or-random commented Nov 3, 2021

I thought of the magnitude verification AS the automated checks. It's inherently worst-case, basically a static bounds check.

Oh sure, I missed this part!

(My thinking was: "it's only dynamic analysis anyway". But if the values are statically determined, then that's indeed all we need...)

@peterdettman
Copy link
Contributor

It sure would be nice to have a way of restricting a function argument to be a literal. Both to allow the obvious implementation for _mul_int, but also to restrict _fe_negate to specifying a literal for its 'm' (magnitude) argument.

@roconnor-blockstream
Copy link
Contributor

roconnor-blockstream commented Nov 3, 2021

I'm moderately sure there is some horrible way to enforce this with macros. The specific solution I came across uses compound literals, but I suspect there are C89 ways too.

But I'm not sure that we really want to go down the horrible-macro route.

Edit: Maybe just casting through a one dimensional array somehow I'm not sure.

#define foo(i) foo_do_not_call_directly((int[1+0*i])(int)(i)[0])

@real-or-random
Copy link
Contributor Author

@roconnor-blockstream What about just appending u in the macro? That's valid only for literals, and magnitude is anyway not negative.

@roconnor-blockstream
Copy link
Contributor

appending u also works for the expression i + 1.

@real-or-random
Copy link
Contributor Author

real-or-random commented Nov 3, 2021

Oh indeed.

What about this?

#define foo(i) foo_do_not_call_directly(sizeof(char[i]))?

edit: that doesn't give an error in GCC. It just gives a warning, and you need -pedantic to trigger the warning (variable-length arrays are a GCC extension)

Here's a better one:

#define foo(i) do { switch (i) {  case i: foo_do_not_call(i) }} while (0)

(suggested by @roconnor-blockstream again)

Another way of doing it is to define a force_const macro like:

#define force_const(i) (sizeof(struct{int a:i;}), i)

That only works up to the bitwidth of int, which is at least 16, so that's enough for this particular purpose...

https://port70.net/~nsz/c/c89/c89-draft.html#45 is pretty useful here.

There's also in https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005fconstant_005fp in gcc.

@real-or-random
Copy link
Contributor Author

An entirely different way to do this is the approach in #833.

@peterdettman
Copy link
Contributor

An entirely different way to do this is the approach in #833.

Does that allow something generic like requiring that for any function parameter named literal_* (just for example), at all call sites the argument must be an actual literal (after pre-processor stage, say)?

(Maybe the restriction is really to compile-time constant or something else more complicated, but we could just look at literals for the moment.)

@real-or-random
Copy link
Contributor Author

An entirely different way to do this is the approach in #833.

Does that allow something generic like requiring that for any function parameter named literal_* (just for example), at all call sites the argument must be an actual literal (after pre-processor stage, say)?

I haven't tried but I'm pretty sure that this is possible. Coupling it to the parameter name is a pretty nice idea!

@sipa
Copy link
Contributor

sipa commented Feb 1, 2022

So I think #1066 makes it very clear in what ways norm/mag are not compile-time known, as it puts all propagation logic for these fields together. I find:

  • fe_cmov (easily fixed: take the max)
  • set_b32 (which only produces a normalized result if the input is < p). If we want to fix this, I think we should split it into 2 functions:
    • One that always sets normalized=0.
    • One that always sets normalized=1, but in case the input is >= p, it returns an error value and resets the field value to 0 or so.
  • fe_mul_int's integer argument (fixed by forcing it to be a constant)
  • fe_negate's magnitude (fixed by forcing it to be a constant)
  • All the ways in which the code flow depends on values, in vartime algorithms, of course.

sipa added a commit that referenced this issue May 11, 2023
7fc642f Simplify secp256k1_fe_{impl_,}verify (Pieter Wuille)
4e176ad Abstract out verify logic for fe_is_square_var (Pieter Wuille)
4371f98 Abstract out verify logic for fe_add_int (Pieter Wuille)
89e324c Abstract out verify logic for fe_half (Pieter Wuille)
283cd80 Abstract out verify logic for fe_get_bounds (Pieter Wuille)
d5aa2f0 Abstract out verify logic for fe_inv{,_var} (Pieter Wuille)
3167646 Abstract out verify logic for fe_from_storage (Pieter Wuille)
76d31e5 Abstract out verify logic for fe_to_storage (Pieter Wuille)
1e6894b Abstract out verify logic for fe_cmov (Pieter Wuille)
be82bd8 Improve comments/checks for fe_sqrt (Pieter Wuille)
6ab3508 Abstract out verify logic for fe_sqr (Pieter Wuille)
4c25f6e Abstract out verify logic for fe_mul (Pieter Wuille)
e179e65 Abstract out verify logic for fe_add (Pieter Wuille)
7e7ad7f Abstract out verify logic for fe_mul_int (Pieter Wuille)
65d82a3 Abstract out verify logic for fe_negate (Pieter Wuille)
1446708 Abstract out verify logic for fe_get_b32 (Pieter Wuille)
f7a7666 Abstract out verify logic for fe_set_b32 (Pieter Wuille)
ce4d209 Abstract out verify logic for fe_cmp_var (Pieter Wuille)
7d7d43c Improve comments/check for fe_equal{,_var} (Pieter Wuille)
c5e788d Abstract out verify logic for fe_is_odd (Pieter Wuille)
d3f3fe8 Abstract out verify logic for fe_is_zero (Pieter Wuille)
c701d9a Abstract out verify logic for fe_clear (Pieter Wuille)
19a2bfe Abstract out verify logic for fe_set_int (Pieter Wuille)
864f9db Abstract out verify logic for fe_normalizes_to_zero{,_var} (Pieter Wuille)
6c31371 Abstract out verify logic for fe_normalize_var (Pieter Wuille)
e28b51f Abstract out verify logic for fe_normalize_weak (Pieter Wuille)
b6b6f9c Abstract out verify logic for fe_normalize (Pieter Wuille)
7fa5195 Bugfix: correct SECP256K1_FE_CONST mag/norm fields (Pieter Wuille)
b29566c Merge magnitude/normalized fields, move/improve comments (Pieter Wuille)

Pull request description:

  Right now, all the logic for propagating/computing the magnitude/normalized fields in `secp256k1_fe` (when `VERIFY` is defined) and the code for checking it, is duplicated across the two field implementations. I believe that is undesirable, as these properties should purely be a function of the performed fe_ functions, and not of the choice of field implementation. This becomes even uglier with #967, which would copy all that, and even needs an additional dimension that would then need to be added to the two other fields. It's also related to #1001, which I think will become easier if it doesn't need to be done/reasoned about separately for every field.

  This PR moves all logic around these fields (collectively called field verification) to implementations in field_impl.h, which dispatch to renamed functions in field_*_impl.h for the actual implementation.

  Fixes #1060.

ACKs for top commit:
  jonasnick:
    ACK 7fc642f
  real-or-random:
    ACK 7fc642f

Tree-SHA512: 0f94e13fedc47e47859261a182c4077308f8910495691f7e4d7877d9298385172c70e98b4a1e270b6bde4d0062b932607106306bdb35a519cdeab9695a5c71e4
@sipa
Copy link
Contributor

sipa commented May 15, 2023

The secp256k1_fe_set_f32 runtime dependency has been addressed through #1207.

See #1317 for the secp256k1_fe_cmov runtime dependency.

real-or-random added a commit that referenced this issue May 19, 2023
31b4bbe Make fe_cmov take max of magnitudes (Pieter Wuille)

Pull request description:

  This addresses part of #1001.

  The magnitude and normalization of the output of `secp256k1_fe_cmov` should not depend on the runtime value of `flag`.

ACKs for top commit:
  real-or-random:
    utACK 31b4bbe
  stratospher:
    ACK 31b4bbe.

Tree-SHA512: 08bef9f63797cb8a1f3ea63c716c09aaa267dfee285b74ef5fbb47d614569d2787ec73d21bce080214872dfe70246f73cea42ad3c24e6baccecabe3312f71433
real-or-random added a commit to real-or-random/secp256k1 that referenced this issue Jun 13, 2023
real-or-random added a commit that referenced this issue Jun 27, 2023
…re constant

be8ff3a field: Static-assert that int args affecting magnitude are constant (Tim Ruffing)

Pull request description:

  See #1001.

  Try to revert the lines in `tests.c` to see the error message in action.

ACKs for top commit:
  sipa:
    ACK be8ff3a. Verified by introducing some non-constant expressions and seeing compilation fail.
  theStack:
    ACK be8ff3a

Tree-SHA512: 8befec6ee64959cdc7f3e29b4b622410794cfaf69e9df8df17600390a93bc787dba5cf86239de6eb2e99c038b9aca5461e4b3c82f0e0c4cf066ad7c689941b19
@real-or-random
Copy link
Contributor Author

One instance we have overlooked is fe_set_int. I missed that in #1345, but I think it should just set the magnitude to 1 always. We have fe_clear for setting to 0. (That one should probably be split in clear for nuking the memory, and fe_set_zero if you really want 0 with magnitude 0.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants