-
Notifications
You must be signed in to change notification settings - Fork 183
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
elliptic-curve: refactor PublicKey
into sec1::EncodedPoint
#264
Conversation
Opening this as a draft, both to get early review, and because it's a SemVer breaking change and I think there are still some non-breaking, purely additive changes that can be made to the |
5c12046
to
e7d4f1f
Compare
elliptic-curve/src/sec1.rs
Outdated
pub enum EncodedPoint<C> | ||
where | ||
C: Curve, | ||
C::ElementSize: Add<U1>, | ||
<C::ElementSize as Add>::Output: Add<U1>, | ||
CompressedPointSize<C>: ArrayLength<u8>, | ||
UncompressedPointSize<C>: ArrayLength<u8>, | ||
{ | ||
/// Compressed Weierstrass elliptic curve point | ||
Compressed(CompressedPoint<C>), | ||
|
||
/// Uncompressed Weierstrass elliptic curve point | ||
Uncompressed(UncompressedPoint<C>), | ||
} |
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.
Something we could do here which would be a significant simplification (especially in regard to the trait bounds) is get rid of CompressedPoint
and UncompressedPoint
(and CompressedPointSize
and UncompressedPointSize
) entirely and have EncodedPoint
be a struct with a GenericArray
buffer which is large enough to contain either a compressed-or-uncompressed point (which can still be disambiguated by the leading tag byte).
I might take a stab at that as part of this PR.
Previously the `PublicKey` was a "wire type" for SEC1 encoded curve points which by design didn't validate the point is on the curve (so as to avoid mandating a curve arithmetic implementation for the type to be useful). However, this has a pretty big drawback, in that when we do have a curve arithmetic implementation, there are many fallible operations which could be infallible if we knew the point is valid a priori. Additionally, thanks to `typenum`-based arithmetic, it has pretty atrocious trait bounds, which are needed anywhere we presently deal with a public key. This commit moves this encoded point type to `sec1::EncodedPoint`, which both much more precisely describes what it represents, but also moves it out-of-the-way so we can add a better `PublicKey` type (e.g. one which is or wraps an `AffinePoint` in crates which have a curve arithmetic implementation).
e7d4f1f
to
7a3a0ce
Compare
I pushed up an additional commit (7a3a0ce) which I think simplifies things greatly by eliminating the |
7a3a0ce
to
e93a293
Compare
Can't say I'm a big fan of a mutable Also, what would be the opposite of |
I take it that's in regard to
I'd also like to conditionally impl |
Yes, and in general for |
That's exactly how the current However, supporting type-level arithmetic for computing the sizes of both necessitates some pretty nasty trait bounds, and also spreads out and duplicates a lot of logic that can be consolidated under this approach (notice that this PR reduces LOC by ~40%). Here's an example of how this approach simplifies the bounds. |
e93a293
to
d2b3f43
Compare
Changes `EncodedPoint<C>` to be backed by a byte slice which is always the size of a SEC-1 encoded uncompressed point, eliminating the previous `CompressedPoint` and `UncompressedPoint` types. This allows for simplified trait bounds as we no longer need to care about `CompressedPointSize` in the bounds.
d2b3f43
to
337e233
Compare
I am not familiar enough with |
As someone who's a fan of newtype wrappers, I'm not sure how they'd help here. Concretely I'd break the problem down here as either validating or constructing an encoded document:
The previous implementation used separate types for when the document is compressed or uncompressed, and an enum to provide a common type for representing both forms. But really in pretty much all cases the enum is all anything ever interacted with. However, despite the enum being pretty much the only thing that mattered, the complexity of the compressed-vs-uncompressed type representations was leaking out everywhere in the trait bounds. |
PublicKey
into sec1::EncodedPoint
PublicKey
into sec1::EncodedPoint
83e9f88
to
027cc9c
Compare
027cc9c
to
430beee
Compare
@fjarri I made I also added a bevy of tests which provide ~95% code coverage on I ended up liking this enough that I marked this as "ready for review" as I think it has significantly higher code quality while also simplifying the trait bounds significantly. Earlier I said I thought we might want to hold off on breaking changes, but I changed my mind: this cleans up the trait bounds enough I think I'd like to move forward with it, along with defining a replacement public key type (e.g. associated type e.g. |
Ah, that's right. So the main reason for the approach in this PR is the simplification of type bounds? I must say, I still prefer the solution with two separate types, especially since But anyway, I do not feel strongly enough about this to insist. It's your call.
I suppose the only issue with this approach might be in some kind of an embedded environment where you really have to fight for every byte - but I don't have much experience in that area. |
I'd say it provides three simplifications:
I think we can add something like a pub trait Decompress<C: Curve> {
fn decompress(x: &ElementBytes<C>, is_y_odd: bool) -> CtOption<Self>
} ...and implement all of the point decompression logic there. Then the As it were, for lack of a nicely encapsulated trait like that, I wound up copying and pasting the point decompression code in the ECDSA public key recovery implementation here:
They are separate types presently: https://docs.rs/elliptic-curve/0.5.0/elliptic_curve/weierstrass/point/struct.CompressedPoint.html Having these two types is the reason for the complex trait bounds, and that wouldn't go away by eliminating the enum, since the source of the complexity isn't the enum itself, but the above types that the variants wrap. The reason for having an enum over them is to avoid having to use a "write everything twice" approach for handling point compression and making it a user-facing concern everywhere and instead make it as automatic as possible. The current approach allows a reasonable default setting for point compression to be selected on a per-curve basis, e.g.:
...but in either case, it's still possible to opt-in or opt-out of point compression. For example, age supports compressed P-256 points.
It uses marginally more stack space, at least until something like placement by return lands. |
@fjarri I just noticed I didn't address your request for a |
Uses the new `sec1::EncodedPoint` type introduced in RustCrypto/traits#264
Uses the new `sec1::EncodedPoint` type introduced in RustCrypto/traits#264
Replaces the previous `PublicKey` type with `sec1::EncodedPoint`, as introduced in RustCrypto/traits#264. This clears the way for a higher-level PublicKey type.
Replaces the previous `PublicKey` type with `sec1::EncodedPoint`, as introduced in RustCrypto/traits#264. This clears the way for a higher-level PublicKey type.
Replaces the previous `PublicKey` type with `sec1::EncodedPoint`, as introduced in RustCrypto/traits#264. This clears the way for a higher-level PublicKey type.
Previously
PublicKey
was a "wire type" for SEC1 encoded curve points which by design didn't validate the point is on the curve (so as to avoid mandating a curve arithmetic implementation for the type to be useful).However, this has a pretty big drawback: when we do have a curve arithmetic implementation, there are many fallible operations which could be infallible if we knew the point is valid a priori.
Additionally, "thanks" to
typenum
-based arithmetic, it has pretty atrocious trait bounds, which are needed anywhere we presently deal with a public key.This commit moves this encoded point type to
sec1::EncodedPoint
, which both much more precisely describes what it represents, but also moves it out-of-the-way so we can add a betterPublicKey
type (e.g. one which is or wraps anAffinePoint
in crates which have a curve arithmetic implementation).