Skip to main content

flux_verify_api/
signing.rs

1use ed25519_dalek::{SigningKey, VerifyingKey, Signer, Verifier, Signature as DalekSignature};
2use sha2::{Sha256, Digest};
3use serde::{Serialize, Deserialize};
4
5/// A signed bytecode blob: Ed25519 signature + SHA-256 fingerprint + timestamp.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Signature {
8    /// 64-byte Ed25519 signature over (fingerprint || timestamp_le_bytes), hex-encoded.
9    #[serde(with = "hex_bytes_64")]
10    pub sig: [u8; 64],
11    /// SHA-256 hash of the bytecode, hex-encoded.
12    #[serde(with = "hex_bytes_32")]
13    pub fingerprint: [u8; 32],
14    /// Unix timestamp (seconds) when the signature was created.
15    pub timestamp: u32,
16}
17
18mod hex_bytes_64 {
19    use serde::{self, Deserialize, Deserializer, Serializer};
20    pub fn serialize<S: Serializer>(data: &[u8; 64], s: S) -> Result<S::Ok, S::Error> {
21        s.serialize_str(&hex::encode(data))
22    }
23    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u8; 64], D::Error> {
24        let s = String::deserialize(d)?;
25        let bytes = hex::decode(&s).map_err(serde::de::Error::custom)?;
26        let mut arr = [0u8; 64];
27        arr.copy_from_slice(&bytes);
28        Ok(arr)
29    }
30}
31
32mod hex_bytes_32 {
33    use serde::{self, Deserialize, Deserializer, Serializer};
34    pub fn serialize<S: Serializer>(data: &[u8; 32], s: S) -> Result<S::Ok, S::Error> {
35        s.serialize_str(&hex::encode(data))
36    }
37    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u8; 32], D::Error> {
38        let s = String::deserialize(d)?;
39        let bytes = hex::decode(&s).map_err(serde::de::Error::custom)?;
40        let mut arr = [0u8; 32];
41        arr.copy_from_slice(&bytes);
42        Ok(arr)
43    }
44}
45
46/// Errors produced by signing / verification.
47#[derive(Debug, thiserror::Error)]
48pub enum SigningError {
49    #[error("invalid private key length: expected 32 bytes, got {0}")]
50    InvalidPrivateKey(usize),
51    #[error("invalid public key: {0}")]
52    InvalidPublicKey(String),
53    #[error("signature verification failed")]
54    VerificationFailed,
55    #[error("fingerprint mismatch — bytecode was tampered with")]
56    FingerprintMismatch,
57}
58
59impl Signature {
60    /// Return the message that gets signed: fingerprint || timestamp_le_bytes.
61    fn signed_message(fingerprint: &[u8; 32], timestamp: u32) -> [u8; 36] {
62        let mut msg = [0u8; 36];
63        msg[..32].copy_from_slice(fingerprint);
64        msg[32..].copy_from_slice(&timestamp.to_le_bytes());
65        msg
66    }
67}
68
69/// Compute SHA-256 fingerprint of arbitrary bytecode.
70pub fn fingerprint(bytecode: &[u8]) -> [u8; 32] {
71    let mut hasher = Sha256::new();
72    hasher.update(bytecode);
73    hasher.finalize().into()
74}
75
76/// Sign bytecode with an Ed25519 private key.
77///
78/// `private_key` must be exactly 32 bytes (seed material for `ed25519-dalek`).
79/// The signature covers the SHA-256 fingerprint of the bytecode concatenated
80/// with a 4-byte little-endian timestamp.
81pub fn sign_bytecode(bytecode: &[u8], private_key: &[u8; 32], timestamp: Option<u32>) -> Signature {
82    let signing_key = SigningKey::from_bytes(private_key);
83    let fp = fingerprint(bytecode);
84    let ts = timestamp.unwrap_or_else(|| {
85        std::time::SystemTime::now()
86            .duration_since(std::time::UNIX_EPOCH)
87            .expect("clock went backwards")
88            .as_secs() as u32
89    });
90    let msg = Signature::signed_message(&fp, ts);
91    let dalek_sig: DalekSignature = signing_key.sign(&msg);
92    let sig_bytes: [u8; 64] = dalek_sig.to_bytes();
93
94    Signature {
95        sig: sig_bytes,
96        fingerprint: fp,
97        timestamp: ts,
98    }
99}
100
101/// Verify a bytecode signature against a trusted Ed25519 public key.
102///
103/// Returns `Ok(())` on success, or an error describing why verification failed.
104pub fn verify_bytecode(
105    bytecode: &[u8],
106    signature: &Signature,
107    public_key: &[u8; 32],
108) -> Result<(), SigningError> {
109    // 1. Re-derive fingerprint and check it matches what was signed.
110    let fp = fingerprint(bytecode);
111    if fp != signature.fingerprint {
112        return Err(SigningError::FingerprintMismatch);
113    }
114
115    // 2. Reconstruct the signed message.
116    let msg = Signature::signed_message(&signature.fingerprint, signature.timestamp);
117
118    // 3. Verify Ed25519 signature.
119    let verifying_key = VerifyingKey::from_bytes(public_key)
120        .map_err(|e| SigningError::InvalidPublicKey(e.to_string()))?;
121    let dalek_sig = DalekSignature::from_bytes(&signature.sig);
122    verifying_key
123        .verify(&msg, &dalek_sig)
124        .map_err(|_| SigningError::VerificationFailed)?;
125
126    Ok(())
127}
128
129#[cfg(test)]
130mod unit {
131    use super::*;
132
133    fn random_keypair() -> ([u8; 32], [u8; 32]) {
134        let signing_key = SigningKey::generate(&mut rand::rngs::OsRng);
135        let public_key = signing_key.verifying_key().to_bytes();
136        let private_key = signing_key.to_bytes();
137        (private_key, public_key)
138    }
139
140    #[test]
141    fn sign_and_verify_roundtrip() {
142        let (sk, pk) = random_keypair();
143        let bytecode = b"LOAD x 42.0; ASSERT_GT x 0;";
144        let sig = sign_bytecode(bytecode, &sk, Some(12345));
145        assert!(verify_bytecode(bytecode, &sig, &pk).is_ok());
146    }
147
148    #[test]
149    fn reject_tampered_bytecode() {
150        let (sk, pk) = random_keypair();
151        let bytecode = b"LOAD x 42.0; ASSERT_GT x 0;";
152        let sig = sign_bytecode(bytecode, &sk, Some(12345));
153        let mut tampered = bytecode.to_vec();
154        tampered[5] ^= 0xFF; // flip a byte
155        assert!(verify_bytecode(&tampered, &sig, &pk).is_err());
156    }
157
158    #[test]
159    fn reject_wrong_public_key() {
160        let (sk, _) = random_keypair();
161        let (_, wrong_pk) = random_keypair();
162        let bytecode = b"LOAD x 42.0;";
163        let sig = sign_bytecode(bytecode, &sk, Some(12345));
164        assert!(verify_bytecode(bytecode, &sig, &wrong_pk).is_err());
165    }
166}