use crate::{AionError, Result};
use ed25519_dalek::{Signature as Ed25519Signature, Signer, Verifier};
use rand::RngCore;
use zeroize::Zeroizing;
pub use ed25519_dalek::{SigningKey as Ed25519SigningKey, VerifyingKey as Ed25519VerifyingKey};
#[derive(Clone)]
pub struct SigningKey(Zeroizing<[u8; 32]>);
impl SigningKey {
#[must_use]
pub fn generate() -> Self {
let key = Ed25519SigningKey::generate(&mut rand::rngs::OsRng);
Self(Zeroizing::new(key.to_bytes()))
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() != 32 {
return Err(AionError::InvalidPrivateKey {
reason: format!("expected 32 bytes, got {}", bytes.len()),
});
}
let mut key_bytes = [0u8; 32];
key_bytes.copy_from_slice(bytes);
let _validate = Ed25519SigningKey::from_bytes(&key_bytes);
Ok(Self(Zeroizing::new(key_bytes)))
}
#[must_use]
pub fn to_bytes(&self) -> &[u8; 32] {
&self.0
}
#[must_use]
pub fn sign(&self, message: &[u8]) -> [u8; 64] {
let signing_key = Ed25519SigningKey::from_bytes(&self.0);
signing_key.sign(message).to_bytes()
}
#[must_use]
pub fn verifying_key(&self) -> VerifyingKey {
let signing_key = Ed25519SigningKey::from_bytes(&self.0);
VerifyingKey(signing_key.verifying_key())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VerifyingKey(Ed25519VerifyingKey);
impl VerifyingKey {
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() != 32 {
return Err(AionError::InvalidPublicKey {
reason: format!("expected 32 bytes, got {}", bytes.len()),
});
}
let mut key_bytes = [0u8; 32];
key_bytes.copy_from_slice(bytes);
let key = Ed25519VerifyingKey::from_bytes(&key_bytes).map_err(|e| {
AionError::InvalidPublicKey {
reason: e.to_string(),
}
})?;
Ok(Self(key))
}
#[must_use]
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}
pub fn verify(&self, message: &[u8], signature: &[u8; 64]) -> Result<()> {
let sig = Ed25519Signature::from_bytes(signature);
self.0
.verify(message, &sig)
.map_err(|_| AionError::InvalidSignature {
reason: "signature verification failed".to_string(),
})
}
}
#[must_use]
pub fn hash(data: &[u8]) -> [u8; 32] {
blake3::hash(data).into()
}
#[must_use]
pub fn keyed_hash(key: &[u8; 32], data: &[u8]) -> [u8; 32] {
blake3::keyed_hash(key, data).into()
}
pub fn derive_key(ikm: &[u8], salt: &[u8], info: &[u8], output: &mut [u8]) -> Result<()> {
use hkdf::Hkdf;
use sha2::Sha256;
let hk = Hkdf::<Sha256>::new(Some(salt), ikm);
hk.expand(info, output)
.map_err(|_| AionError::InvalidPrivateKey {
reason: "HKDF expand failed".to_string(),
})?;
Ok(())
}
pub fn encrypt(key: &[u8; 32], nonce: &[u8; 12], plaintext: &[u8], aad: &[u8]) -> Result<Vec<u8>> {
use chacha20poly1305::{
aead::{Aead, KeyInit, Payload},
ChaCha20Poly1305,
};
let cipher = ChaCha20Poly1305::new(key.into());
let payload = Payload {
msg: plaintext,
aad,
};
cipher
.encrypt(nonce.into(), payload)
.map_err(|e| AionError::EncryptionFailed {
reason: e.to_string(),
})
}
pub fn decrypt(key: &[u8; 32], nonce: &[u8; 12], ciphertext: &[u8], aad: &[u8]) -> Result<Vec<u8>> {
use chacha20poly1305::{
aead::{Aead, KeyInit, Payload},
ChaCha20Poly1305,
};
let cipher = ChaCha20Poly1305::new(key.into());
let payload = Payload {
msg: ciphertext,
aad,
};
cipher
.decrypt(nonce.into(), payload)
.map_err(|e| AionError::DecryptionFailed {
reason: e.to_string(),
})
}
#[must_use]
pub fn generate_nonce() -> [u8; 12] {
let mut nonce = [0u8; 12];
rand::rngs::OsRng.fill_bytes(&mut nonce);
nonce
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use super::*;
mod signing {
use super::*;
#[test]
fn should_generate_signing_key() {
let key = SigningKey::generate();
let bytes = key.to_bytes();
assert_eq!(bytes.len(), 32);
}
#[test]
fn should_create_signing_key_from_bytes() {
let original = SigningKey::generate();
let bytes = *original.to_bytes();
let restored = SigningKey::from_bytes(&bytes).unwrap();
assert_eq!(*original.to_bytes(), *restored.to_bytes());
}
#[test]
fn should_reject_invalid_key_length() {
let result = SigningKey::from_bytes(&[0u8; 16]);
assert!(result.is_err());
}
#[test]
fn should_sign_message() {
let key = SigningKey::generate();
let signature = key.sign(b"test message");
assert_eq!(signature.len(), 64);
}
#[test]
fn should_verify_valid_signature() {
let key = SigningKey::generate();
let message = b"test message";
let signature = key.sign(message);
let verifying_key = key.verifying_key();
assert!(verifying_key.verify(message, &signature).is_ok());
}
#[test]
fn should_reject_invalid_signature() {
let key = SigningKey::generate();
let message = b"test message";
let mut signature = key.sign(message);
signature[0] ^= 1;
let verifying_key = key.verifying_key();
assert!(verifying_key.verify(message, &signature).is_err());
}
#[test]
fn should_reject_wrong_message() {
let key = SigningKey::generate();
let signature = key.sign(b"original message");
let verifying_key = key.verifying_key();
assert!(verifying_key
.verify(b"different message", &signature)
.is_err());
}
#[test]
fn should_serialize_verifying_key() {
let key = SigningKey::generate();
let verifying_key = key.verifying_key();
let bytes = verifying_key.to_bytes();
assert_eq!(bytes.len(), 32);
let restored = VerifyingKey::from_bytes(&bytes).unwrap();
assert_eq!(verifying_key.to_bytes(), restored.to_bytes());
}
}
mod hashing {
use super::*;
#[test]
fn should_hash_data() {
let hash1 = hash(b"test data");
assert_eq!(hash1.len(), 32);
let hash2 = hash(b"test data");
assert_eq!(hash1, hash2);
}
#[test]
fn should_produce_different_hashes_for_different_data() {
let hash1 = hash(b"data1");
let hash2 = hash(b"data2");
assert_ne!(hash1, hash2);
}
#[test]
fn should_create_keyed_hash() {
let key = [0u8; 32];
let mac = keyed_hash(&key, b"message");
assert_eq!(mac.len(), 32);
}
#[test]
fn should_produce_different_macs_with_different_keys() {
let key1 = [0u8; 32];
let key2 = [1u8; 32];
let mac1 = keyed_hash(&key1, b"message");
let mac2 = keyed_hash(&key2, b"message");
assert_ne!(mac1, mac2);
}
}
mod key_derivation {
use super::*;
#[test]
fn should_derive_key() {
let ikm = b"input key material";
let salt = b"salt";
let info = b"context";
let mut output = [0u8; 32];
derive_key(ikm, salt, info, &mut output).unwrap();
assert_ne!(output, [0u8; 32]);
}
#[test]
fn should_produce_deterministic_output() {
let ikm = b"input key material";
let salt = b"salt";
let info = b"context";
let mut output1 = [0u8; 32];
derive_key(ikm, salt, info, &mut output1).unwrap();
let mut output2 = [0u8; 32];
derive_key(ikm, salt, info, &mut output2).unwrap();
assert_eq!(output1, output2);
}
#[test]
fn should_produce_different_keys_for_different_info() {
let ikm = b"input key material";
let salt = b"salt";
let mut output1 = [0u8; 32];
derive_key(ikm, salt, b"context1", &mut output1).unwrap();
let mut output2 = [0u8; 32];
derive_key(ikm, salt, b"context2", &mut output2).unwrap();
assert_ne!(output1, output2);
}
}
mod encryption {
use super::*;
#[test]
fn should_encrypt_and_decrypt() {
let key = [0u8; 32];
let nonce = generate_nonce();
let plaintext = b"secret message";
let aad = b"additional data";
let ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap();
assert_eq!(ciphertext.len(), plaintext.len() + 16);
let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn should_reject_tampered_ciphertext() {
let key = [0u8; 32];
let nonce = generate_nonce();
let plaintext = b"secret message";
let aad = b"additional data";
let mut ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap();
if let Some(byte) = ciphertext.get_mut(0) {
*byte ^= 1;
}
let result = decrypt(&key, &nonce, &ciphertext, aad);
assert!(result.is_err());
}
#[test]
fn should_reject_wrong_aad() {
let key = [0u8; 32];
let nonce = generate_nonce();
let plaintext = b"secret message";
let ciphertext = encrypt(&key, &nonce, plaintext, b"aad1").unwrap();
let result = decrypt(&key, &nonce, &ciphertext, b"aad2");
assert!(result.is_err());
}
#[test]
fn should_reject_wrong_key() {
let key1 = [0u8; 32];
let key2 = [1u8; 32];
let nonce = generate_nonce();
let plaintext = b"secret message";
let aad = b"additional data";
let ciphertext = encrypt(&key1, &nonce, plaintext, aad).unwrap();
let result = decrypt(&key2, &nonce, &ciphertext, aad);
assert!(result.is_err());
}
#[test]
fn should_generate_unique_nonces() {
let nonce1 = generate_nonce();
let nonce2 = generate_nonce();
assert_ne!(nonce1, nonce2);
}
}
mod properties {
use super::*;
use hegel::generators as gs;
#[hegel::test]
fn prop_sign_verify_roundtrip(tc: hegel::TestCase) {
let message = tc.draw(gs::binary().max_size(4096));
let key = SigningKey::generate();
let signature = key.sign(&message);
let verifying_key = key.verifying_key();
assert!(verifying_key.verify(&message, &signature).is_ok());
}
#[hegel::test]
fn prop_verify_rejects_wrong_key(tc: hegel::TestCase) {
let message = tc.draw(gs::binary().max_size(4096));
let signer = SigningKey::generate();
let other = SigningKey::generate();
let signature = signer.sign(&message);
assert!(other.verifying_key().verify(&message, &signature).is_err());
}
#[hegel::test]
fn prop_verify_rejects_tampered_message(tc: hegel::TestCase) {
let mut message = tc.draw(gs::binary().min_size(1).max_size(4096));
let key = SigningKey::generate();
let signature = key.sign(&message);
let max = message
.len()
.checked_sub(1)
.unwrap_or_else(|| std::process::abort());
let flip_index = tc.draw(gs::integers::<usize>().max_value(max));
if let Some(byte) = message.get_mut(flip_index) {
*byte ^= 0x01;
}
assert!(key.verifying_key().verify(&message, &signature).is_err());
}
#[hegel::test]
fn prop_hash_is_deterministic(tc: hegel::TestCase) {
let data = tc.draw(gs::binary().max_size(8192));
assert_eq!(hash(&data), hash(&data));
}
#[hegel::test]
fn prop_verifying_key_roundtrip_verifies(tc: hegel::TestCase) {
let message = tc.draw(gs::binary().max_size(4096));
let signer = SigningKey::generate();
let original = signer.verifying_key();
let restored = VerifyingKey::from_bytes(&original.to_bytes())
.unwrap_or_else(|_| std::process::abort());
let signature = signer.sign(&message);
assert!(restored.verify(&message, &signature).is_ok());
}
}
}