Apply the Elligator map twice to ensure a uniform distribution

As noted in the Decaf paper, mapping twice and adding the results ensures a
uniform distribution over the group.  This changes our random point and
hash-to-point functions to do this, matching the Sage script.
This commit is contained in:
Henry de Valence 2018-03-19 15:37:38 -07:00
parent fc6672ab43
commit 7f39656b4a

View file

@ -474,7 +474,7 @@ use core::borrow::Borrow;
use rand::Rng; use rand::Rng;
use digest::Digest; use digest::Digest;
use generic_array::typenum::U32; use generic_array::typenum::U64;
use constants; use constants;
use field::FieldElement; use field::FieldElement;
@ -815,7 +815,7 @@ impl RistrettoPoint {
/// ///
/// This method is not public because it's just used for hashing /// This method is not public because it's just used for hashing
/// to a point -- proper elligator support is deferred for now. /// to a point -- proper elligator support is deferred for now.
pub(crate) fn elligator_ristretto_flavour(r_0: &FieldElement) -> RistrettoPoint { pub(crate) fn elligator_ristretto_flavor(r_0: &FieldElement) -> RistrettoPoint {
let (i, d) = (&constants::SQRT_M1, &constants::EDWARDS_D); let (i, d) = (&constants::SQRT_M1, &constants::EDWARDS_D);
let one = FieldElement::one(); let one = FieldElement::one();
@ -870,27 +870,40 @@ impl RistrettoPoint {
/// ///
/// # Implementation /// # Implementation
/// ///
/// Uses the Ristretto-flavoured Elligator 2 map, so that the discrete log of the /// Uses the Ristretto-flavoured Elligator 2 map, so that the
/// output point with respect to any other point should be unknown. /// discrete log of the output point with respect to any other
/// point should be unknown. The map is applied twice and the
/// results are added, to ensure a uniform distribution.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn random<T: Rng>(rng: &mut T) -> Self { pub fn random<T: Rng>(rng: &mut T) -> Self {
let mut field_bytes = [0u8; 32]; let mut field_bytes = [0u8; 32];
rng.fill_bytes(&mut field_bytes); rng.fill_bytes(&mut field_bytes);
let r_0 = FieldElement::from_bytes(&field_bytes); let r_1 = FieldElement::from_bytes(&field_bytes);
RistrettoPoint::elligator_ristretto_flavour(&r_0) let R_1 = RistrettoPoint::elligator_ristretto_flavor(&r_1);
rng.fill_bytes(&mut field_bytes);
let r_2 = FieldElement::from_bytes(&field_bytes);
let R_2 = RistrettoPoint::elligator_ristretto_flavor(&r_2);
// Applying Elligator twice and adding the results ensures a
// uniform distribution.
&R_1 + &R_2
} }
/// Hash a slice of bytes into a `RistrettoPoint`. /// Hash a slice of bytes into a `RistrettoPoint`.
/// ///
/// Takes a type parameter `D`, which is any `Digest` producing 32 /// Takes a type parameter `D`, which is any `Digest` producing 64
/// bytes (256 bits) of output. /// bytes of output.
/// ///
/// Convenience wrapper around `from_hash`. /// Convenience wrapper around `from_hash`.
/// ///
/// # Implementation /// # Implementation
/// ///
/// Uses the Ristretto-flavoured Elligator 2 map, so that the discrete log of the /// Uses the Ristretto-flavoured Elligator 2 map, so that the
/// output point with respect to any other point should be unknown. /// discrete log of the output point with respect to any other
/// point should be unknown. The map is applied twice and the
/// results are added, to ensure a uniform distribution.
/// ///
/// # Example /// # Example
/// ///
@ -898,18 +911,18 @@ impl RistrettoPoint {
/// # extern crate curve25519_dalek; /// # extern crate curve25519_dalek;
/// # use curve25519_dalek::ristretto::RistrettoPoint; /// # use curve25519_dalek::ristretto::RistrettoPoint;
/// extern crate sha2; /// extern crate sha2;
/// use sha2::Sha256; /// use sha2::Sha512;
/// ///
/// # // Need fn main() here in comment so the doctest compiles /// # // Need fn main() here in comment so the doctest compiles
/// # // See https://doc.rust-lang.org/book/documentation.html#documentation-as-tests /// # // See https://doc.rust-lang.org/book/documentation.html#documentation-as-tests
/// # fn main() { /// # fn main() {
/// let msg = "To really appreciate architecture, you may even need to commit a murder"; /// let msg = "To really appreciate architecture, you may even need to commit a murder";
/// let P = RistrettoPoint::hash_from_bytes::<Sha256>(msg.as_bytes()); /// let P = RistrettoPoint::hash_from_bytes::<Sha512>(msg.as_bytes());
/// # } /// # }
/// ``` /// ```
/// ///
pub fn hash_from_bytes<D>(input: &[u8]) -> RistrettoPoint pub fn hash_from_bytes<D>(input: &[u8]) -> RistrettoPoint
where D: Digest<OutputSize = U32> + Default where D: Digest<OutputSize = U64> + Default
{ {
let mut hash = D::default(); let mut hash = D::default();
hash.input(input); hash.input(input);
@ -922,13 +935,24 @@ impl RistrettoPoint {
/// to stream data into the `Digest` than to pass a single byte /// to stream data into the `Digest` than to pass a single byte
/// slice. /// slice.
pub fn from_hash<D>(hash: D) -> RistrettoPoint pub fn from_hash<D>(hash: D) -> RistrettoPoint
where D: Digest<OutputSize = U32> + Default where D: Digest<OutputSize = U64> + Default
{ {
// XXX this seems clumsy // dealing with generic arrays is clumsy, until const generics land
let mut output = [0u8; 32]; let output = hash.result();
output.copy_from_slice(hash.result().as_slice());
let r_0 = FieldElement::from_bytes(&output); let mut r_1_bytes = [0u8; 32];
RistrettoPoint::elligator_ristretto_flavour(&r_0) r_1_bytes.copy_from_slice(&output.as_slice()[0..32]);
let r_1 = FieldElement::from_bytes(&r_1_bytes);
let R_1 = RistrettoPoint::elligator_ristretto_flavor(&r_1);
let mut r_2_bytes = [0u8; 32];
r_2_bytes.copy_from_slice(&output.as_slice()[0..32]);
let r_2 = FieldElement::from_bytes(&r_2_bytes);
let R_2 = RistrettoPoint::elligator_ristretto_flavor(&r_2);
// Applying Elligator twice and adding the results ensures a
// uniform distribution.
&R_1 + &R_2
} }
} }
@ -1427,7 +1451,7 @@ mod test {
]; ];
for i in 0..16 { for i in 0..16 {
let r_0 = FieldElement::from_bytes(&bytes[i]); let r_0 = FieldElement::from_bytes(&bytes[i]);
let Q = RistrettoPoint::elligator_ristretto_flavour(&r_0); let Q = RistrettoPoint::elligator_ristretto_flavor(&r_0);
assert_eq!(Q.compress(), encoded_images[i]); assert_eq!(Q.compress(), encoded_images[i]);
} }
} }