Repository: dalek-cryptography/curve25519-dalek (language: rust)

Issue:
Misleading description for hash-to-Edwards point
The function `EdwardsPoint::hash_from_bytes()` is described as "performing hashing to the group" and explicitly references draft-irtf-cfrg-hash-to-curve:

https://github.com/dalek-cryptography/curve25519-dalek/blob/8abb22bcafe30ac2dbad372d0444fdba9e63bc0e/src/edwards.rs#L529-L532

This is a rather misleading description because:

  - This function does _not_ implement the hash-to-curve process of draft-irtf-cfrg-hash-to-curve: the hash from input to a field element is different, and the handling of the sign bit is also different.
  - Only a single Elligator2 map is applied, yielding something that [draft-irtf-cfrg-hash-to-curve](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-3) calls `encode_to_curve`, not `hash_to_curve` (the draft reserves the latter name for the construction that invokes the map twice, and adds together the two output points, to yield a quasi-uniform output distribution).

The first point is mostly about interoperability; while the map is not the same as in the draft, it is still safe (in particular, this code just uses the first 255 bits of a hash output as a field element, while the draft would generate 129 extra bits and perform a modular reduction; but in Curve25519, the field modulus is very close to 2^255, so the bias from using just 255 input bits is negligible). The second point is, arguably, more important for security, because the current `hash_from_bytes()` produces points with a non-uniform distribution that is easy to distinguish from a uniform random generation. Most protocols (and in particular the Signal stuff that this implementation follows) don't mind, but some of them require a uniform distribution, and the lack of uniformity can have consequences ranging from invalid security proofs to actual information leaks.

I recommend, at least, modifying the documentation to make it explicit that the implemented functionality is not compatible with draft-irtf-cfrg-hash-to-curve, and that it has a non-uniform output distribution, which may be troublesome for some (admittedly not many) protocols.

Looking at the existing pull requests, I see PR #377, which not only modifies the description of `hash_from_bytes()`, but also implements (in new, additional functions) the draft-irtf-hash-to-curve process. I have not looked in details at that PR, but the idea seems good.



Discussion / hints:
I don't like that these function is here at all, actually.

* `hash_from_bytes` doesn't appear to adhere to any specs, it's misdocumented, and I can't tell who uses it ([libsignal](https://github.com/signalapp/libsignal/search?q=hash_from_bytes) doens't seem to be). I also can't figure out where these test vectors are from.
* The `elligator_encode` function (called `map_to_curve` in the RFC) uses notation from the paper, not the RFC, which makes it a bit hard to read. Also it only has a single test vector, and it doens't appear to have come from the RFC. Also, it's the Montgomery version of `map_to_curve`, but `hash_from_bytes` uses it to output Edwards curves.

I'd like to remove these functions entirely, unless someone can find a non-broken upstream use. If removal isn't possible, I'd like to feature-gate it behind `hazmat` or something.

**Plan for removal:** What I'd like to do is deprecate this in 4.0 and remove it in 4.1. But this technically breaks semver. So maybe a better route is to feature-gate it behind `deprecate-in-4.1` or something.

@tarcieri thoughts?
We’ve done a decent amount of work in the @RustCrypto `elliptic-curve` crate trying to implement curve-agnostic traits for the hash2curve APIs, although they’re still rough around the edges and I wouldn’t recommend them as anything more than an optional dependency, especially as the `elliptic-curve` crate is pre-1.0. But it may be good to expose inherent methods which try to align with those APIs.

My best guess is `hash_from_bytes` predates the RFC and is a nonstandard construction. If it doesn’t appear to have users it can probably be safely removed.
Also you can feature gate them behind a feature which is explicitly called out as exempt from semver, perhaps complete with a deprecation warning.

The same thing could be done with the `elliptic-curve` APIs too: they could be gated behind a `hash2curve` or `hash2curve-unstable` feature if we wanted to conditionally impl them.
[`elligator_ristretto_flavor`](https://doc.dalek.rs/src/curve25519_dalek/ristretto.rs.html#510 )appears to be a separate beast entirely. 
Ugh you're right, the Ristretto version also [does not](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-03#section-4.3.4) appear to implement the Elligator standard (it only does half the map). I think this should also be deprecated. We could pretty easily re-introduce elligator2 methods in a future major release. The RFC has lots of test vectors
There were good reasons to expose the original singleton elligator map for ed25519, no?

It's not exposed for ristretto now anyways, likely because there are no ristretto protocols you wish to resemble.  I simply meant the ristretto version does not depend upon the ed25519 version.  

In ristretto, the actual hash [`from_uniform_bytes`](https://doc.dalek.rs/src/curve25519_dalek/ristretto.rs.html#614) adds two elligator calls, so just add the [RFC test vectors](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-03#appendix-B.3) into the code.  Right now, the code uses elligator test vectors, while the RFC uses elligator2 test vectors.  It should all be fine. 
I'd like to release before fixing this issue though. We can deprecate these functions but you bring up an important question: do we need a new name for the functions that will replace these? Is that a question for later?
Ignore my ed25519 question above..  

Afaik, there are no problems with the Ristretto code, so afaik nothing there should be deprecated or changed now or in the future, just maybe adding the RFC test vectors eventually.
~~The Ristretto code appears to only be doing half of the one-way map defined in the RFC (ie it should be running `MAP` twice, and summing the results). We could retroactively fix it, but this would cause subtle breakage. So what I'd rather do is deprecate this, put it behind a feature flag, and introduce the correct function later.~~

I'm wrong. It appears to be doing [the correct thing](https://github.com/dalek-cryptography/curve25519-dalek/blob/90292dccf84cdc614c1a5640f2d6cb13bfff9761/src/ristretto.rs#L739). I should add the real test vectors to make sure.
Fixed my point above. A separate point is that these hashing functions do no domain separation, despite the RFC mandating it. Fixing it would be breaking, but leaving it would be Not Good. Hmmm

Fix patch:
diff --git a/src/edwards.rs b/src/edwards.rs
index 3d93b1beb..24c2b0509 100644
--- a/src/edwards.rs
+++ b/src/edwards.rs
@@ -534,10 +534,16 @@ impl EdwardsPoint {
         CompressedEdwardsY(s)
     }
 
-    /// Perform hashing to the group using the Elligator2 map
-    ///
-    /// See https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-10#section-6.7.1
-    pub fn hash_from_bytes<D>(bytes: &[u8]) -> EdwardsPoint
+    /// Maps the digest of the input bytes to the curve. This is NOT a hash-to-curve function, as
+    /// it produces points with a non-uniform distribution. Rather, it performs something that
+    /// resembles (but is not) half of the
+    /// [`hash_to_curve`](https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#section-3-4.2.1)
+    /// function from the Elligator2 spec.
+    #[deprecated(
+        since = "4.0.0",
+        note = "previously named `hash_from_bytes`, this is not a secure hash function"
+    )]
+    pub fn nonspec_map_to_curve<D>(bytes: &[u8]) -> EdwardsPoint
     where
         D: Digest<OutputSize = U64> + Default,
     {
@@ -1719,13 +1725,14 @@ mod test {
     }
 
     #[test]
+    #[allow(deprecated)]
     #[cfg(feature = "alloc")]
     fn elligator_signal_test_vectors() {
         for vector in test_vectors().iter() {
             let input = hex::decode(vector[0]).unwrap();
             let output = hex::decode(vector[1]).unwrap();
 
-            let point = EdwardsPoint::hash_from_bytes::<sha2::Sha512>(&input);
+            let point = EdwardsPoint::nonspec_map_to_curve::<sha2::Sha512>(&input);
             assert_eq!(point.compress().to_bytes(), output[..]);
         }
     }
diff --git a/src/ristretto.rs b/src/ristretto.rs
index 7375bf471..fe59cacaa 100644
--- a/src/ristretto.rs
+++ b/src/ristretto.rs
@@ -615,7 +615,9 @@ impl RistrettoPoint {
         ]
     }
 
-    /// Computes the Ristretto Elligator map.
+    /// Computes the Ristretto Elligator map. This is the
+    /// [`MAP`](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-04#section-4.3.4)
+    /// function defined in the Ristretto spec.
     ///
     /// # Note
     ///
@@ -748,6 +750,8 @@ impl RistrettoPoint {
     /// takes the low 255 bits of each half mod p, applies the
     /// Ristretto-flavored Elligator map to each, and adds the results.
     pub fn from_uniform_bytes(bytes: &[u8; 64]) -> RistrettoPoint {
+        // This follows the one-way map construction from the Ristretto RFC:
+        // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-04#section-4.3.4
         let mut r_1_bytes = [0u8; 32];
         r_1_bytes.copy_from_slice(&bytes[0..32]);
         let r_1 = FieldElement::from_bytes(&r_1_bytes);
@@ -1488,6 +1492,174 @@ mod test {
         }
     }
 
+    // Known answer tests for the one-way mapping function in the Ristretto RFC
+    #[test]
+    fn one_way_map() {
+        // These inputs are from
+        // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-04#appendix-A.3
+        let test_vectors: &[([u8; 64], CompressedRistretto)] = &[
+            (
+                [
+                    0x5d, 0x1b, 0xe0, 0x9e, 0x3d, 0x0c, 0x82, 0xfc, 0x53, 0x81, 0x12, 0x49, 0x0e,
+                    0x35, 0x70, 0x19, 0x79, 0xd9, 0x9e, 0x06, 0xca, 0x3e, 0x2b, 0x5b, 0x54, 0xbf,
+                    0xfe, 0x8b, 0x4d, 0xc7, 0x72, 0xc1, 0x4d, 0x98, 0xb6, 0x96, 0xa1, 0xbb, 0xfb,
+                    0x5c, 0xa3, 0x2c, 0x43, 0x6c, 0xc6, 0x1c, 0x16, 0x56, 0x37, 0x90, 0x30, 0x6c,
+                    0x79, 0xea, 0xca, 0x77, 0x05, 0x66, 0x8b, 0x47, 0xdf, 0xfe, 0x5b, 0xb6,
+                ],
+                CompressedRistretto([
+                    0x30, 0x66, 0xf8, 0x2a, 0x1a, 0x74, 0x7d, 0x45, 0x12, 0x0d, 0x17, 0x40, 0xf1,
+                    0x43, 0x58, 0x53, 0x1a, 0x8f, 0x04, 0xbb, 0xff, 0xe6, 0xa8, 0x19, 0xf8, 0x6d,
+                    0xfe, 0x50, 0xf4, 0x4a, 0x0a, 0x46,
+                ]),
+            ),
+            (
+                [
+                    0xf1, 0x16, 0xb3, 0x4b, 0x8f, 0x17, 0xce, 0xb5, 0x6e, 0x87, 0x32, 0xa6, 0x0d,
+                    0x91, 0x3d, 0xd1, 0x0c, 0xce, 0x47, 0xa6, 0xd5, 0x3b, 0xee, 0x92, 0x04, 0xbe,
+                    0x8b, 0x44, 0xf6, 0x67, 0x8b, 0x27, 0x01, 0x02, 0xa5, 0x69, 0x02, 0xe2, 0x48,
+                    0x8c, 0x46, 0x12, 0x0e, 0x92, 0x76, 0xcf, 0xe5, 0x46, 0x38, 0x28, 0x6b, 0x9e,
+                    0x4b, 0x3c, 0xdb, 0x47, 0x0b, 0x54, 0x2d, 0x46, 0xc2, 0x06, 0x8d, 0x38,
+                ],
+                CompressedRistretto([
+                    0xf2, 0x6e, 0x5b, 0x6f, 0x7d, 0x36, 0x2d, 0x2d, 0x2a, 0x94, 0xc5, 0xd0, 0xe7,
+                    0x60, 0x2c, 0xb4, 0x77, 0x3c, 0x95, 0xa2, 0xe5, 0xc3, 0x1a, 0x64, 0xf1, 0x33,
+                    0x18, 0x9f, 0xa7, 0x6e, 0xd6, 0x1b,
+                ]),
+            ),
+            (
+                [
+                    0x84, 0x22, 0xe1, 0xbb, 0xda, 0xab, 0x52, 0x93, 0x8b, 0x81, 0xfd, 0x60, 0x2e,
+                    0xff, 0xb6, 0xf8, 0x91, 0x10, 0xe1, 0xe5, 0x72, 0x08, 0xad, 0x12, 0xd9, 0xad,
+                    0x76, 0x7e, 0x2e, 0x25, 0x51, 0x0c, 0x27, 0x14, 0x07, 0x75, 0xf9, 0x33, 0x70,
+                    0x88, 0xb9, 0x82, 0xd8, 0x3d, 0x7f, 0xcf, 0x0b, 0x2f, 0xa1, 0xed, 0xff, 0xe5,
+                    0x19, 0x52, 0xcb, 0xe7, 0x36, 0x5e, 0x95, 0xc8, 0x6e, 0xaf, 0x32, 0x5c,
+                ],
+                CompressedRistretto([
+                    0x00, 0x6c, 0xcd, 0x2a, 0x9e, 0x68, 0x67, 0xe6, 0xa2, 0xc5, 0xce, 0xa8, 0x3d,
+                    0x33, 0x02, 0xcc, 0x9d, 0xe1, 0x28, 0xdd, 0x2a, 0x9a, 0x57, 0xdd, 0x8e, 0xe7,
+                    0xb9, 0xd7, 0xff, 0xe0, 0x28, 0x26,
+                ]),
+            ),
+            (
+                [
+                    0xac, 0x22, 0x41, 0x51, 0x29, 0xb6, 0x14, 0x27, 0xbf, 0x46, 0x4e, 0x17, 0xba,
+                    0xee, 0x8d, 0xb6, 0x59, 0x40, 0xc2, 0x33, 0xb9, 0x8a, 0xfc, 0xe8, 0xd1, 0x7c,
+                    0x57, 0xbe, 0xeb, 0x78, 0x76, 0xc2, 0x15, 0x0d, 0x15, 0xaf, 0x1c, 0xb1, 0xfb,
+                    0x82, 0x4b, 0xbd, 0x14, 0x95, 0x5f, 0x2b, 0x57, 0xd0, 0x8d, 0x38, 0x8a, 0xab,
+                    0x43, 0x1a, 0x39, 0x1c, 0xfc, 0x33, 0xd5, 0xba, 0xfb, 0x5d, 0xbb, 0xaf,
+                ],
+                CompressedRistretto([
+                    0xf8, 0xf0, 0xc8, 0x7c, 0xf2, 0x37, 0x95, 0x3c, 0x58, 0x90, 0xae, 0xc3, 0x99,
+                    0x81, 0x69, 0x00, 0x5d, 0xae, 0x3e, 0xca, 0x1f, 0xbb, 0x04, 0x54, 0x8c, 0x63,
+                    0x59, 0x53, 0xc8, 0x17, 0xf9, 0x2a,
+                ]),
+            ),
+            (
+                [
+                    0x16, 0x5d, 0x69, 0x7a, 0x1e, 0xf3, 0xd5, 0xcf, 0x3c, 0x38, 0x56, 0x5b, 0xee,
+                    0xfc, 0xf8, 0x8c, 0x0f, 0x28, 0x2b, 0x8e, 0x7d, 0xbd, 0x28, 0x54, 0x4c, 0x48,
+                    0x34, 0x32, 0xf1, 0xce, 0xc7, 0x67, 0x5d, 0xeb, 0xea, 0x8e, 0xbb, 0x4e, 0x5f,
+                    0xe7, 0xd6, 0xf6, 0xe5, 0xdb, 0x15, 0xf1, 0x55, 0x87, 0xac, 0x4d, 0x4d, 0x4a,
+                    0x1d, 0xe7, 0x19, 0x1e, 0x0c, 0x1c, 0xa6, 0x66, 0x4a, 0xbc, 0xc4, 0x13,
+                ],
+                CompressedRistretto([
+                    0xae, 0x81, 0xe7, 0xde, 0xdf, 0x20, 0xa4, 0x97, 0xe1, 0x0c, 0x30, 0x4a, 0x76,
+                    0x5c, 0x17, 0x67, 0xa4, 0x2d, 0x6e, 0x06, 0x02, 0x97, 0x58, 0xd2, 0xd7, 0xe8,
+                    0xef, 0x7c, 0xc4, 0xc4, 0x11, 0x79,
+                ]),
+            ),
+            (
+                [
+                    0xa8, 0x36, 0xe6, 0xc9, 0xa9, 0xca, 0x9f, 0x1e, 0x8d, 0x48, 0x62, 0x73, 0xad,
+                    0x56, 0xa7, 0x8c, 0x70, 0xcf, 0x18, 0xf0, 0xce, 0x10, 0xab, 0xb1, 0xc7, 0x17,
+                    0x2d, 0xdd, 0x60, 0x5d, 0x7f, 0xd2, 0x97, 0x98, 0x54, 0xf4, 0x7a, 0xe1, 0xcc,
+                    0xf2, 0x04, 0xa3, 0x31, 0x02, 0x09, 0x5b, 0x42, 0x00, 0xe5, 0xbe, 0xfc, 0x04,
+                    0x65, 0xac, 0xcc, 0x26, 0x31, 0x75, 0x48, 0x5f, 0x0e, 0x17, 0xea, 0x5c,
+                ],
+                CompressedRistretto([
+                    0xe2, 0x70, 0x56, 0x52, 0xff, 0x9f, 0x5e, 0x44, 0xd3, 0xe8, 0x41, 0xbf, 0x1c,
+                    0x25, 0x1c, 0xf7, 0xdd, 0xdb, 0x77, 0xd1, 0x40, 0x87, 0x0d, 0x1a, 0xb2, 0xed,
+                    0x64, 0xf1, 0xa9, 0xce, 0x86, 0x28,
+                ]),
+            ),
+            (
+                [
+                    0x2c, 0xdc, 0x11, 0xea, 0xeb, 0x95, 0xda, 0xf0, 0x11, 0x89, 0x41, 0x7c, 0xdd,
+                    0xdb, 0xf9, 0x59, 0x52, 0x99, 0x3a, 0xa9, 0xcb, 0x9c, 0x64, 0x0e, 0xb5, 0x05,
+                    0x8d, 0x09, 0x70, 0x2c, 0x74, 0x62, 0x2c, 0x99, 0x65, 0xa6, 0x97, 0xa3, 0xb3,
+                    0x45, 0xec, 0x24, 0xee, 0x56, 0x33, 0x5b, 0x55, 0x6e, 0x67, 0x7b, 0x30, 0xe6,
+                    0xf9, 0x0a, 0xc7, 0x7d, 0x78, 0x10, 0x64, 0xf8, 0x66, 0xa3, 0xc9, 0x82,
+                ],
+                CompressedRistretto([
+                    0x80, 0xbd, 0x07, 0x26, 0x25, 0x11, 0xcd, 0xde, 0x48, 0x63, 0xf8, 0xa7, 0x43,
+                    0x4c, 0xef, 0x69, 0x67, 0x50, 0x68, 0x1c, 0xb9, 0x51, 0x0e, 0xea, 0x55, 0x70,
+                    0x88, 0xf7, 0x6d, 0x9e, 0x50, 0x65,
+                ]),
+            ),
+            (
+                [
+                    0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                ],
+                CompressedRistretto([
+                    0x30, 0x42, 0x82, 0x79, 0x10, 0x23, 0xb7, 0x31, 0x28, 0xd2, 0x77, 0xbd, 0xcb,
+                    0x5c, 0x77, 0x46, 0xef, 0x2e, 0xac, 0x08, 0xdd, 0xe9, 0xf2, 0x98, 0x33, 0x79,
+                    0xcb, 0x8e, 0x5e, 0xf0, 0x51, 0x7f,
+                ]),
+            ),
+            (
+                [
+                    0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                    0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                ],
+                CompressedRistretto([
+                    0x30, 0x42, 0x82, 0x79, 0x10, 0x23, 0xb7, 0x31, 0x28, 0xd2, 0x77, 0xbd, 0xcb,
+                    0x5c, 0x77, 0x46, 0xef, 0x2e, 0xac, 0x08, 0xdd, 0xe9, 0xf2, 0x98, 0x33, 0x79,
+                    0xcb, 0x8e, 0x5e, 0xf0, 0x51, 0x7f,
+                ]),
+            ),
+            (
+                [
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
+                ],
+                CompressedRistretto([
+                    0x30, 0x42, 0x82, 0x79, 0x10, 0x23, 0xb7, 0x31, 0x28, 0xd2, 0x77, 0xbd, 0xcb,
+                    0x5c, 0x77, 0x46, 0xef, 0x2e, 0xac, 0x08, 0xdd, 0xe9, 0xf2, 0x98, 0x33, 0x79,
+                    0xcb, 0x8e, 0x5e, 0xf0, 0x51, 0x7f,
+                ]),
+            ),
+            (
+                [
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+                ],
+                CompressedRistretto([
+                    0x30, 0x42, 0x82, 0x79, 0x10, 0x23, 0xb7, 0x31, 0x28, 0xd2, 0x77, 0xbd, 0xcb,
+                    0x5c, 0x77, 0x46, 0xef, 0x2e, 0xac, 0x08, 0xdd, 0xe9, 0xf2, 0x98, 0x33, 0x79,
+                    0xcb, 0x8e, 0x5e, 0xf0, 0x51, 0x7f,
+                ]),
+            ),
+        ];
+        // Check that onewaymap(input) == output for all the above vectors
+        for (input, output) in test_vectors {
+            let Q = RistrettoPoint::from_uniform_bytes(&input);
+            assert_eq!(&Q.compress(), output);
+        }
+    }
+
     #[test]
     fn random_roundtrip() {
         let mut rng = OsRng;


Test patch: