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

feat: add traits for group digest #865

Merged
merged 2 commits into from
Jan 7, 2022
Merged

Conversation

mikelodder7
Copy link
Contributor

Traits for hash to curve in groups and fields

Copy link
Contributor

@daxpedda daxpedda left a comment

Choose a reason for hiding this comment

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

I think this is a bit too narrowly targeted towards P-256 & co., I believe a CurveToMap trait to fill the gap would alleviate that problem.

elliptic-curve/src/group_digest.rs Outdated Show resolved Hide resolved
elliptic-curve/src/group_digest.rs Outdated Show resolved Hide resolved
elliptic-curve/src/group_digest.rs Outdated Show resolved Hide resolved
elliptic-curve/src/group_digest.rs Outdated Show resolved Hide resolved
elliptic-curve/src/group_digest.rs Outdated Show resolved Hide resolved
elliptic-curve/src/hash2field.rs Show resolved Hide resolved
///
/// let pt = ProjectivePoint::hash_from_bytes::<hash2field::ExpandMsgXof<sha3::Shake256>>(b"test data", b"CURVE_XOF:SHAKE-256_SSWU_RO_");
///
fn hash_from_bytes<X: ExpandMsg, M: AsRef<[u8]>>(msg: M, dst: &'static [u8]) -> Self::Output {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
fn hash_from_bytes<X: ExpandMsg, M: AsRef<[u8]>>(msg: M, dst: &'static [u8]) -> Self::Output {
fn hash_from_bytes<X: ExpandMsg, M: AsRef<[u8]>>(msg: M, dst: &[u8]) -> Self::Output {

Is there a reason this is 'static?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. HashToCurve requires handling a DST longer than 255 chars. See Domain. Having implemented this multiple times across various languages and libraries, 99% (I've never seen a dynamic one yet but there must be one somewhere) of the time the domain is static or known and not composed at run time. To be no-std compliant I can't use Vec. That leaves a couple of options: use [u8; 255] or GenericArray<u8, U255>, use &'a [u8] or &'static [u8]. The first option allocates more than enough space to handle any DST < 255 but overly so since they are usually < 32. This also requires copying the DST into the other array which is not really necessary. That leaves the other two. The first &'a [u8] requires adding the lifetime to all traits up the chain and more complex work to make Rust happy. This is undesirable and complicated. Thus the use of &'static [u8] which is not that strict of a requirement and keeps the API clean.

Copy link
Contributor

Choose a reason for hiding this comment

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

I actually thought that &'a [u8] was a perfectly fine solution here. This can be improved in a follow-up PR, not really a big issue.

elliptic-curve/src/group_digest.rs Outdated Show resolved Hide resolved
Comment on lines 6 to 11
pub trait GroupDigest {
/// The field element representation for a group value with multiple elements
type FieldElement: FromOkm + Default + Copy;
/// The resulting group element
type Output: CofactorGroup<Subgroup = Self::Output>
+ MapToCurve<FieldElement = Self::FieldElement, Output = Self::Output>;
Copy link
Contributor

Choose a reason for hiding this comment

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

After some thinking, I'm not sure this trait actually adds anything useful. All functions are already provided by default and the associated types can be derived from other traits.

For example, this blanket impl would work:

impl<C: PrimeCurveArithmetic + ScalarArithmetic> GroupDigest for C
where
    C::Scalar: FromOkm + Default + Copy,
    C::CurveGroup: CofactorGroup<Subgroup = C::CurveGroup>
        + MapToCurve<FieldElement = C::Scalar, Output = C::CurveGroup>,
{
    type FieldElement = C::Scalar;

    type Output = C::CurveGroup;
}

I would actually suggest making these free standing functions, like diffie_hellman or hash_to_field below. It could also be added to an existing trait behind where bounds.

fn hash_from_bytes<C: PrimeCurveArithmetic + ScalarArithmetic, X: ExpandMsg>(
    msg: &[u8],
    dst: &'static [u8],
) -> C::CurveGroup
where
    C::Scalar: FromOkm,
    C::CurveGroup: CofactorGroup<Subgroup = C::CurveGroup>
        + MapToCurve<FieldElement = C::Scalar, Output = C::CurveGroup>,
{
    let mut u = [C::Scalar::default(), C::Scalar::default()];
    hash_to_field::<X, _>(msg, dst, &mut u);
    let q0 = C::CurveGroup::map_to_curve(u[0]);
    let q1 = C::CurveGroup::map_to_curve(u[1]);
    q0.clear_cofactor() + q1.clear_cofactor()
}

WDYT?

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 like where you're going with this but I prefer the methods be directly accessible on the traits if possible rather than standalone functions.

elliptic-curve/src/group_digest.rs Outdated Show resolved Hide resolved
.chain(tmp)
.chain([self.index as u8])
.chain(self.domain.data())
.chain([self.domain.len() as u8])
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't this overflow? As far as I understand this depends on the OutputSize of the hash, I don't see anything preventing it from being bigger then 255? If this is required, it could be enforced on the type level with a where bound.

Copy link
Member

Choose a reason for hiding this comment

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

You can use typenum's IsLess or IsLessOrEqual traits to express that constraint, e.g.

where
    HashT: Digest + BlockInput,
    HashT::OutputSize: IsLess<U256>

Comment on lines 57 to 58
let ell = (len_in_bytes + b_in_bytes - 1) / b_in_bytes;
if ell > 255 {
panic!("ell was too big in expand_message_xmd");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I played around with how to circumvent this locally and I achieved some pretty ugly code to check this on a type level.

So I believe the goal is to keep these functions infallible, certainly not panicking. I don't know how else to do this without doing some ugly where bounds on the length. To do this we will need to keep the previous implementation you had of storing the LEN_IN_BYTES as a generic (probably using ArrayLength instead of const usize).

That way we can put all these kind of checks on the ExpandMsgXmd type itself and keep functions infallible.
Another check that's missing here:
https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html#section-5.4.1-4

[..] For correctness, H requires b <= s.

This could be easily checked on the type level: HashT::OutputSize: IsLessOrEqual<HashT::BlockSize>.

The constraint pointed above that is currently handled by panicking:
https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html#section-5.4.1-6

  • len_in_bytes, the length of the requested output in bytes,
    not greater than the lesser of (255 * b_in_bytes) or 2^16-1.

Would be a bit harder, but not impossible.

Alternatively of course, we can just return a Result, but panicking is not a long-term solution, or am I missing something? Maybe I'm overcautious or not understanding something properly.

WDYT? I'm willing to help, either by making follow-up PR's or PR against your fork @mikelodder7.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A follow PR would be great if we can somehow enforce it with type bounds. I'm not sure how to do this cleanly though. We could just change it to be a Result and declare it good enough. The odds of this happening are pretty low when using any hash function from RustCrypto. That won't stop someone from shooting themselves if they have a custom function that has a ridiculous OutputSize

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe an easy solution would be just limit the Outputsize to something sane and be done with it. But yes, let's clear that in a follow-up.

elliptic-curve/src/hash2field/expand_msg_xmd.rs Outdated Show resolved Hide resolved
elliptic-curve/src/hash2field/expand_msg_xof.rs Outdated Show resolved Hide resolved
Signed-off-by: Michael Lodder <redmike7@gmail.com>
@mikelodder7
Copy link
Contributor Author

I propose we merge this PR but have a follow-up to address before publishing the update to the crate:

Limit HashT::OutputSize so it can't possibly result in an ell larger than 255. This could possibly be done with Traits somehow. @daxpedda I'm curious about your approach to this.

We could also try to do the blanket impl. This could be done by breaking GroupDigest into two traits where the first requires to implement the required const values and the 2nd is the blanket impl.

@daxpedda
Copy link
Contributor

daxpedda commented Jan 6, 2022

I agree. It would be great to keep RustCrypto/elliptic-curves#495 up-to-date to see how it actually plays out. Preferably also run it over the test-vectors.

I will find some time to make a follow-up PR with some experiments on how to do the constraint for ell.

elliptic-curve/Cargo.toml Outdated Show resolved Hide resolved
@tarcieri
Copy link
Member

tarcieri commented Jan 6, 2022

Perhaps it would make sense to put all of this functionality in a hash2curve module to keep it organized and avoid polluting the toplevel crate namespace.

It would also make gating for conditional compilation easier.

It seems like modules like osswu could be placed in there as well? (or hash2field)

@mikelodder7
Copy link
Contributor Author

@tarcieri Moved all traits to hash2curve module. digest has been restored and now these traits are gated under the hash2curve feature now.

elliptic-curve/Cargo.toml Outdated Show resolved Hide resolved
elliptic-curve/src/lib.rs Outdated Show resolved Hide resolved
@tarcieri
Copy link
Member

tarcieri commented Jan 6, 2022

I propose we merge this PR but have a follow-up to address before publishing the update to the crate:

Sounds good to me!

A few nits but otherwise this seems like a reasonable start.

I can hold off on any releases until you've had time to play around with the RustCrypto/elliptic-curves PR and are happy with the API.

@mikelodder7
Copy link
Contributor Author

Done w/nits @tarcieri

@tarcieri
Copy link
Member

tarcieri commented Jan 7, 2022

@mikelodder7 had one more suggestion for avoiding overflow here: #865 (comment)

.chain([(LEN_IN_BYTES >> 8) as u8, LEN_IN_BYTES as u8, 0u8])
.chain(dst)
.chain([dst.len() as u8])
.chain([(len_in_bytes >> 8) as u8, len_in_bytes as u8, 0u8])
Copy link
Contributor

@daxpedda daxpedda Jan 7, 2022

Choose a reason for hiding this comment

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

If we want to solve overflows in this PR, this can overflow too.

Signed-off-by: Michael Lodder <redmike7@gmail.com>
@tarcieri
Copy link
Member

tarcieri commented Jan 7, 2022

Going to go ahead and merge this and follow up with a small PR for some of the remaining nits

@tarcieri tarcieri merged commit 4b5c1e4 into RustCrypto:master Jan 7, 2022
tarcieri added a commit that referenced this pull request Jan 7, 2022
As noted on #865 there are some potential overflow problems constructing
the messages used for expansion, particularly around input lengths.

This commit changes `ExpandMsg::expand_message`, and all of its
transitive callers, to be fallible in order to handle these cases.
tarcieri added a commit that referenced this pull request Jan 7, 2022
As noted on #865 there are some potential overflow problems constructing
the messages used for expansion, particularly around input lengths.

This commit changes `ExpandMsg::expand_message`, and all of its
transitive callers, to be fallible in order to handle these cases.
@tarcieri tarcieri mentioned this pull request Jan 14, 2022
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.

3 participants