#[cfg(feature = "crypto")]
use aes_gcm::{
aead::{Aead, KeyInit},
Aes256Gcm, Nonce,
};
#[cfg(feature = "crypto")]
use ed25519_dalek::{SigningKey, VerifyingKey};
#[cfg(feature = "crypto")]
use x25519_dalek::{PublicKey, StaticSecret};
#[cfg(feature = "crypto")]
use crate::{parse, VsfType};
#[cfg(feature = "crypto")]
use std::fs;
#[cfg(feature = "crypto")]
use std::path::Path;
#[cfg(feature = "crypto")]
#[derive(Clone)]
pub struct Keypair {
pub ed25519_secret: SigningKey,
pub ed25519_public: VerifyingKey,
pub x25519_secret: StaticSecret,
pub x25519_public: PublicKey,
}
#[cfg(feature = "crypto")]
impl Keypair {
pub fn load_from_vsf(path: impl AsRef<Path>) -> Result<Self, String> {
let bytes =
fs::read(path.as_ref()).map_err(|e| format!("Failed to read key file: {}", e))?;
if bytes.len() < 4 || &bytes[0..4] != b"R\xC3\x85<" {
return Err("Invalid VSF magic number".to_string());
}
let (_, header_bytes_consumed) = crate::file_format::VsfHeader::decode(&bytes)
.map_err(|e| format!("Failed to parse VSF header: {}", e))?;
let mut ptr = header_bytes_consumed;
while ptr < bytes.len() && bytes[ptr] != b'[' {
ptr += 1;
}
if ptr >= bytes.len() {
return Err("No section found".to_string());
}
ptr += 1;
let _section_name =
parse(&bytes, &mut ptr).map_err(|e| format!("Parse section name: {}", e))?;
use std::collections::HashMap;
let mut fields: HashMap<String, VsfType> = HashMap::new();
while ptr < bytes.len() && bytes[ptr] != b']' {
if bytes[ptr] == b'(' {
ptr += 1;
let field_name = match parse(&bytes, &mut ptr)
.map_err(|e| format!("Parse field name: {}", e))?
{
VsfType::d(name) => name,
_ => return Err("Expected field name".to_string()),
};
if ptr < bytes.len() && bytes[ptr] == b':' {
ptr += 1;
let value =
parse(&bytes, &mut ptr).map_err(|e| format!("Parse field value: {}", e))?;
fields.insert(field_name, value);
}
if ptr < bytes.len() && bytes[ptr] == b')' {
ptr += 1;
}
} else {
ptr += 1;
}
}
let secret_bytes = match fields.get("secret") {
Some(VsfType::ke(bytes)) => {
if bytes.len() != 32 {
return Err("Secret key must be 32 bytes".to_string());
}
bytes.clone()
}
_ => return Err("Secret key not found".to_string()),
};
let public_bytes = match fields.get("public") {
Some(VsfType::ke(bytes)) => {
if bytes.len() != 32 {
return Err("Public key must be 32 bytes".to_string());
}
bytes.clone()
}
_ => return Err("Public key not found".to_string()),
};
let ed25519_secret = SigningKey::from_bytes(
&secret_bytes
.try_into()
.map_err(|_| "Invalid secret key length".to_string())?,
);
let ed25519_public = VerifyingKey::from_bytes(
&public_bytes
.try_into()
.map_err(|_| "Invalid public key length".to_string())?,
)
.map_err(|e| format!("Invalid public key: {}", e))?;
let x25519_secret = StaticSecret::from(ed25519_secret.to_bytes());
let x25519_public = PublicKey::from(&x25519_secret);
Ok(Self {
ed25519_secret,
ed25519_public,
x25519_secret,
x25519_public,
})
}
}
#[cfg(feature = "crypto")]
pub fn decrypt_ve(encrypted_bytes: &[u8], x25519_secret: &StaticSecret) -> Result<Vec<u8>, String> {
if encrypted_bytes.len() < 32 + 12 + 16 {
return Err(format!(
"Encrypted data too short: {} bytes (need at least 60)",
encrypted_bytes.len()
));
}
let ephemeral_pubkey_bytes: [u8; 32] = encrypted_bytes[0..32]
.try_into()
.map_err(|_| "Failed to extract ephemeral pubkey".to_string())?;
let ephemeral_pubkey = PublicKey::from(ephemeral_pubkey_bytes);
let nonce_bytes: [u8; 12] = encrypted_bytes[32..44]
.try_into()
.map_err(|_| "Failed to extract nonce".to_string())?;
let nonce = Nonce::from(nonce_bytes);
let ciphertext = &encrypted_bytes[44..];
let shared_secret = x25519_secret.diffie_hellman(&ephemeral_pubkey);
let cipher = Aes256Gcm::new(shared_secret.as_bytes().into());
let plaintext = cipher
.decrypt(&nonce, ciphertext)
.map_err(|e| format!("Decryption failed: {}", e))?;
Ok(plaintext)
}
#[cfg(test)]
#[cfg(feature = "crypto")]
mod tests {
use super::*;
#[test]
fn test_decrypt_roundtrip() {
use aes_gcm::aead::OsRng;
use x25519_dalek::EphemeralSecret;
let recipient_secret = StaticSecret::random_from_rng(OsRng);
let recipient_public = PublicKey::from(&recipient_secret);
let plaintext = b"Hello, VSF encryption!";
let ephemeral_secret = EphemeralSecret::random_from_rng(OsRng);
let ephemeral_public = PublicKey::from(&ephemeral_secret);
let shared_secret = ephemeral_secret.diffie_hellman(&recipient_public);
let cipher = Aes256Gcm::new(shared_secret.as_bytes().into());
let mut nonce_bytes = [0u8; 12];
rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut nonce_bytes);
let nonce = Nonce::from(nonce_bytes);
let ciphertext = cipher.encrypt(&nonce, plaintext.as_ref()).unwrap();
let mut encrypted_blob = Vec::new();
encrypted_blob.extend_from_slice(ephemeral_public.as_bytes());
encrypted_blob.extend_from_slice(&nonce_bytes);
encrypted_blob.extend_from_slice(&ciphertext);
let decrypted = decrypt_ve(&encrypted_blob, &recipient_secret).unwrap();
assert_eq!(decrypted, plaintext);
}
}