-
Notifications
You must be signed in to change notification settings - Fork 560
ff FieldElement #787
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
base: main
Are you sure you want to change the base?
ff FieldElement #787
Conversation
These changes are largely straightforward, with the note prior, the only arithmetic supported prior was for borrows. Now that we've added support for non-borrowed addition, the existing code is 'unnecessarily borrowing', hence the added lint. The larger question applicable is how this exposes FieldElement from several different backends, assuming they're all correct for arbitrary usage. The implementation of ConstantTimeEq has the following notes. ```rs /// Test equality between two `FieldElement`s. Since the /// internal representation is not canonical, the field elements /// are normalized to wire format before comparison. ``` While that doesn't suggest the mathematical operations are incomplete, it should be double-checked that no backend bounded the amount of operations they're correct to on the presumption the curve formulas would never exceed so many operations.
…y have inconsistent internal representations
Also fixes the feature flagging for the wide reduction function.
This forces a reduction after every operation via converting to bytes (canonical) and re-reading it. There's probably a better way...
The no-std test failures appear entirely unrelated to my commits.
I am happy to see this work with all backends however. |
/// implementations. Its size and internals are not guaranteed to have | ||
/// any specific properties and are not covered by semver. | ||
#[derive(Clone, Copy, PartialEq, Eq)] | ||
pub struct FfFieldElement(FieldElement); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about adding another field element type, especially with a name like FfFieldElement
(Finite field field element?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a thin wrapper around the existing FieldElement into the ff backend. It's exported publicly as FieldElement, but as it's a distinct type, it obviously needed its own name internally. The alternative would be this in its own module (as now), with the underlying FieldElement imported under the alias UnderlyingFieldElement, which an earlier draft of mine did. I'm happy to make that change, though I'll note I don't substantially believe this to be new.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry. I realize now my comment doesn't iterate over the primary issue. FieldElement does not satisfy the ff API and will not without invasive changes and performance regressions, due to its policy of lazy reduction.
A new type, isolating from the rest of dalek while ensuring proper fulfillment of the intended API, truly seemed optimal.
Alternatively, exposing everything under a hazmat feature and then a wrapper crate may define this ff-compliant type with a pin to an exact version of dalek, including patch, would work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are so many field element types in this codebase I worry about introducing another, especially one with the same name as a different FieldElement
type (but only when re-exported), it just seems very confusing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heard. While I can point out it's a higher-level on the stack and feature-gated, it'd be better to discuss solutions.
How about it's own file, plus the nomenclature Ff25519 or anything of similar distance? The alternatives would be:
- Exposing the unsafe FieldElement type, under a hazmat feature, letting crates define this ff wrapper itself. I'm fine with that but consider it more damaging than any complexity of this patch.
- Not exposing the 2**255-19 field. As someone who's maintained my own field impl, and am now planning to maintain my own fork of dalek with this patch if not upstreamed, I'd continue as I have but be disappointed. I truly believe this represents a real-world use and this patch can be done incredibly non-intrusively.
There are similar issues with lazy reductions in crates like Perhaps support for lazy reductions should be added to |
The Monero project recently held a public competition for optimized implementations of two elliptic curves, replacing the ones I presented as reference deferring to crypto-bigint Residue. Multiple implementations attempted lazy arithmetic, and all were incorrect in doing so. Lazy aritmetic, bounding correctness, shoves the footgun to the caller for what can be marginal performance gains. While I won't say it's impossible to leverage the Rust type system to check at compile-time the amount of correct operations remain, I believe the effort entirely outstrips the gains, except within immediate, tight scopes such as point addition. The one potential alternative for which I can see any reasonable merit is for a field element which allows a single addition (so for a 255-bit field, producing an unreduced 256-bit value) before either reducing explicitly or being used in a multiplication (producing a 512-bit value to be wide-reduced as existing). The bound on a single addition prevents having to encode a entire counter system into the type system, and should reasonably real-life parameters. I'm unsure how marginal the pair of saved reductions for combinations of the form My PR here wraps the FieldElement into a type which is no longer lazy, reducing after every single operation, to ensure safe public usage. While that comes at a performance penalty compared to the underlying FieldElement as used within dalek, dalek itself suffers no performance penalty and users no longer have to implement the field themselves (which will statistically be the real performance penalty. I'm present here BECAUSE my own implementation premised on crypto-bigint is notably too slow in comparison). Residue held up amazingly well in Monero's contest, for the record :) Overall, the best submissions fared ~30% better for point addition. |
I think type-safe abstractions around lazy reduction are definitely possible. The footgun exists because implementations reuse the same types for reduced and unreduced field elements, easily leading to caller confusion due to the need to "remember to reduce" or the code is mathematically broken. Beyond that, I actually don't think such type-safe abstractions are conceptually that hard to implement. The "loose" field element representation in However, having attempted it in the past in All that said, it's abundantly clear to me in 20/20 hindsight that lazy reduction without such type safety is hugely problematic like you're saying @kayabaNerve, and makes it very easy to introduce subtle bugs without diligent code review. See also: relevant |
For what it's worth, after calling another developer and as someone who has a 255-bit field, I'm onboard for introducing a LazyScalar type, to a codebase currently entirely using full reductions, which allows computing Definitely worth a discussion elsewhere. I may also posit a wrapper usable... |
Sorry to bump this issue, but what is needed to action this? I can accept if it won't be actioned (even though I believe there is a clear use-case for it), but I'm unclear if this would be accepted, what has to be done to get it merged. My last comment was:
|
I remain curious about whether we could put together traits to be upstreamed to The approach in this PR seems like one worth avoiding if possible due to its numerous drawbacks (need for an intermediate wrapper type, performance loss due to eager reductions).
It's something we can consider if the other options don't work out. That said, the way it's factored/implemented now, it also seems like something that could alternatively remain in a third party crate. |
Oh. I didn't realize that was still being included under this issue. Sorry. In that case, my work would be:
I don't hate that as a long-term goal. I do worry about how long that may take. If I knew that:
I would, because that's what my goal for usage within the Monero project requires. I'm just concerned there is no and cannot be such a guarantee, which leaves me to:
I'll accept option two, but I will ask you for the following compromise: If I make Regardless, yep, I'll move forward with lazy PRs when I next have time. Sorry for misunderstand that was required on my end. Also, again, this can't be done in a third-party crate UNLESS:
|
I was thinking more like: try to create a zero-cost abstraction which avoids the need to have a suboptimal wrapper type to fit an API it diverges from. And if we can't do that, consider other options, i.e. this PR. I thought you already had this implemented in a third party crate? So perhaps I'm confused.
What exactly do you mean by this? We can put more functionality under hazmat if need be, I'm just unclear what "underlying impls" means. You mean the various field backends? This PR is basically additive, so can you spell out specifically what functionality is needed under |
No, such a wrapper wasn't already implemented. Monero held a contest for EC impls. Some impls premised themselves on lazy reduction, as curve25519-dalek does, and all trusted the caller to handle it correctly. All were also unsafe. The solution would be a type-safe wrapper. None were submitted and I did not have one. Since then, I prototyped one premised on Re:
This would allow a crate which depends on |
Okay, apologies, I work on too many elliptic curve crates and forgot that Ideally I think it would be nice if there could be a single common All that said, talking to @rozbb he sounded potentially interested in this approach of defining an entire separate type for external |
I'll poke at updating this for optional lazy reduction, and the other item in this repo I was pinged on, shortly. I'd like to maintain this as a MINIMAL PR to finally achieve a publicly accessible FieldElement however, hence why I pushed another commit. As for whether FromUniformBytes<64> is minimal, it is for my use-case 😅 And it's a proper utilization of the |
I'm doing the work now on this. The traits don't make sense for curve25519-dalek but I'll do the end-to-end here before we discuss where to upstream ( |
Provides a FieldElement backed by the underlying curve25519-dalek implementation.
Unfortunately, the underlying implementation is unsafe to directly expose, which can be near-immediately observed when tested. The solution here is to only rely on the underlying implementation for a minimal amount of operations, encoding and decoding at each step to effect a reduction.
Note there is a
reduce
function provided by each backend, yet their type signatures are non-uniform so they could not be called as an alternative toto_repr/from_repr
. That would be an acceptable alternative.This branch includes a test over the newly introduced wrapper, which defers to a library I wrote to test implementers off the ff/group APIs. While I did not introduce it as a dev-dependency, this ad-hoc offering is suitable to:
to_repr/from_repr
calls after every operation are removedResolves #389.