-
Notifications
You must be signed in to change notification settings - Fork 191
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
Conversation
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.
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
/// | ||
/// 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 { |
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.
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
?
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.
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.
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.
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
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>; |
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.
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?
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.
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.
.chain(tmp) | ||
.chain([self.index as u8]) | ||
.chain(self.domain.data()) | ||
.chain([self.domain.len() as u8]) |
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.
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.
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.
You can use typenum
's IsLess
or IsLessOrEqual
traits to express that constraint, e.g.
where
HashT: Digest + BlockInput,
HashT::OutputSize: IsLess<U256>
let ell = (len_in_bytes + b_in_bytes - 1) / b_in_bytes; | ||
if ell > 255 { | ||
panic!("ell was too big in expand_message_xmd"); | ||
} |
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.
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.
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.
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
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.
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.
c59e45e
to
302fb0a
Compare
Signed-off-by: Michael Lodder <redmike7@gmail.com>
I propose we merge this PR but have a follow-up to address before publishing the update to the crate: Limit We could also try to do the blanket impl. This could be done by breaking |
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 |
Perhaps it would make sense to put all of this functionality in a It would also make gating for conditional compilation easier. It seems like modules like |
@tarcieri Moved all traits to |
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. |
Done w/nits @tarcieri |
@mikelodder7 had one more suggestion for avoiding overflow here: #865 (comment) |
ccd8569
to
83395bf
Compare
.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]) |
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.
If we want to solve overflows in this PR, this can overflow too.
Signed-off-by: Michael Lodder <redmike7@gmail.com>
Going to go ahead and merge this and follow up with a small PR for some of the remaining nits |
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.
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.
Traits for hash to curve in groups and fields