use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use hkdf::Hkdf;
use sha2::{Digest, Sha256, Sha512};
use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret as X25519StaticSecret};
use super::error::CryptoError;
const DERIVATION_SALT: &[u8] = b"newton-privacy-ed25519";
const SIGNED_READ_DERIVATION_SALT: &[u8] = b"newton-pds-signed-read-v1";
pub fn derive_ed25519_from_ecdsa(ecdsa_key: &[u8; 32]) -> Result<SigningKey, CryptoError> {
let hkdf = Hkdf::<Sha256>::new(Some(DERIVATION_SALT), ecdsa_key);
let mut okm = [0u8; 32];
hkdf.expand(b"", &mut okm)
.map_err(|e| CryptoError::KeyDerivation(e.to_string()))?;
Ok(SigningKey::from_bytes(&okm))
}
pub fn derive_ed25519_signed_read(ecdsa_key: &[u8; 32]) -> Result<SigningKey, CryptoError> {
let hkdf = Hkdf::<Sha256>::new(Some(SIGNED_READ_DERIVATION_SALT), ecdsa_key);
let mut okm = [0u8; 32];
hkdf.expand(b"", &mut okm)
.map_err(|e| CryptoError::KeyDerivation(e.to_string()))?;
Ok(SigningKey::from_bytes(&okm))
}
pub fn ed25519_to_x25519_private(signing_key: &SigningKey) -> Result<Vec<u8>, CryptoError> {
let hash = Sha512::digest(signing_key.to_bytes());
let mut x25519_bytes = [0u8; 32];
x25519_bytes.copy_from_slice(&hash[..32]);
Ok(x25519_bytes.to_vec())
}
pub fn ed25519_to_x25519_public(signing_key: &SigningKey) -> Result<Vec<u8>, CryptoError> {
let x25519_secret_bytes = ed25519_to_x25519_private(signing_key)?;
let secret_array: [u8; 32] = x25519_secret_bytes
.as_slice()
.try_into()
.map_err(|_| CryptoError::KeyDerivation("x25519 secret must be 32 bytes".into()))?;
let secret = X25519StaticSecret::from(secret_array);
let public = X25519PublicKey::from(&secret);
Ok(public.as_bytes().to_vec())
}
pub fn sign_data_ref(signing_key: &SigningKey, data: &[u8]) -> Vec<u8> {
let sig: Signature = signing_key.sign(data);
sig.to_bytes().to_vec()
}
pub fn verify_data_ref(verifying_key: &VerifyingKey, data: &[u8], signature: &[u8]) -> Result<(), CryptoError> {
let sig = Signature::from_slice(signature)
.map_err(|e| CryptoError::VerificationFailed(format!("invalid signature bytes: {e}")))?;
verifying_key
.verify(data, &sig)
.map_err(|e| CryptoError::VerificationFailed(format!("{e}")))
}
#[cfg(test)]
mod tests {
use super::*;
fn test_ecdsa_key() -> [u8; 32] {
let mut key = [0u8; 32];
key[0] = 0x42;
key[31] = 0x01;
key
}
#[test]
fn derivation_is_deterministic() {
let ecdsa_key = test_ecdsa_key();
let sk1 = derive_ed25519_from_ecdsa(&ecdsa_key).expect("derivation 1 failed");
let sk2 = derive_ed25519_from_ecdsa(&ecdsa_key).expect("derivation 2 failed");
assert_eq!(
sk1.to_bytes(),
sk2.to_bytes(),
"same ECDSA key must produce same Ed25519 key"
);
}
#[test]
fn different_ecdsa_keys_produce_different_ed25519_keys() {
let key_a = {
let mut k = [0u8; 32];
k[0] = 0x01;
k
};
let key_b = {
let mut k = [0u8; 32];
k[0] = 0x02;
k
};
let sk_a = derive_ed25519_from_ecdsa(&key_a).expect("derivation a failed");
let sk_b = derive_ed25519_from_ecdsa(&key_b).expect("derivation b failed");
assert_ne!(
sk_a.to_bytes(),
sk_b.to_bytes(),
"different ECDSA keys must produce different Ed25519 keys"
);
}
#[test]
fn sign_verify_roundtrip() {
let ecdsa_key = test_ecdsa_key();
let sk = derive_ed25519_from_ecdsa(&ecdsa_key).expect("derivation failed");
let vk = sk.verifying_key();
let data = b"newton privacy test message";
let signature = sign_data_ref(&sk, data);
assert_eq!(signature.len(), 64, "Ed25519 signature must be 64 bytes");
verify_data_ref(&vk, data, &signature).expect("verification should succeed");
}
#[test]
fn verify_rejects_wrong_data() {
let ecdsa_key = test_ecdsa_key();
let sk = derive_ed25519_from_ecdsa(&ecdsa_key).expect("derivation failed");
let vk = sk.verifying_key();
let signature = sign_data_ref(&sk, b"correct data");
let result = verify_data_ref(&vk, b"wrong data", &signature);
assert!(result.is_err(), "verification with wrong data should fail");
}
#[test]
fn verify_rejects_invalid_signature_bytes() {
let ecdsa_key = test_ecdsa_key();
let sk = derive_ed25519_from_ecdsa(&ecdsa_key).expect("derivation failed");
let vk = sk.verifying_key();
let result = verify_data_ref(&vk, b"data", &[0u8; 10]);
assert!(result.is_err(), "verification with truncated signature should fail");
}
#[test]
fn x25519_conversion_consistency() {
let ecdsa_key = test_ecdsa_key();
let sk = derive_ed25519_from_ecdsa(&ecdsa_key).expect("derivation failed");
let x25519_priv = ed25519_to_x25519_private(&sk).expect("x25519 private conversion failed");
assert_eq!(x25519_priv.len(), 32, "X25519 private key must be 32 bytes");
let x25519_pub = ed25519_to_x25519_public(&sk).expect("x25519 public conversion failed");
assert_eq!(x25519_pub.len(), 32, "X25519 public key must be 32 bytes");
let secret_array: [u8; 32] = x25519_priv.as_slice().try_into().unwrap();
let expected_pub = X25519PublicKey::from(&X25519StaticSecret::from(secret_array));
assert_eq!(
x25519_pub,
expected_pub.as_bytes().to_vec(),
"X25519 public key must correspond to the derived private key"
);
}
#[test]
fn x25519_keys_work_with_hpke() {
let ecdsa_key = test_ecdsa_key();
let sk = derive_ed25519_from_ecdsa(&ecdsa_key).expect("derivation failed");
let x25519_priv = ed25519_to_x25519_private(&sk).expect("x25519 priv failed");
let x25519_pub = ed25519_to_x25519_public(&sk).expect("x25519 pub failed");
let hpke_sk = super::super::hpke::HpkePrivateKey::from_bytes(&x25519_priv).expect("hpke sk failed");
let hpke_pk = super::super::hpke::HpkePublicKey::from_bytes(&x25519_pub).expect("hpke pk failed");
let plaintext = b"cross-module integration test";
let aad = b"test-aad";
let (enc, ct) = super::super::hpke::encrypt(&hpke_pk, plaintext, aad).expect("encrypt failed");
let recovered = super::super::hpke::decrypt(&hpke_sk, &enc, &ct, aad).expect("decrypt failed");
assert_eq!(&*recovered, plaintext);
}
#[test]
fn signed_read_derivation_is_deterministic() {
let ecdsa_key = test_ecdsa_key();
let sk1 = derive_ed25519_signed_read(&ecdsa_key).expect("derivation 1 failed");
let sk2 = derive_ed25519_signed_read(&ecdsa_key).expect("derivation 2 failed");
assert_eq!(
sk1.to_bytes(),
sk2.to_bytes(),
"same ECDSA key must produce same signed-read key"
);
}
#[test]
fn signed_read_different_from_privacy_derivation() {
let ecdsa_key = test_ecdsa_key();
let privacy_sk = derive_ed25519_from_ecdsa(&ecdsa_key).expect("privacy derivation failed");
let signed_read_sk = derive_ed25519_signed_read(&ecdsa_key).expect("signed-read derivation failed");
assert_ne!(
privacy_sk.to_bytes(),
signed_read_sk.to_bytes(),
"domain separation violated: privacy and signed-read derivations produced same key"
);
}
#[test]
fn signed_read_different_ecdsa_keys_produce_different_keys() {
let key_a = {
let mut k = [0u8; 32];
k[0] = 0x01;
k
};
let key_b = {
let mut k = [0u8; 32];
k[0] = 0x02;
k
};
let sk_a = derive_ed25519_signed_read(&key_a).expect("derivation a failed");
let sk_b = derive_ed25519_signed_read(&key_b).expect("derivation b failed");
assert_ne!(
sk_a.to_bytes(),
sk_b.to_bytes(),
"different ECDSA keys must produce different signed-read keys"
);
}
}