2020-05-08 19:21:19 -07:00
|
|
|
#![cfg_attr(feature = "nightly", feature(external_doc))]
|
|
|
|
#![cfg_attr(feature = "nightly", doc(include = "../README.md"))]
|
2020-05-06 18:32:07 -07:00
|
|
|
|
2020-05-10 16:39:50 -07:00
|
|
|
use std::convert::TryFrom;
|
|
|
|
|
2020-05-07 22:15:38 -07:00
|
|
|
use curve25519_dalek::{
|
|
|
|
constants,
|
|
|
|
ristretto::{CompressedRistretto, RistrettoPoint},
|
|
|
|
scalar::Scalar,
|
|
|
|
};
|
2020-05-06 18:32:07 -07:00
|
|
|
use rand_core::{CryptoRng, RngCore};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
#[cfg(test)]
|
2020-05-11 21:29:31 -07:00
|
|
|
use proptest::{arbitrary::Arbitrary, array, collection, prelude::*};
|
2020-05-06 18:32:07 -07:00
|
|
|
|
|
|
|
/// A Diffie-Hellman secret key used to derive a shared secret when
|
|
|
|
/// combined with a public key, that only exists for a short time.
|
2020-05-07 22:23:37 -07:00
|
|
|
#[cfg_attr(test, derive(Debug))]
|
2020-05-06 18:32:07 -07:00
|
|
|
pub struct EphemeralSecret(pub(crate) Scalar);
|
|
|
|
|
2020-05-06 23:57:07 -07:00
|
|
|
impl From<[u8; 32]> for EphemeralSecret {
|
2020-05-07 23:06:39 -07:00
|
|
|
fn from(bytes: [u8; 32]) -> EphemeralSecret {
|
2020-05-06 23:57:07 -07:00
|
|
|
match Scalar::from_canonical_bytes(bytes) {
|
|
|
|
Some(scalar) => Self(scalar),
|
|
|
|
None => Self(Scalar::from_bytes_mod_order(bytes)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-11 21:29:31 -07:00
|
|
|
impl From<[u8; 64]> for EphemeralSecret {
|
|
|
|
fn from(bytes: [u8; 64]) -> EphemeralSecret {
|
|
|
|
Self(Scalar::from_bytes_mod_order_wide(&bytes))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-06 18:32:07 -07:00
|
|
|
impl EphemeralSecret {
|
|
|
|
/// Generate a `EphemeralSecret` using a new scalar mod the group
|
|
|
|
/// order.
|
2020-05-07 23:06:39 -07:00
|
|
|
pub fn new<T>(mut rng: T) -> EphemeralSecret
|
2020-05-06 18:32:07 -07:00
|
|
|
where
|
|
|
|
T: RngCore + CryptoRng,
|
|
|
|
{
|
|
|
|
Self(Scalar::random(&mut rng))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Do Diffie-Hellman key agreement between self's secret
|
|
|
|
/// and a peer's public key, resulting in a `SharedSecret`.
|
|
|
|
pub fn diffie_hellman(&self, peer_public: &PublicKey) -> SharedSecret {
|
|
|
|
SharedSecret(self.0 * peer_public.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-06 23:57:07 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
impl Arbitrary for EphemeralSecret {
|
|
|
|
type Parameters = ();
|
|
|
|
|
|
|
|
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
2020-05-10 16:39:50 -07:00
|
|
|
array::uniform32(any::<u8>()).prop_map_into().boxed()
|
2020-05-06 23:57:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
type Strategy = BoxedStrategy<Self>;
|
|
|
|
}
|
|
|
|
|
2020-05-06 18:32:07 -07:00
|
|
|
/// The public key derived from an ephemeral or static secret key.
|
2020-05-07 22:23:37 -07:00
|
|
|
#[derive(Clone, Copy, Debug, Eq, Deserialize, PartialEq, Serialize)]
|
2020-05-07 22:15:38 -07:00
|
|
|
pub struct PublicKey(pub(crate) RistrettoPoint);
|
2020-05-06 18:32:07 -07:00
|
|
|
|
2020-05-06 23:57:07 -07:00
|
|
|
impl<'a> From<&'a EphemeralSecret> for PublicKey {
|
2020-05-07 23:06:39 -07:00
|
|
|
fn from(secret: &'a EphemeralSecret) -> PublicKey {
|
2020-05-06 18:32:07 -07:00
|
|
|
Self(&secret.0 * &constants::RISTRETTO_BASEPOINT_TABLE)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<PublicKey> for [u8; 32] {
|
|
|
|
/// Copy the bytes of the internal `RistrettoPoint` as the
|
|
|
|
/// canonical compressed wire format. Two `RistrettoPoint`s (and
|
|
|
|
/// thus two `PublicKey`s) are equal iff their encodings are
|
|
|
|
/// equal.
|
2020-05-07 23:06:39 -07:00
|
|
|
fn from(public_key: PublicKey) -> [u8; 32] {
|
2020-05-06 18:32:07 -07:00
|
|
|
public_key.0.compress().to_bytes()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-06 23:57:07 -07:00
|
|
|
impl<'a> From<&'a StaticSecret> for PublicKey {
|
2020-05-07 23:06:39 -07:00
|
|
|
fn from(secret: &'a StaticSecret) -> PublicKey {
|
2020-05-06 18:32:07 -07:00
|
|
|
Self(&secret.0 * &constants::RISTRETTO_BASEPOINT_TABLE)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-10 16:39:50 -07:00
|
|
|
impl TryFrom<[u8; 32]> for PublicKey {
|
|
|
|
type Error = &'static str;
|
|
|
|
|
2020-05-06 18:32:07 -07:00
|
|
|
/// Attempts to decompress an internal `RistrettoPoint` from the
|
|
|
|
/// input bytes, which should be the canonical compressed encoding
|
|
|
|
/// of a `RistrettoPoint`.
|
2020-05-10 16:39:50 -07:00
|
|
|
fn try_from(bytes: [u8; 32]) -> Result<PublicKey, Self::Error> {
|
|
|
|
match CompressedRistretto::from_slice(&bytes).decompress() {
|
|
|
|
Some(ristretto_point) => Ok(Self(ristretto_point)),
|
|
|
|
None => Err("Ristretto point decompression failed"),
|
|
|
|
}
|
2020-05-06 18:32:07 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-07 22:15:38 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
impl Arbitrary for PublicKey {
|
|
|
|
type Parameters = ();
|
|
|
|
|
|
|
|
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
|
|
|
array::uniform32(any::<u8>())
|
2020-05-11 20:36:04 -07:00
|
|
|
.prop_filter_map("Decompressible Ristretto point", |b| {
|
|
|
|
PublicKey::try_from(b).ok()
|
|
|
|
})
|
2020-05-07 22:15:38 -07:00
|
|
|
.boxed()
|
|
|
|
}
|
|
|
|
|
|
|
|
type Strategy = BoxedStrategy<Self>;
|
|
|
|
}
|
|
|
|
|
2020-05-06 18:32:07 -07:00
|
|
|
/// A Diffie-Hellman shared secret derived from an `EphemeralSecret`
|
|
|
|
/// or `StaticSecret` and the other party's `PublicKey`.
|
2020-05-07 22:15:38 -07:00
|
|
|
pub struct SharedSecret(pub(crate) RistrettoPoint);
|
2020-05-06 18:32:07 -07:00
|
|
|
|
2020-05-06 23:57:07 -07:00
|
|
|
impl From<SharedSecret> for [u8; 32] {
|
|
|
|
/// Copy the bytes of the internal `RistrettoPoint` as the
|
|
|
|
/// canonical compressed wire format. Two `RistrettoPoint`s (and
|
|
|
|
/// thus two `PublicKey`s) are equal iff their encodings are
|
|
|
|
/// equal.
|
2020-05-07 23:06:39 -07:00
|
|
|
fn from(shared_secret: SharedSecret) -> [u8; 32] {
|
2020-05-06 23:57:07 -07:00
|
|
|
shared_secret.0.compress().to_bytes()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-06 18:32:07 -07:00
|
|
|
/// A Diffie-Hellman secret key used to derive a shared secret when
|
|
|
|
/// combined with a public key, that can be stored and loaded.
|
2020-05-11 20:36:51 -07:00
|
|
|
#[derive(Clone, Copy, Eq, PartialEq, Deserialize, Serialize)]
|
2020-05-07 22:23:37 -07:00
|
|
|
#[cfg_attr(test, derive(Debug))]
|
2020-05-06 18:32:07 -07:00
|
|
|
pub struct StaticSecret(pub(crate) Scalar);
|
|
|
|
|
2020-05-11 20:36:51 -07:00
|
|
|
impl From<StaticSecret> for [u8; 32] {
|
|
|
|
fn from(static_secret: StaticSecret) -> [u8; 32] {
|
|
|
|
static_secret.0.to_bytes()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-06 18:32:07 -07:00
|
|
|
impl From<[u8; 32]> for StaticSecret {
|
2020-05-07 23:06:39 -07:00
|
|
|
fn from(bytes: [u8; 32]) -> StaticSecret {
|
2020-05-06 18:32:07 -07:00
|
|
|
match Scalar::from_canonical_bytes(bytes) {
|
|
|
|
Some(scalar) => Self(scalar),
|
|
|
|
None => Self(Scalar::from_bytes_mod_order(bytes)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-11 21:29:31 -07:00
|
|
|
impl From<[u8; 64]> for StaticSecret {
|
|
|
|
fn from(bytes: [u8; 64]) -> StaticSecret {
|
|
|
|
Self(Scalar::from_bytes_mod_order_wide(&bytes))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-06 18:32:07 -07:00
|
|
|
impl StaticSecret {
|
|
|
|
/// Generate a `StaticSecret` using a new scalar mod the group
|
|
|
|
/// order.
|
2020-05-07 23:06:39 -07:00
|
|
|
pub fn new<T>(mut rng: T) -> StaticSecret
|
2020-05-06 18:32:07 -07:00
|
|
|
where
|
|
|
|
T: RngCore + CryptoRng,
|
|
|
|
{
|
|
|
|
Self(Scalar::random(&mut rng))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Do Diffie-Hellman key agreement between self's secret
|
|
|
|
/// and a peer's public key, resulting in a `SharedSecret`.
|
|
|
|
pub fn diffie_hellman(&self, peer_public: &PublicKey) -> SharedSecret {
|
2020-05-06 23:57:07 -07:00
|
|
|
SharedSecret(self.0 * peer_public.0)
|
2020-05-06 18:32:07 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-06 23:57:07 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
impl Arbitrary for StaticSecret {
|
|
|
|
type Parameters = ();
|
|
|
|
|
|
|
|
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
|
|
|
array::uniform32(any::<u8>())
|
|
|
|
.prop_filter("Valid scalar mod l", |b| {
|
|
|
|
Scalar::from_bytes_mod_order(*b).is_canonical()
|
|
|
|
})
|
|
|
|
.prop_map(|bytes| return Self::from(bytes))
|
|
|
|
.boxed()
|
|
|
|
}
|
|
|
|
|
|
|
|
type Strategy = BoxedStrategy<Self>;
|
|
|
|
}
|
|
|
|
|
2020-05-01 08:33:34 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2020-05-06 18:32:07 -07:00
|
|
|
|
2020-05-07 21:31:23 -07:00
|
|
|
use bincode;
|
|
|
|
use rand_core::OsRng;
|
|
|
|
|
2020-05-06 18:32:07 -07:00
|
|
|
use super::*;
|
|
|
|
|
2020-05-07 21:31:23 -07:00
|
|
|
#[test]
|
|
|
|
fn random_dh() {
|
|
|
|
let alice_secret = EphemeralSecret::new(&mut OsRng);
|
|
|
|
let alice_public = PublicKey::from(&alice_secret);
|
|
|
|
|
2020-05-07 22:15:38 -07:00
|
|
|
let bob_secret = StaticSecret::new(&mut OsRng);
|
2020-05-07 21:31:23 -07:00
|
|
|
let bob_public = PublicKey::from(&bob_secret);
|
|
|
|
|
|
|
|
let alice_shared_secret = alice_secret.diffie_hellman(&bob_public);
|
|
|
|
let bob_shared_secret = bob_secret.diffie_hellman(&alice_public);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
<[u8; 32]>::from(alice_shared_secret),
|
|
|
|
<[u8; 32]>::from(bob_shared_secret)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-05-06 23:57:07 -07:00
|
|
|
proptest! {
|
|
|
|
|
2020-05-11 21:29:31 -07:00
|
|
|
#[test]
|
|
|
|
fn random_dh_wide(alice_bytes in collection::vec(any::<u8>(), 64),
|
|
|
|
bob_bytes in collection::vec(any::<u8>(), 64)) {
|
|
|
|
let mut a = [0u8; 64];
|
|
|
|
a.copy_from_slice(alice_bytes.as_slice());
|
|
|
|
|
|
|
|
let alice_secret = EphemeralSecret::from(a);
|
|
|
|
let alice_public = PublicKey::from(&alice_secret);
|
|
|
|
|
|
|
|
let mut b = [0u8; 64];
|
|
|
|
b.copy_from_slice(bob_bytes.as_slice());
|
|
|
|
|
|
|
|
let bob_secret = StaticSecret::from(b);
|
|
|
|
let bob_public = PublicKey::from(&bob_secret);
|
|
|
|
|
|
|
|
let alice_shared_secret = alice_secret.diffie_hellman(&bob_public);
|
|
|
|
let bob_shared_secret = bob_secret.diffie_hellman(&alice_public);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
<[u8; 32]>::from(alice_shared_secret),
|
|
|
|
<[u8; 32]>::from(bob_shared_secret)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-05-06 23:57:07 -07:00
|
|
|
#[test]
|
|
|
|
fn ephemeral_dh(
|
|
|
|
alice_secret in any::<EphemeralSecret>(),
|
|
|
|
bob_secret in any::<EphemeralSecret>()
|
|
|
|
) {
|
|
|
|
let alice_public = PublicKey::from(&alice_secret);
|
|
|
|
let bob_public = PublicKey::from(&bob_secret);
|
|
|
|
|
|
|
|
let alice_shared_secret = alice_secret.diffie_hellman(&bob_public);
|
|
|
|
let bob_shared_secret = bob_secret.diffie_hellman(&alice_public);
|
|
|
|
|
|
|
|
prop_assert_eq!(
|
|
|
|
<[u8; 32]>::from(alice_shared_secret),
|
|
|
|
<[u8; 32]>::from(bob_shared_secret)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn static_dh(
|
|
|
|
alice_secret in any::<StaticSecret>(),
|
|
|
|
bob_secret in any::<StaticSecret>()
|
|
|
|
) {
|
|
|
|
let alice_public = PublicKey::from(&alice_secret);
|
|
|
|
let bob_public = PublicKey::from(&bob_secret);
|
|
|
|
|
|
|
|
let alice_shared_secret = alice_secret.diffie_hellman(&bob_public);
|
|
|
|
let bob_shared_secret = bob_secret.diffie_hellman(&alice_public);
|
|
|
|
|
|
|
|
prop_assert_eq!(
|
|
|
|
<[u8; 32]>::from(alice_shared_secret),
|
|
|
|
<[u8; 32]>::from(bob_shared_secret)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-05-07 21:31:23 -07:00
|
|
|
#[test]
|
|
|
|
fn serde_pubkey(alice_secret in any::<EphemeralSecret>()) {
|
|
|
|
let alice_public = PublicKey::from(&alice_secret);
|
|
|
|
|
|
|
|
let serialized = bincode::serialize(&alice_public).unwrap();
|
|
|
|
|
|
|
|
prop_assert_eq!(
|
|
|
|
alice_public, bincode::deserialize(&serialized[..]).unwrap()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn serde_static_key(alice_secret in any::<StaticSecret>()) {
|
|
|
|
let serialized = bincode::serialize(&alice_secret).unwrap();
|
|
|
|
|
|
|
|
prop_assert_eq!(
|
|
|
|
alice_secret, bincode::deserialize(&serialized[..]).unwrap()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-05-07 22:15:38 -07:00
|
|
|
#[test]
|
|
|
|
fn from_into_pubkey_bytes(pubkey in any::<PublicKey>()) {
|
|
|
|
let bytes: [u8; 32] = pubkey.into();
|
2020-05-07 21:31:23 -07:00
|
|
|
|
2020-05-07 22:15:38 -07:00
|
|
|
prop_assert_eq!(
|
2020-05-10 16:39:50 -07:00
|
|
|
Ok(pubkey), PublicKey::try_from(bytes)
|
2020-05-07 22:15:38 -07:00
|
|
|
);
|
|
|
|
}
|
2020-05-07 21:31:23 -07:00
|
|
|
|
2020-05-11 20:36:51 -07:00
|
|
|
#[test]
|
|
|
|
fn from_into_static_secret_bytes(static_secret in any::<StaticSecret>()) {
|
|
|
|
let bytes: [u8; 32] = static_secret.into();
|
|
|
|
|
|
|
|
prop_assert_eq!(
|
|
|
|
static_secret, StaticSecret::from(bytes)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-05-07 22:38:25 -07:00
|
|
|
#[test]
|
|
|
|
fn scalar_mul_different_paths(
|
|
|
|
secret in any::<EphemeralSecret>(),
|
|
|
|
) {
|
|
|
|
let other_public = PublicKey(constants::RISTRETTO_BASEPOINT_POINT * secret.0);
|
|
|
|
|
|
|
|
prop_assert_eq!(
|
|
|
|
other_public,
|
|
|
|
PublicKey::from(&secret)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-05-06 23:57:07 -07:00
|
|
|
}
|
2020-05-01 08:33:34 -07:00
|
|
|
}
|