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

Impl TryFrom<&[u8]> for all compressed point types. #296

Conversation

isislovecruft
Copy link
Member

This reduces copy-pasta in downstream users to check the length of the slice beforehand.

@hdevalence
Copy link
Contributor

This seems good, should there also be an implementation for montgomery points?

src/edwards.rs Outdated Show resolved Hide resolved
@hdevalence
Copy link
Contributor

Thinking about this a bit more on the weekend, I have concerns about the use of () as the error type.

I think that this will interact poorly with the type conversions used in the ? operator, which require a tree of error types with From conversions from more-specific to less-specific. But the error type used here is (), which is one of the least-specific types already -- how can another crate use this impl in a method of type -> Result<_, SomeError> without using map_err? They would need to have impl From<()> for SomeError, which a) may not be possible (if SomeError is not defined locally) and b) is wrong, because it doesn't make sense in general to convert () to SomeError.

@isislovecruft
Copy link
Member Author

I can also implement this for Montgomery points. Also, I can make a curve25519 specific error type—I just avoided doing that because I didn't know how much error-handling boiler plate we'd want in this crate.

@isislovecruft
Copy link
Member Author

@hdevalence Alright, you've now got custom error types, with optional dependency and implemenation on the failure crate. The impl TryFrom<&[u8]> for MontgomeryPoint additionally checks that the u-coordinate was a canonicalised field element.

@hdevalence
Copy link
Contributor

I don't think we should depend on failure, because it has its own custom error trait that doesn't play well with the standard library's Error trait, meaning that it's difficult to use in non-failure contexts, and now that Error has been fixed to include the improvements from failure, failure will be less-used anyways. However, the stdlib's Error trait isn't in core, so using it would mean either doing a no_std/std compatibility hack, like the rand crate did, or having a deserialization API that's different in no_std contexts, which seems less than ideal.

Previously, we avoided all of this by using only Option-based APIs, which are easily compatible with any error type in any curve25519-dalek-using crate, as the caller can use ok_or with that crate's preferred error type. I think continuing with that design decision seems like the best option here.

@tarcieri
Copy link
Contributor

The approach we've used in various RustCrypto crates is an opaque struct Error; type, which receives a std::error::Error impl whenever the std feature is enabled. It seems to be working out fairly well for us.

@isislovecruft
Copy link
Member Author

@hdevalence Would you be okay with the std-only impl of std::error::Error for CurveError that @tarcieri suggested?

@isislovecruft
Copy link
Member Author

Hmm.. I'm afraid I don't understand why I'm getting this error, for like the first time ever in Rust: https://travis-ci.org/dalek-cryptography/curve25519-dalek/jobs/602034204#L355

error[E0433]: failed to resolve: use of undeclared type or module `dyn`

  --> src/errors.rs:60:34

   |

60 |     fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {

   |                                  ^^^ use of undeclared type or module `dyn`

warning: trait objects without an explicit `dyn` are deprecated

  --> src/errors.rs:60:34

   |

60 |     fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {

   |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `dyn`: `dyn dyn ::std::error::Error + 'static`

   |

   = note: `#[warn(bare_trait_objects)]` on by default

error: aborting due to previous error

@WildCryptoFox
Copy link

WildCryptoFox commented Oct 23, 2019

@isislovecruft Try &(dyn std::error::Error + 'static). This is a limitation of Rust's 2015 edition. It works as expected in 2018 edition. Recall the compiler rarely recognizes whitespace. I thought the dyn keyword was introduced in 2018 edition? shrugs

@hdevalence
Copy link
Contributor

Rather than having a stub error type that may or not be std::error::Error, and which is only ever used in one method, I think it would be better to provide an Option-based API, which can be consistent with all of the other functions the library exports. Library consumers can use .ok_or to transform the Option into a Result however they like, we maintain API consistency, and avoid having to deal with error types entirely.

@vlopes11
Copy link

vlopes11 commented Oct 24, 2019

Need to use Scalar as one of a generic type set that serializes / deserializes. For that, I need a implementation TryFrom[&[u8]]; was very happy to see this PR 😃

Created the following PR to extend this one for other types:
isislovecruft#1

@isislovecruft
Copy link
Member Author

@hdevalence I'm not sure I precisely understand what you're suggesting.. do you mean, rather than implementing TryFrom, to instead create a custom try_from_bytes() method?

@hdevalence
Copy link
Contributor

No, I'm suggesting that we should provide an Option-based API like the rest of the crate, e.g., making from_slice return an Option instead of panicking. Then callers can use .ok_or with their crate's error type to get something that can be used with ? with no compatibility issues.

This reduces copy-pasta in downstream users to check the length of the
slice beforehand.
We due this in lieu of implementing `TryFrom` to allow for API
consumers to use the `?` operator to convert potential `None`s into
their own `Result<T, CustomError>` types for better error handling
with less boilerplate.

Note that this is a breaking API change.
@isislovecruft
Copy link
Member Author

Ah, I see. Sure.

@isislovecruft
Copy link
Member Author

Done. Do we also want a similar API for Scalar?

@hdevalence
Copy link
Contributor

I would prefer not to have a similar API for Scalar, because I don't think that there's an appropriate default deserialization check in the same way as there is for the compressed point types. Unfortunately, being compatible with the various X/Ed25519 scalar usecases etc. means that API users need to be aware of their requirements, and I don't think we can fix the broken ecosystem with our API alone, so I would prefer to keep the current design where there are different constructors for different requirements and users can't avoid making an explicit choice.

@isislovecruft
Copy link
Member Author

Sounds good. This branch is ready for review then. Also I can squash it down and remove the history if you like.


u.ct_eq(&self.0).into()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not the right definition of validity; all 32-byte strings are valid representations of elements of the curve+twist pair, and this check should not exist in the source code.

Copy link
Member Author

Choose a reason for hiding this comment

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

§5.2 of "Montgomery Curves and Their Arithmetic" reads:

both E_{(A,B)}(𝔽_q) and E_{(A,B')}(𝔽_q) have cryptographically strong (i.e. almost prime) group orders. This means that every element of 𝔽_q corresponds to a point on a cryptographically strong Montgomery curve, and implementers need not perform any point validation checks in this protocol.

My reading of that is that we do not need to check that a 32-byte string is a canonical representation of a field element, however we probably should check (which the decoding function explicitly does not do) in order to avoid inputs in [2^255 - 18, 2^255).

Copy link
Contributor

Choose a reason for hiding this comment

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

That is the check that the encoding is canonical, which is why we don't need it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I realise that is the the check that the encoding is canonical. I'm arguing that we should do the check to ensure the 32-bytes are a canonical element in 𝔽_q, otherwise the callee must check this if they care about potential "non-contributory" inputs. (My understanding of TryFrom is essentially "here's some random crap, make it absolutely safe if you can".)

Copy link
Member Author

@isislovecruft isislovecruft Nov 13, 2019

Choose a reason for hiding this comment

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

I mean, consumers of our library are doing similar checks on their own (cf. tendermint/tmkms#279). I'm arguing that we should give them proper tools to Just Do The Thing And Not Have To Worry, since the whole point of this PR is to reduce copy-pasta.

@isislovecruft isislovecruft merged commit 46f56f9 into dalek-cryptography:develop Nov 26, 2019
hdevalence added a commit that referenced this pull request Dec 10, 2019
…-try-from"

This reverts commit 46f56f9, reversing
changes made to a9b1d50.

These changes are not semver-compatible with the 2.0.0 release.
@isislovecruft
Copy link
Member Author

Uh.. I'm confused, @hdevalence, why you'd unilaterally revert my commits without any given reasoning or explanation? And then furthermore gaslight me by claiming you're not doing this?

2019-12-10-harry-slack-dm-1
2019-12-10-harry-slack-dm-2

Do you have a problem with my code?

@hdevalence
Copy link
Contributor

The reasoning is given in the commit message of the revert commit, linked above in this thread:

These changes are not semver-compatible with the 2.0.0 release.

Searching for "2.0.0" on the issue tracker shows #265 (comment), which was crossreferenced from various other issues tracking the 2.0.0 change.

Regarding "gaslighting", the date on your original message, which you did not include in the screenshot, was November 22; I made the revert commit yesterday afternoon, December 10, because I was concerned that other minor changes would be made on top of the incompatible code in develop and that it would end up in a patch release, breaking downstream code.

pinkforest pushed a commit to pinkforest/curve25519-dalek that referenced this pull request Jun 27, 2023
* Add `Scalar` and `MontgomeryPoint` conversions

- Adds `SigningKey::to_scalar` to extract the private scalar
- Adds `VerifyingKey::to_montgomery` to map the verifying key's
  `EdwardsPoint` to a `MontgomeryPoint`
- Also adds corresponding `From<&T>` impls which call the inherent
  methods.

This is useful for systems which are keyed using Ed25519 keys which
would like to use X25519 for D-H. Having inherent methods means it's
possible to call these methods without having to import `Scalar` and
`MontgomeryPoint` from `curve25519-dalek`.

This is of course a bit circuitous: we could just multiply `Scalar` by
`EdwardsPoint` and use the resulting `EdwardsPoint` as the D-H shared
secret, however it seems many protocols have adopted this approach of
mapping to `MontgomeryPoint` and using that for the shared secret, since
X25519 is traditionally used for ECDH with Curve25519.

* Add reference to eprint 2021/509

* Basic X25519 Diffie-Hellman test
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

6 participants