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

Add derivation of precomputed values for fields #769

Merged
merged 1 commit into from
Feb 21, 2024

Conversation

divergentdave
Copy link
Contributor

This adds a Sage script that demonstrates how to compute all the values in src/fp.rs for each supported FFT-friendly field. It starts from the choices of prime modulus and generator element (two coming from draft-irtf-cfrg-vdaf, and one coming from Prio2 code), reproduces all the values we have in that file currently, and documents what each value means and how they are defined. Note that some fields are pre-transformed into Montgomery representation.

@divergentdave divergentdave requested a review from a team as a code owner September 26, 2023 23:57
Copy link
Contributor

@inahga inahga left a comment

Choose a reason for hiding this comment

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

Just some naive questions, nothing actionable.

self.modulus,
)
)
for i in range(min(self.num_roots, 20) + 1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Where does the min(self.num_roots, 20) come from, i.e. why do we need at least 20 roots?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is mainly here to match the Rust-side data structure, which uses [u128; 21]. If a field had a smaller power-of-two-sized multiplicative subgroup, such that we couldn't provide as many roots, then that would impact utility by putting a ceiling on how large of an FFT we could do. 20 should be plenty for most cases, as that lets us call gadgets about a million times (~2^20). We haven't had a need for more roots, and it seems unlikely for now that we will in practice.

Field(
"Field64",
2 ^ 32 * 4294967295 + 1,
pow(7, 4294967295, 2 ^ 32 * 4294967295 + 1),
Copy link
Contributor

Choose a reason for hiding this comment

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

How were the generators originally chosen (I get that they're defined in the spec)? Just some arbitrary value that fit this criteria?

To be FFT-friendly, the order of this subgroup MUST be a power of 2. In addition, the size of the subgroup dictates how large interpolated polynomials can be. It is RECOMMENDED that a generator is chosen with order at least 2^20.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wasn't able to figure out how the generator used with FieldPrio2 was selected, but it has the right order, so it works. For the other two, the definitions use a neat trick wherein they start with an arbitrary group member with an order that's too high, and then knock off the unneeded cofactors through exponentiation until they get an element with the desired order. (If $g$ has order 6, then by the definition of the order of an element, $g^6 = e$, where $e$ is the identity, and $g^2 \neq e$ and $g^3 \neq e$. Then we can set $h = g^3$, and by substituting we know $h^2 = (g^3)^2 = g^6 = e$. By assumption, $h = g^3 \neq e$, thus $h$ has order two.) Choosing an arbitrary group element in a group this big has a large chance of selecting one with an order equal to the group's order, and 7 worked out as a starting place for both cases. If it hadn't, we could try again with something else.

sage: Field64 = GF(2^32 * 4294967295 + 1)
sage: factor(Field64.unit_group().order())
2^32 * 3 * 5 * 17 * 257 * 65537
sage: factor(Field64(7).multiplicative_order())
2^32 * 3 * 5 * 17 * 257 * 65537
sage: factor((Field64(7) ^ 4294967295).multiplicative_order())
2^32
sage: Field128 = GF(2^66 * 4611686018427387897 + 1)
sage: factor(Field128.unit_group().order())
2^66 * 3 * 3491 * 440340496364689
sage: factor(Field128(7).multiplicative_order())
2^66 * 3 * 3491 * 440340496364689
sage: factor((Field128(7) ^ 4611686018427387897).multiplicative_order())
2^66

@armfazh
Copy link
Contributor

armfazh commented Oct 4, 2023

In #758 , I added documentation about the generation of the constants for field128:
See this chunk of comments.
https://github.com/armfazh/libprio-rs/blob/fiat_fp128/src/field/field128.rs#L327-L358

How were the generators originally chosen:

This a = GF(p).primitive_element() gives the smallest element that is a primitive element, and from it, one can get a generator by raising to the cofactor.

@tgeoghegan tgeoghegan force-pushed the david/field-parameter-derivation branch from 1eed2ff to 4240c94 Compare February 21, 2024 00:12
@tgeoghegan tgeoghegan enabled auto-merge (squash) February 21, 2024 00:12
@tgeoghegan tgeoghegan merged commit 22d78ca into main Feb 21, 2024
7 checks passed
@tgeoghegan tgeoghegan deleted the david/field-parameter-derivation branch February 21, 2024 00:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants