mirror of
https://github.com/saymrwulf/curve25519-dalek-source.git
synced 2026-09-05 20:30:57 +00:00
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:
parent
fc6672ab43
commit
7f39656b4a
1 changed files with 44 additions and 20 deletions
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue