use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use super::derive::DerivedKey;
use crate::error::JoyError;
pub struct IdentityKeypair {
signing_key: SigningKey,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublicKey(VerifyingKey);
impl IdentityKeypair {
pub fn from_derived_key(key: &DerivedKey) -> Self {
let signing_key = SigningKey::from_bytes(key.as_bytes());
Self { signing_key }
}
pub fn from_signing_key(key: SigningKey) -> Self {
Self { signing_key: key }
}
pub fn from_seed(seed: &[u8; 32]) -> Self {
let signing_key = SigningKey::from_bytes(seed);
Self { signing_key }
}
pub fn from_random() -> Self {
use rand::rngs::OsRng;
let signing_key = SigningKey::generate(&mut OsRng);
Self { signing_key }
}
pub fn from_token_seed(token: &str, project_id: &str) -> Self {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(token.as_bytes());
hasher.update(project_id.as_bytes());
let hash = hasher.finalize();
let mut seed = [0u8; 32];
seed.copy_from_slice(&hash);
Self::from_seed(&seed)
}
pub fn public_key(&self) -> PublicKey {
PublicKey(self.signing_key.verifying_key())
}
pub fn sign(&self, message: &[u8]) -> Vec<u8> {
let sig: Signature = self.signing_key.sign(message);
sig.to_bytes().to_vec()
}
}
impl PublicKey {
pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), JoyError> {
let sig = Signature::from_slice(signature)
.map_err(|e| JoyError::AuthFailed(format!("invalid signature: {e}")))?;
self.0
.verify(message, &sig)
.map_err(|_| JoyError::AuthFailed("signature verification failed".into()))
}
pub fn to_hex(&self) -> String {
hex::encode(self.0.as_bytes())
}
pub fn from_hex(s: &str) -> Result<Self, JoyError> {
let bytes =
hex::decode(s).map_err(|e| JoyError::AuthFailed(format!("invalid public key: {e}")))?;
let arr: [u8; 32] = bytes
.try_into()
.map_err(|_| JoyError::AuthFailed("public key must be 32 bytes".into()))?;
let key = VerifyingKey::from_bytes(&arr)
.map_err(|e| JoyError::AuthFailed(format!("invalid Ed25519 key: {e}")))?;
Ok(Self(key))
}
}
impl Drop for IdentityKeypair {
fn drop(&mut self) {
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::auth::derive;
const TEST_PASSPHRASE: &str = "correct horse battery staple extra words";
fn test_keypair() -> (IdentityKeypair, DerivedKey) {
let salt = derive::Salt::from_hex(
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
)
.unwrap();
let key = derive::derive_key(TEST_PASSPHRASE, &salt).unwrap();
let keypair = IdentityKeypair::from_derived_key(&key);
(keypair, key)
}
#[test]
fn keypair_deterministic() {
let salt = derive::Salt::from_hex(
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
)
.unwrap();
let k1 = derive::derive_key(TEST_PASSPHRASE, &salt).unwrap();
let k2 = derive::derive_key(TEST_PASSPHRASE, &salt).unwrap();
let kp1 = IdentityKeypair::from_derived_key(&k1);
let kp2 = IdentityKeypair::from_derived_key(&k2);
assert_eq!(kp1.public_key(), kp2.public_key());
}
#[test]
fn sign_verify_roundtrip() {
let (keypair, _) = test_keypair();
let message = b"hello world";
let signature = keypair.sign(message);
assert!(keypair.public_key().verify(message, &signature).is_ok());
}
#[test]
fn verify_wrong_message() {
let (keypair, _) = test_keypair();
let signature = keypair.sign(b"original");
assert!(keypair
.public_key()
.verify(b"tampered", &signature)
.is_err());
}
#[test]
fn verify_wrong_key() {
let (keypair, _) = test_keypair();
let signature = keypair.sign(b"hello");
let salt = derive::generate_salt();
let other_key =
derive::derive_key("alpha bravo charlie delta echo foxtrot", &salt).unwrap();
let other_kp = IdentityKeypair::from_derived_key(&other_key);
assert!(other_kp.public_key().verify(b"hello", &signature).is_err());
}
#[test]
fn public_key_hex_roundtrip() {
let (keypair, _) = test_keypair();
let pk = keypair.public_key();
let hex = pk.to_hex();
let parsed = PublicKey::from_hex(&hex).unwrap();
assert_eq!(pk, parsed);
}
}