use std::fmt;
use cryptoxide::ed25519 as ed;
use packtool::Packed;
use rand_core::{CryptoRng, RngCore};
use super::{Curve, DhCurve, SharedSecret, SigningCurve};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("invalid public key length: expected 32 bytes, got {0}")]
InvalidPublicKeyLength(usize),
#[error("invalid signature length: expected 64 bytes, got {0}")]
InvalidSignatureLength(usize),
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[error("{0}")]
Platform(String),
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Ed25519;
impl Curve for Ed25519 {
const NAME: &'static str = "Ed25519";
const PUBLIC_KEY_SIZE: usize = 32;
const PRIVATE_KEY_SIZE: usize = 32;
type Error = Error;
type PublicKey = Ed25519PublicKey;
fn public_key_from_bytes(bytes: &[u8]) -> Result<Self::PublicKey, Self::Error> {
Ed25519PublicKey::from_bytes(bytes)
}
}
impl DhCurve for Ed25519 {
const DHLEN: usize = 32;
type SharedSecret = SharedSecret<32>;
}
impl SigningCurve for Ed25519 {
type Signature = Ed25519Signature;
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Packed)]
pub struct Ed25519PublicKey(#[packed(accessor = false)] [u8; 32]);
impl Ed25519PublicKey {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
let arr: [u8; 32] = bytes
.try_into()
.map_err(|_| Error::InvalidPublicKeyLength(bytes.len()))?;
Ok(Self(arr))
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn verify(&self, signature: Ed25519Signature, message: impl AsRef<[u8]>) -> bool {
ed::verify(message.as_ref(), &self.0, &signature.0)
}
}
impl AsRef<[u8]> for Ed25519PublicKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl fmt::Display for Ed25519PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&hex::encode(self.0))
}
}
impl fmt::Debug for Ed25519PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&hex::encode(self.0))
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Packed)]
pub struct Ed25519Signature(#[packed(accessor = false)] [u8; 64]);
impl Ed25519Signature {
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, Error> {
let arr: [u8; 64] = bytes
.try_into()
.map_err(|_| Error::InvalidSignatureLength(bytes.len()))?;
Ok(Self(arr))
}
pub fn as_bytes(&self) -> &[u8; 64] {
&self.0
}
}
impl AsRef<[u8]> for Ed25519Signature {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl fmt::Display for Ed25519Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&hex::encode(self.0))
}
}
impl fmt::Debug for Ed25519Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&hex::encode(self.0))
}
}
pub struct SoftwareEd25519PrivateKey {
seed: [u8; 32],
keypair: [u8; 64],
}
impl SoftwareEd25519PrivateKey {
pub fn generate<R: RngCore + CryptoRng>(mut rng: R) -> Self {
let mut seed = [0u8; 32];
rng.fill_bytes(&mut seed);
Self::from_seed(seed)
}
pub fn from_seed(seed: [u8; 32]) -> Self {
let (keypair, _public) = ed::keypair(&seed);
Self { seed, keypair }
}
pub fn public_key(&self) -> Ed25519PublicKey {
let pk_bytes: [u8; 32] = self.keypair[32..64].try_into().unwrap();
Ed25519PublicKey(pk_bytes)
}
pub fn sign(&self, message: &[u8]) -> Ed25519Signature {
Ed25519Signature(ed::signature(message, &self.keypair))
}
pub fn dh(&self, peer: &Ed25519PublicKey) -> SharedSecret<32> {
SharedSecret::new(ed::exchange(&peer.0, &self.seed))
}
pub fn seed(&self) -> &[u8; 32] {
&self.seed
}
}
impl Drop for SoftwareEd25519PrivateKey {
fn drop(&mut self) {
crate::zeroize::zeroize_array(&mut self.seed);
crate::zeroize::zeroize_array(&mut self.keypair);
}
}
#[cfg(not(test))]
impl fmt::Debug for SoftwareEd25519PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SoftwareEd25519PrivateKey")
.finish_non_exhaustive()
}
}
#[cfg(test)]
impl fmt::Debug for SoftwareEd25519PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SoftwareEd25519PrivateKey")
.field("seed", &hex::encode(self.seed))
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::provider::{
CryptoKeyProviderAsync, DhProviderAsync, EphemeralOnly, ProviderExt, SigningProviderAsync,
};
use rand::{SeedableRng, rngs::StdRng};
#[test]
fn generate_and_sign_verify() {
let sk = SoftwareEd25519PrivateKey::generate(rand::rng());
let pk = sk.public_key();
let msg = b"Hello hiss";
let sig = sk.sign(msg);
assert!(pk.verify(sig, msg));
}
#[test]
fn deterministic_signatures() {
let sk = SoftwareEd25519PrivateKey::generate(rand::rng());
let msg = b"determinism matters";
let sig1 = sk.sign(msg);
let sig2 = sk.sign(msg);
assert_eq!(
sig1, sig2,
"Ed25519 signatures must be deterministic (RFC 8032)"
);
}
#[test]
fn wrong_message_fails_verification() {
let sk = SoftwareEd25519PrivateKey::generate(rand::rng());
let pk = sk.public_key();
let sig = sk.sign(b"correct message");
assert!(!pk.verify(sig, b"wrong message"));
}
#[test]
fn wrong_key_fails_verification() {
let sk1 = SoftwareEd25519PrivateKey::generate(rand::rng());
let sk2 = SoftwareEd25519PrivateKey::generate(rand::rng());
let pk2 = sk2.public_key();
let sig = sk1.sign(b"signed by sk1");
assert!(!pk2.verify(sig, b"signed by sk1"));
}
#[test]
fn corrupted_signature_fails() {
let sk = SoftwareEd25519PrivateKey::generate(rand::rng());
let pk = sk.public_key();
let sig = sk.sign(b"test");
let mut raw = *sig.as_bytes();
raw[16] ^= 0xFF;
let corrupted = Ed25519Signature::try_from_bytes(&raw).unwrap();
assert!(!pk.verify(corrupted, b"test"));
}
#[test]
fn zero_signature_fails() {
let sk = SoftwareEd25519PrivateKey::generate(rand::rng());
let pk = sk.public_key();
let zero_sig = Ed25519Signature::try_from_bytes(&[0u8; 64]).unwrap();
assert!(!pk.verify(zero_sig, b"anything"));
}
#[test]
fn public_key_round_trip() {
let sk = SoftwareEd25519PrivateKey::generate(rand::rng());
let pk = sk.public_key();
let pk2 = Ed25519PublicKey::from_bytes(pk.as_bytes()).unwrap();
assert_eq!(pk, pk2);
}
#[test]
fn public_key_wrong_length_rejected() {
let err = Ed25519PublicKey::from_bytes(&[0u8; 31]).unwrap_err();
assert!(matches!(err, Error::InvalidPublicKeyLength(31)));
let err = Ed25519PublicKey::from_bytes(&[0u8; 33]).unwrap_err();
assert!(matches!(err, Error::InvalidPublicKeyLength(33)));
}
#[test]
fn signature_wrong_length_rejected() {
let err = Ed25519Signature::try_from_bytes(&[0u8; 63]).unwrap_err();
assert!(matches!(err, Error::InvalidSignatureLength(63)));
let err = Ed25519Signature::try_from_bytes(&[0u8; 65]).unwrap_err();
assert!(matches!(err, Error::InvalidSignatureLength(65)));
}
#[test]
fn from_seed_is_deterministic() {
let seed = [42u8; 32];
let sk1 = SoftwareEd25519PrivateKey::from_seed(seed);
let sk2 = SoftwareEd25519PrivateKey::from_seed(seed);
assert_eq!(sk1.public_key(), sk2.public_key());
let sig1 = sk1.sign(b"same seed same key");
let sig2 = sk2.sign(b"same seed same key");
assert_eq!(sig1, sig2);
}
#[test]
fn different_seeds_produce_different_keys() {
let sk1 = SoftwareEd25519PrivateKey::from_seed([1u8; 32]);
let sk2 = SoftwareEd25519PrivateKey::from_seed([2u8; 32]);
assert_ne!(sk1.public_key(), sk2.public_key());
}
#[test]
fn dh_is_symmetric() {
let sk1 = SoftwareEd25519PrivateKey::generate(rand::rng());
let pk1 = sk1.public_key();
let sk2 = SoftwareEd25519PrivateKey::generate(rand::rng());
let pk2 = sk2.public_key();
let ss1 = sk1.dh(&pk2);
let ss2 = sk2.dh(&pk1);
assert_eq!(ss1, ss2);
}
#[test]
fn dh_different_peers_produce_different_secrets() {
let sk = SoftwareEd25519PrivateKey::generate(rand::rng());
let peer1 = SoftwareEd25519PrivateKey::generate(rand::rng()).public_key();
let peer2 = SoftwareEd25519PrivateKey::generate(rand::rng()).public_key();
let ss1 = sk.dh(&peer1);
let ss2 = sk.dh(&peer2);
assert_ne!(ss1, ss2);
}
#[tokio::test]
async fn provider_sign_and_dh() {
let mut provider = EphemeralOnly::new(StdRng::from_os_rng());
let sk1 = CryptoKeyProviderAsync::<Ed25519>::generate_static_key_async(&mut provider)
.await
.unwrap();
let pk1 = provider.public(&sk1).unwrap();
let sk2 = CryptoKeyProviderAsync::<Ed25519>::generate_ephemeral_key_async(&mut provider)
.await
.unwrap();
let pk2 = provider.public(&sk2).unwrap();
const MSG: &[u8] = b"hello hiss";
let sig = SigningProviderAsync::<Ed25519>::sign_async(&provider, &sk1, MSG)
.await
.unwrap();
assert!(pk1.verify(sig, MSG));
assert!(!pk2.verify(sig, MSG));
let ss1 = DhProviderAsync::<Ed25519>::dh_async(&provider, &sk1, &pk2)
.await
.unwrap();
let ss2 = DhProviderAsync::<Ed25519>::dh_async(&provider, &sk2, &pk1)
.await
.unwrap();
assert_eq!(ss1, ss2);
}
}