use crate::{NoiseConfig, NoiseError, Protocol, ProtocolParams};
use curve25519_dalek::edwards::CompressedEdwardsY;
use lazy_static::lazy_static;
use libp2p_core::UpgradeInfo;
use libp2p_core::{identity, identity::ed25519};
use rand::Rng;
use sha2::{Digest, Sha512};
use x25519_dalek::{x25519, X25519_BASEPOINT_BYTES};
use zeroize::Zeroize;
use super::*;
lazy_static! {
static ref PARAMS_IK: ProtocolParams = "Noise_IK_25519_ChaChaPoly_SHA256"
.parse()
.map(ProtocolParams)
.expect("Invalid protocol name");
static ref PARAMS_IX: ProtocolParams = "Noise_IX_25519_ChaChaPoly_SHA256"
.parse()
.map(ProtocolParams)
.expect("Invalid protocol name");
static ref PARAMS_XX: ProtocolParams = "Noise_XX_25519_ChaChaPoly_SHA256"
.parse()
.map(ProtocolParams)
.expect("Invalid protocol name");
}
#[derive(Clone)]
pub struct X25519([u8; 32]);
impl AsRef<[u8]> for X25519 {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl Zeroize for X25519 {
fn zeroize(&mut self) {
self.0.zeroize()
}
}
impl UpgradeInfo for NoiseConfig<IX, X25519> {
type Info = &'static [u8];
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once(b"/noise/ix/25519/chachapoly/sha256/0.1.0")
}
}
impl UpgradeInfo for NoiseConfig<XX, X25519> {
type Info = &'static [u8];
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once(b"/noise/xx/25519/chachapoly/sha256/0.1.0")
}
}
impl<R> UpgradeInfo for NoiseConfig<IK, X25519, R> {
type Info = &'static [u8];
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once(b"/noise/ik/25519/chachapoly/sha256/0.1.0")
}
}
impl Protocol<X25519> for X25519 {
fn params_ik() -> ProtocolParams {
PARAMS_IK.clone()
}
fn params_ix() -> ProtocolParams {
PARAMS_IX.clone()
}
fn params_xx() -> ProtocolParams {
PARAMS_XX.clone()
}
fn public_from_bytes(bytes: &[u8]) -> Result<PublicKey<X25519>, NoiseError> {
if bytes.len() != 32 {
return Err(NoiseError::InvalidKey);
}
let mut pk = [0u8; 32];
pk.copy_from_slice(bytes);
Ok(PublicKey(X25519(pk)))
}
fn linked(id_pk: &identity::PublicKey, dh_pk: &PublicKey<X25519>) -> bool {
if let identity::PublicKey::Ed25519(ref p) = id_pk {
PublicKey::from_ed25519(p).as_ref() == dh_pk.as_ref()
} else {
false
}
}
}
impl Keypair<X25519> {
pub(super) fn default() -> Self {
Keypair {
secret: SecretKey(X25519([0u8; 32])),
public: PublicKey(X25519([0u8; 32])),
}
}
pub fn new() -> Keypair<X25519> {
let mut sk_bytes = [0u8; 32];
rand::thread_rng().fill(&mut sk_bytes);
let sk = SecretKey(X25519(sk_bytes)); sk_bytes.zeroize();
Self::from(sk)
}
pub fn from_identity(id_keys: &identity::Keypair) -> Option<AuthenticKeypair<X25519>> {
match id_keys {
identity::Keypair::Ed25519(p) => {
let kp = Keypair::from(SecretKey::from_ed25519(&p.secret()));
let id = KeypairIdentity {
public: id_keys.public(),
signature: None,
};
Some(AuthenticKeypair {
keypair: kp,
identity: id,
})
}
_ => None,
}
}
}
impl From<SecretKey<X25519>> for Keypair<X25519> {
fn from(secret: SecretKey<X25519>) -> Keypair<X25519> {
let public = PublicKey(X25519(x25519((secret.0).0, X25519_BASEPOINT_BYTES)));
Keypair { secret, public }
}
}
impl PublicKey<X25519> {
pub fn from_ed25519(pk: &ed25519::PublicKey) -> Self {
PublicKey(X25519(
CompressedEdwardsY(pk.encode())
.decompress()
.expect("An Ed25519 public key is a valid point by construction.")
.to_montgomery()
.0,
))
}
}
impl SecretKey<X25519> {
pub fn from_ed25519(ed25519_sk: &ed25519::SecretKey) -> Self {
let mut curve25519_sk: [u8; 32] = [0; 32];
let hash = Sha512::digest(ed25519_sk.as_ref());
curve25519_sk.copy_from_slice(&hash[..32]);
let sk = SecretKey(X25519(curve25519_sk)); curve25519_sk.zeroize();
sk
}
}
#[doc(hidden)]
impl snow::types::Dh for Keypair<X25519> {
fn name(&self) -> &'static str {
"25519"
}
fn pub_len(&self) -> usize {
32
}
fn priv_len(&self) -> usize {
32
}
fn pubkey(&self) -> &[u8] {
self.public.as_ref()
}
fn privkey(&self) -> &[u8] {
self.secret.as_ref()
}
fn set(&mut self, sk: &[u8]) {
let mut secret = [0u8; 32];
secret.copy_from_slice(sk);
self.secret = SecretKey(X25519(secret)); self.public = PublicKey(X25519(x25519(secret, X25519_BASEPOINT_BYTES)));
secret.zeroize();
}
fn generate(&mut self, rng: &mut dyn snow::types::Random) {
let mut secret = [0u8; 32];
rng.fill_bytes(&mut secret);
self.secret = SecretKey(X25519(secret)); self.public = PublicKey(X25519(x25519(secret, X25519_BASEPOINT_BYTES)));
secret.zeroize();
}
fn dh(&self, pk: &[u8], shared_secret: &mut [u8]) -> Result<(), ()> {
let mut p = [0; 32];
p.copy_from_slice(&pk[..32]);
let ss = x25519((self.secret.0).0, p);
shared_secret[..32].copy_from_slice(&ss[..]);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use libp2p_core::identity::ed25519;
use quickcheck::*;
use sodiumoxide::crypto::sign;
use std::os::raw::c_int;
use x25519_dalek::StaticSecret;
#[test]
fn prop_ed25519_to_x25519_matches_libsodium() {
fn prop() -> bool {
let ed25519 = ed25519::Keypair::generate();
let x25519 = Keypair::from(SecretKey::from_ed25519(&ed25519.secret()));
let sodium_sec = ed25519_sk_to_curve25519(&sign::SecretKey(ed25519.encode()));
let sodium_pub =
ed25519_pk_to_curve25519(&sign::PublicKey(ed25519.public().encode().clone()));
let our_pub = x25519.public.0;
let our_sec = StaticSecret::from((x25519.secret.0).0).to_bytes();
sodium_sec.as_ref() == Some(&our_sec) && sodium_pub.as_ref() == Some(&our_pub.0)
}
quickcheck(prop as fn() -> _);
}
#[test]
fn prop_public_ed25519_to_x25519_matches() {
fn prop() -> bool {
let ed25519 = ed25519::Keypair::generate();
let x25519 = Keypair::from(SecretKey::from_ed25519(&ed25519.secret()));
let x25519_public = PublicKey::from_ed25519(&ed25519.public());
x25519.public == x25519_public
}
quickcheck(prop as fn() -> _);
}
extern "C" {
pub fn crypto_sign_ed25519_pk_to_curve25519(c: *mut u8, e: *const u8) -> c_int;
pub fn crypto_sign_ed25519_sk_to_curve25519(c: *mut u8, e: *const u8) -> c_int;
}
pub fn ed25519_pk_to_curve25519(k: &sign::PublicKey) -> Option<[u8; 32]> {
let mut out = [0u8; 32];
unsafe {
if crypto_sign_ed25519_pk_to_curve25519(out.as_mut_ptr(), (&k.0).as_ptr()) == 0 {
Some(out)
} else {
None
}
}
}
pub fn ed25519_sk_to_curve25519(k: &sign::SecretKey) -> Option<[u8; 32]> {
let mut out = [0u8; 32];
unsafe {
if crypto_sign_ed25519_sk_to_curve25519(out.as_mut_ptr(), (&k.0).as_ptr()) == 0 {
Some(out)
} else {
None
}
}
}
}