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 AffineCoordinates trait + DRY out point serialization #50

Closed
wants to merge 2 commits into from

Conversation

tarcieri
Copy link
Member

@tarcieri tarcieri commented Jun 19, 2020

NOTE: this PR includes a commit from #49 which should get merged first

Adds a trait for obtaining the x and y-coordinates of an affine point.

So as to avoid exposing field elements as part of the public API, the coordinates are first serialized as bytes.

Using this trait, it's possible to generically implement compressed and uncompressed point serialization according to the SEC-1 Elliptic-Curve-Point-to-Octet-String encoding.

This commit also modifies the trait bounds of FixedBaseScalarMul to bound on the AffineCoordinates trait, allowing the result to be an affine point for the curve which can be passed as input to the newly added from_affine_point methods on CompressedCurvePoint and UncompressedCurvePoint.

Adds a trait for fixed-base scalar multiplication which accepts
`&ScalarBytes` as input and returns an associated point type, whose
bounds allow for a `From` conversion to either the
`CompressedCurvePoint` or `UncompressedCurvePoint` for a given curve.

Using this trait, a `weierstrass::PublicKey<C>::from_secret_key` method
is conditionally implemented when the curve `C` impls the
`FixedBaseScalarMul` trait, allowing generic computation of a public key
from a secret key, with optional point compression (selected via a
`compress` argument).
@codecov-commenter
Copy link

codecov-commenter commented Jun 19, 2020

Codecov Report

Merging #50 into master will decrease coverage by 0.55%.
The diff coverage is 72.50%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #50      +/-   ##
==========================================
- Coverage   82.02%   81.46%   -0.56%     
==========================================
  Files          12       12              
  Lines        1296     1354      +58     
==========================================
+ Hits         1063     1103      +40     
- Misses        233      251      +18     
Impacted Files Coverage Δ
elliptic-curve-crate/src/weierstrass/public_key.rs 44.11% <0.00%> (-7.61%) ⬇️
p256/src/arithmetic/scalar.rs 75.00% <0.00%> (-18.75%) ⬇️
p256/src/arithmetic.rs 84.19% <70.58%> (-1.43%) ⬇️
k256/src/arithmetic/scalar.rs 60.60% <75.00%> (+4.60%) ⬆️
k256/src/arithmetic.rs 86.20% <92.00%> (+0.69%) ⬆️
elliptic-curve-crate/src/weierstrass/point.rs 65.00% <100.00%> (+24.25%) ⬆️
k256/src/arithmetic/field.rs 85.63% <100.00%> (+0.08%) ⬆️
p256/src/arithmetic/field.rs 82.25% <100.00%> (+0.14%) ⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d204d2d...7edcd0c. Read the comment docs.

Adds a trait for obtaining the x and y-coordinates of an affine point.

So as to avoid exposing field elements as part of the public API, the
coordinates are first serialized as bytes.

Using this trait, it's possible to generically implement compressed and
uncompressed point serialization according to the SEC-1
Elliptic-Curve-Point-to-Octet-String encoding.

This commit also modifies the trait bounds of `FixedBaseScalarMul` to
bound on the `AffineCoordinates` trait, allowing the result to be an
affine point for the curve which can be passed as input to the newly
added `from_affine_point` methods on `CompressedCurvePoint` and
`UncompressedCurvePoint`.
Comment on lines +6 to +16
/// Trait for obtaining the coordinates of an affine point
pub trait AffineCoordinates {
/// Size of a byte array representing an affine coordinate
type ScalarSize: ArrayLength<u8>;

/// x-coordinate
fn x(&self) -> ScalarBytes<Self::ScalarSize>;

/// y-coordinate
fn y(&self) -> ScalarBytes<Self::ScalarSize>;
}
Copy link
Contributor

@str4d str4d Jun 19, 2020

Choose a reason for hiding this comment

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

This abstraction directly exposes the coordinates instead of the effect of using them. Making it a separate trait is better than having it be part of the core traits, but it still implies that coordinates are an API surface that is supported and encouraged.

I would prefer instead e.g. an EcdsaPrimitive trait that requires implementing the inner part of the ECDSA algorithm that requires coordinates, and then EcdsaCurve: EcdsaPrimitive that provides the full algorithm, which users can import without exposing the inner API unnecessarily. This is similar to the rsa::PrivateKey: rsa::DecryptionPrimitive split.

Separately, using ScalarBytes for the coordinates strongly encourages type errors during implementation of the trait. The coordinates are field elements, and scalars (of the affine point's curve) are field elements (of a different field), but the coordinates are not scalars (in the sense that elliptic curve scalars are generally understood).

UncompressedPointSize<Self::ScalarSize>: ArrayLength<u8>,
{
/// Affine point type for this elliptic curve
type Point: AffineCoordinates + ConditionallySelectable + Default;
Copy link
Contributor

Choose a reason for hiding this comment

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

Fixed-base scalar multiplication doesn't require exposing affine coordinates, so this is a more restrictive abstraction than necessary. I suspect we can address the type bounds issue in another way.

@tarcieri
Copy link
Member Author

Okay, based on @str4d's comments this is too leaky a design, so let me propose an alternative.

Fixed-base scalar multiplication doesn't require exposing affine coordinates, so this is a more restrictive abstraction than necessary.

What if we had something like:

pub struct Coordinate<C: Curve> {
    bytes: ScalarBytes<C::ScalarSize>
}

...but with a twist: while it's possible for library-level code to instantiate, say with this sort of API:

impl<C: Curve> From<ScalarBytes<C::ScalarSize>> {
...
}

...to everything but the elliptic-curve crate itself, the contents would be opaque. This would allow elliptic curve implementations to provide the elliptic-curve crate with serialized field elements, but nothing else would be directly able to consume them.

This could be used by the elliptic-curve crate to expose an EcdsaPrimitive, as well as the current usage in this PR: generic serializers for the SEC-1 Elliptic-Curve-Point-to-Octet-String encoding.

@tarcieri
Copy link
Member Author

Alternatively to that, we could keep the bounds from #49, but still add from_affine_coordinates methods to CompressedCurvePoint and UncompressedCurvePoint which take GenericArray-based x and y coordinates.

Concrete curve implementations could then invoke those methods directly, rather than exposing them as a public trait.

This keeps the coordinates encapsulated while still allowing for generic SEC-1 serialization, which seems like a nice middle ground.

I might open a separate PR based on #49 which implements that idea.

@tarcieri
Copy link
Member Author

Closing out this PR as this does not seem like the right abstraction.

I'll open another PR to DRY out SEC-1 encoding by incorporating it directly into the elliptic-curve crate, but in a way that doesn't leak affine coordinate field elements in the public API.

@tarcieri tarcieri closed this Jun 20, 2020
tarcieri added a commit that referenced this pull request Jul 7, 2020
Protocols like ECDSA need access to affine coordinates: the x-coordinate
for ECDSA itself, but also the y-coordinate for things like recovering
public keys from ECDSA signatures (as seen in Ethereum).

This commit adds support for accessing affine coordinates for use
in these protocols, taking into account some feedback from the previous
attempt to do this (#50):

- Trait is placed inside a `legacy` module with comments strongly
  suggesting it should only be used for implementing legacy protocols
  which explicitly need coordinate access
- Feature is hidden from https://docs.rs to discourage (mis)use
- Coordinates are represented using a `FieldElementBytes` type alias
  which is also defined in the `legacy` module
tarcieri added a commit that referenced this pull request Jul 10, 2020
Protocols like ECDSA need access to affine coordinates: the x-coordinate
for ECDSA itself, but also the y-coordinate for things like recovering
public keys from ECDSA signatures (as seen in Ethereum).

This commit adds support for accessing affine coordinates for use
in these protocols, taking into account some feedback from the previous
attempt to do this (#50):

- Trait is placed inside a `legacy` module with comments strongly
  suggesting it should only be used for implementing legacy protocols
  which explicitly need coordinate access
- Feature is hidden from https://docs.rs to discourage (mis)use
- Coordinates are represented using a `FieldElementBytes` type alias
  which is also defined in the `legacy` module
@tarcieri tarcieri deleted the affine-coordinates-trait branch July 17, 2020 02:17
tarcieri added a commit to RustCrypto/traits that referenced this pull request Feb 1, 2023
See RustCrypto/elliptic-curves#50 for some historic context.

After being able to get by on `AffineXCoordinate` for generic ECDH and
ECDSA, #1199 added an `AffineYIsOdd` trait which was needed to enable
the generic ECDSA implementation in the `ecdsa` crate to compute the
"recovery ID" for signatures (which is effectively point compression for
the `R` curve point).

This commit consolidates `AffineXCoordinate` and `AffineYIsOdd` into
an `AffineCoordinates` trait.

Some observations since prior discussion in
RustCrypto/elliptic-curves#50:

- Access to coordinates is through bytes, namely `FieldBytes`. This is
  so as to avoid exposing a crate's field element type. This approach
  isn't type safe (base field elements and scalar field elements share
  the same serialization) but does make ECDSA's weird reduction of a
  base field element into the scalar field straightforward in generic
  code.
- Prior to this attempts were made to extract ECDSA-specific bits into a
  trait to handle these conversions, but it complicates both writing
  generic code and optimizing performance. While this still might be
  worth exploring, so far those explorations have largely failed.
- Generally there have been a lot of requests for coordinate access
  specifically for things like point serialization formats. We ended up
  adding "compaction" support upstream but we have had requests for
  several other formats (e.g. Elligator Squared) where direct coordinate
  access would be useful.

This trait can hopefully be replaced by a coordinate access API provided
by the `group` crate in the future. See zkcrypto/group#30
tarcieri added a commit to RustCrypto/traits that referenced this pull request Feb 1, 2023
See RustCrypto/elliptic-curves#50 for some historic context.

After being able to get by on `AffineXCoordinate` for generic ECDH and
ECDSA, #1199 added an `AffineYIsOdd` trait which was needed to enable
the generic ECDSA implementation in the `ecdsa` crate to compute the
"recovery ID" for signatures (which is effectively point compression for
the `R` curve point).

This commit consolidates `AffineXCoordinate` and `AffineYIsOdd` into
an `AffineCoordinates` trait.

Some observations since prior discussion in
RustCrypto/elliptic-curves#50:

- Access to coordinates is through bytes, namely `FieldBytes`. This is
  so as to avoid exposing a crate's field element type. This approach
  isn't type safe (base field elements and scalar field elements share
  the same serialization) but does make ECDSA's weird reduction of a
  base field element into the scalar field straightforward in generic
  code.
- Prior to this attempts were made to extract ECDSA-specific bits into a
  trait to handle these conversions, but it complicates both writing
  generic code and optimizing performance. While this still might be
  worth exploring, so far those explorations have largely failed.
- Generally there have been a lot of requests for coordinate access
  specifically for things like point serialization formats. We ended up
  adding "compaction" support upstream but we have had requests for
  several other formats (e.g. Elligator Squared) where direct coordinate
  access would be useful.

This trait can hopefully be replaced by a coordinate access API provided
by the `group` crate in the future. See zkcrypto/group#30
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.

None yet

3 participants