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

Modified fuzz tests about field operations #1407

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

YafeiXie1
Copy link

Thanks for the previous review.
This PR:
Added a function to construct valid field elements with random magnitude.
Added the constraint in each field operation test based on the magnitude and normalization.
Could you give me some suggestions?

@sipa
Copy link
Contributor

sipa commented Aug 21, 2023

@YafeiXie1 Just FYI, you don't need to open a new pull request when you make changes. You can push again to the same branch with more commits (or if you modified existing commits, you'd need a force push, but it's still possible).

src/fuzz.c Show resolved Hide resolved
src/fuzz.c Outdated
}
}

#ifdef VERIFY
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

src/fuzz.c Outdated
if (size >= 32) {
secp256k1_fe a;
fuzz_field_construct(data, size, &a);
if (a.normalized) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test could be a lot more efficient if fuzz_field_construct had an input argument that can guarantee it only produces normalized field elements (because as-is, in many cases no test will be performed at all). Alternatively, there could be a separate function for just that.

src/fuzz.c Outdated
int rand_magnitude = data[31] % 33;
secp256k1_fe_normalize(r);
if (rand_magnitude == 0) {
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, the returned field element will not have magnitude 0. In fact, it'll be unspecified, making the code that uses it below have undefined behavior. For magnitude 0, you should set the output to just be the 0 field element.

src/fuzz.c Outdated
if (size>=32) {
secp256k1_fe a, zero;
secp256k1_fe_set_b32_mod(r, data);
int rand_magnitude = data[31] % 33;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a good idea to reuse fuzz input bytes for two distinct meanings (here: magnitude and the lowest byte of the field element representation). It may also be better to let the caller decide the magnitude (or at least max magnitude), and then let the function construct it.

src/fuzz.c Outdated
}
secp256k1_fe_clear(&zero);
secp256k1_fe_negate(&zero, &zero, 0);
secp256k1_fe_mul_int_unchecked(&zero, rand_magnitude - 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach here does not reach all possible field element representations; only ones that can be represented as "normalized_number times integer in range 1 to 31".

I think it would be better if you'd read a full 40 bytes, and then use masking (using bitwise AND to set certain bits to zero) and/or rejection (just give up if the resulting value has bad magnitude) to obtain field elements of the desired magnitude.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your suggestions! I will modify this fuzz_field_construct function and other errors.

src/fuzz.c Outdated
if (size >= 32) {
secp256k1_fe a, negate;
fuzz_field_construct(data, size, &a);
if (a.magnitude >= 32) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is unnecessary; fuzz_field_construct only constructs values with magnitude at most 32.

src/fuzz.c Outdated
if (size >= 64) {
secp256k1_fe a, b, r1, r2;
fuzz_field_construct(data, size, &a);
fuzz_field_construct(data + 32, size, &b);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The size pass here is incorrect, because the array pointed to by data + 32 only has length size - 32. See also my comment about responsibility of guaranteeing length on fuzz_field_construct.

@YafeiXie1
Copy link
Author

Thanks for the previous review.
This PR:
Modified the field construct function to build more valid field elements.
Modified and added the fuzzing tests for the group operations.
Could you give me some suggestions?

@@ -0,0 +1,5 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't add this file, please. It's for your local setup, not everyone's.

if (r->normalized) {
if ((r->n[4] == 0x0FFFFFFFFFFFFULL) && ((r->n[3] & r->n[2] & r->n[1]) == 0xFFFFFFFFFFFFFULL)) {
uint64_t mask3 = 0xFFFFEFFFFFC2FULL - 1;
r->n[0] &= mask3;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is inaccurate, as it will unconditionally wipe some bits in r->n[0] even when unnecessary, making certain values unreachable. E.g. it'll turn r->n[0] = 0x1000003d1 into r->n[0] = 0.

src/fuzz.c Outdated
* If 'normalized' = 0, this function will construct normalized or non-normalized field elements depending on the fuzzer data.
* On output, r will be a valid field element
**/
static void fuzz_field_construct(const uint8_t *data, int normalized, secp256k1_fe *r) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the effectiveness of some of the tests below could be increased further by passing a max magnitude argument to fuzz_field_construct, so that things like

        fuzz_field_construct(data, 0, &a);
        fuzz_field_construct(data + 42, 0, &b);         
        if (a.magnitude + b.magnitude <= 32) {

could become

        fuzz_field_construct(data, 0, 32, &a);
        fuzz_field_construct(data + 42, 0, 32 - a.magnitude, &b);

It'd also remove the need for fuzz_ge_fe_construct.

src/fuzz.c Outdated
}

/* Test commutativity of addition on two field elements */
static void fuzz_field_add_commutativty(const uint8_t *data, size_t size) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: typo

src/fuzz.c Outdated
if (a.magnitude <= 10) {
int m = a.magnitude;
r1 = a;
secp256k1_fe_mul_int(&r1, 3);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit limiting to only test multiplication by 3.

src/fuzz.c Outdated
fuzz_ge_fe_construct(data, 1, &x);
if (secp256k1_ge_x_on_curve_var(&x)) {
int odd = data[42] % 2;
secp256k1_ge_set_xo_var(&ge, &x, odd);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if the y coordinate could also be given a nontrivial normalization/magnitude here.

As a stretch, it could be interesting to (optionally) also construct the group element by reading the Y coordinate from the fuzz input, and then construct the corresponding X coordinate (it needs a cube root, but that's relatively simple for our field). I can give you some pointers if you're interested in that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comments! Yes, I’m interested in constructing the group element by reading the Y coordinates from fuzzer data (find X from the elliptic curve equation).

In addition, I changed the fuzz_field_construct function (add another input argument to determine the max magnitude) and fixed the mask errors. Currently, I’m working on the fuzzing test about the function related to point multiplication operation, is this a good part to carry on, or do you have a better recommendation?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have a look over your changes soon. In the mean time, yes, having some fuzzing of EC multiplication would be good.

To compute cube roots modulo 2^256-2^32-977 (or field size), see this explanation here: https://github.com/bitcoin/bips/blob/cc177ab7bc5abcdcdf9c956ee88afd1052053328/bip-0324/reference.py#L165L191

src/fuzz.c Outdated Show resolved Hide resolved
@YafeiXie1
Copy link
Author

This PR:
Field element construct function:
Added another input argument to determine the max magnitude.
Changed the mask for special case when the field element is normalized.

Group element construct function:
Remove fuzz_ge_fe_construct function.
Give Y coordinate the normalization/magnitude depending on the fuzz input.

I found an interesting situation: when I run the fuzzing test ‘fuzz_field_equal’ for the ‘secp256k1_fe_equal(a, b)’ (the magnitude of a and b cannot exceed 1 and 31 respectively), the test failed when the magnitude of ‘b’ equals to 31.
In the implementation of ‘secp256k1_fe_equal’ (in ‘field_impl.h’ file), after the negating function, the magnitude of ‘a’ increases to 2. Maybe the max magnitude for ‘b’ should be 30 in this function?

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

Successfully merging this pull request may close these issues.

4 participants