use ed25519_dalek::{SigningKey, VerifyingKey, Signer, Verifier, Signature as DalekSignature};
use sha2::{Sha256, Digest};
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Signature {
#[serde(with = "hex_bytes_64")]
pub sig: [u8; 64],
#[serde(with = "hex_bytes_32")]
pub fingerprint: [u8; 32],
pub timestamp: u32,
}
mod hex_bytes_64 {
use serde::{self, Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(data: &[u8; 64], s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(&hex::encode(data))
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u8; 64], D::Error> {
let s = String::deserialize(d)?;
let bytes = hex::decode(&s).map_err(serde::de::Error::custom)?;
let mut arr = [0u8; 64];
arr.copy_from_slice(&bytes);
Ok(arr)
}
}
mod hex_bytes_32 {
use serde::{self, Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(data: &[u8; 32], s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(&hex::encode(data))
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u8; 32], D::Error> {
let s = String::deserialize(d)?;
let bytes = hex::decode(&s).map_err(serde::de::Error::custom)?;
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
Ok(arr)
}
}
#[derive(Debug, thiserror::Error)]
pub enum SigningError {
#[error("invalid private key length: expected 32 bytes, got {0}")]
InvalidPrivateKey(usize),
#[error("invalid public key: {0}")]
InvalidPublicKey(String),
#[error("signature verification failed")]
VerificationFailed,
#[error("fingerprint mismatch — bytecode was tampered with")]
FingerprintMismatch,
}
impl Signature {
fn signed_message(fingerprint: &[u8; 32], timestamp: u32) -> [u8; 36] {
let mut msg = [0u8; 36];
msg[..32].copy_from_slice(fingerprint);
msg[32..].copy_from_slice(×tamp.to_le_bytes());
msg
}
}
pub fn fingerprint(bytecode: &[u8]) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(bytecode);
hasher.finalize().into()
}
pub fn sign_bytecode(bytecode: &[u8], private_key: &[u8; 32], timestamp: Option<u32>) -> Signature {
let signing_key = SigningKey::from_bytes(private_key);
let fp = fingerprint(bytecode);
let ts = timestamp.unwrap_or_else(|| {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("clock went backwards")
.as_secs() as u32
});
let msg = Signature::signed_message(&fp, ts);
let dalek_sig: DalekSignature = signing_key.sign(&msg);
let sig_bytes: [u8; 64] = dalek_sig.to_bytes();
Signature {
sig: sig_bytes,
fingerprint: fp,
timestamp: ts,
}
}
pub fn verify_bytecode(
bytecode: &[u8],
signature: &Signature,
public_key: &[u8; 32],
) -> Result<(), SigningError> {
let fp = fingerprint(bytecode);
if fp != signature.fingerprint {
return Err(SigningError::FingerprintMismatch);
}
let msg = Signature::signed_message(&signature.fingerprint, signature.timestamp);
let verifying_key = VerifyingKey::from_bytes(public_key)
.map_err(|e| SigningError::InvalidPublicKey(e.to_string()))?;
let dalek_sig = DalekSignature::from_bytes(&signature.sig);
verifying_key
.verify(&msg, &dalek_sig)
.map_err(|_| SigningError::VerificationFailed)?;
Ok(())
}
#[cfg(test)]
mod unit {
use super::*;
fn random_keypair() -> ([u8; 32], [u8; 32]) {
let signing_key = SigningKey::generate(&mut rand::rngs::OsRng);
let public_key = signing_key.verifying_key().to_bytes();
let private_key = signing_key.to_bytes();
(private_key, public_key)
}
#[test]
fn sign_and_verify_roundtrip() {
let (sk, pk) = random_keypair();
let bytecode = b"LOAD x 42.0; ASSERT_GT x 0;";
let sig = sign_bytecode(bytecode, &sk, Some(12345));
assert!(verify_bytecode(bytecode, &sig, &pk).is_ok());
}
#[test]
fn reject_tampered_bytecode() {
let (sk, pk) = random_keypair();
let bytecode = b"LOAD x 42.0; ASSERT_GT x 0;";
let sig = sign_bytecode(bytecode, &sk, Some(12345));
let mut tampered = bytecode.to_vec();
tampered[5] ^= 0xFF; assert!(verify_bytecode(&tampered, &sig, &pk).is_err());
}
#[test]
fn reject_wrong_public_key() {
let (sk, _) = random_keypair();
let (_, wrong_pk) = random_keypair();
let bytecode = b"LOAD x 42.0;";
let sig = sign_bytecode(bytecode, &sk, Some(12345));
assert!(verify_bytecode(bytecode, &sig, &wrong_pk).is_err());
}
}