Skip to main content

hush_core/
signing.rs

1//! Ed25519 signing and verification
2
3use ed25519_dalek::{
4    Signature as DalekSignature, Signer as DalekSigner, SigningKey, Verifier, VerifyingKey,
5};
6use rand_core::OsRng;
7use serde::{Deserialize, Serialize};
8use zeroize::Zeroize;
9
10use crate::error::{Error, Result};
11
12/// Signing interface used by hush-core (e.g., receipts).
13///
14/// This intentionally mirrors the minimal surface we need: public key export and message signing.
15/// Implementations may keep keys in-memory (`Keypair`) or unseal on demand (TPM-backed).
16pub trait Signer {
17    fn public_key(&self) -> PublicKey;
18    fn sign(&self, message: &[u8]) -> Result<Signature>;
19}
20
21/// Ed25519 keypair for signing
22#[derive(Clone)]
23pub struct Keypair {
24    signing_key: SigningKey,
25}
26
27impl Keypair {
28    /// Generate a new random keypair.
29    ///
30    /// # Examples
31    ///
32    /// ```rust
33    /// use hush_core::Keypair;
34    ///
35    /// let keypair = Keypair::generate();
36    /// let pubkey = keypair.public_key();
37    /// assert_eq!(pubkey.as_bytes().len(), 32);
38    /// ```
39    pub fn generate() -> Self {
40        let signing_key = SigningKey::generate(&mut OsRng);
41        Self { signing_key }
42    }
43
44    /// Create from raw seed bytes (32 bytes)
45    pub fn from_seed(seed: &[u8; 32]) -> Self {
46        let signing_key = SigningKey::from_bytes(seed);
47        Self { signing_key }
48    }
49
50    /// Create from hex-encoded seed
51    pub fn from_hex(hex_seed: &str) -> Result<Self> {
52        let hex_seed = hex_seed.strip_prefix("0x").unwrap_or(hex_seed);
53        let bytes = hex::decode(hex_seed).map_err(|e| Error::InvalidHex(e.to_string()))?;
54
55        if bytes.len() != 32 {
56            return Err(Error::InvalidPrivateKey);
57        }
58
59        let mut seed = [0u8; 32];
60        seed.copy_from_slice(&bytes);
61        Ok(Self::from_seed(&seed))
62    }
63
64    /// Get the public key
65    pub fn public_key(&self) -> PublicKey {
66        PublicKey {
67            verifying_key: self.signing_key.verifying_key(),
68        }
69    }
70
71    /// Sign a message.
72    ///
73    /// # Examples
74    ///
75    /// ```rust
76    /// use hush_core::Keypair;
77    ///
78    /// let keypair = Keypair::generate();
79    /// let signature = keypair.sign(b"hello");
80    /// assert_eq!(signature.to_bytes().len(), 64);
81    /// ```
82    pub fn sign(&self, message: &[u8]) -> Signature {
83        let sig = self.signing_key.sign(message);
84        Signature { inner: sig }
85    }
86
87    /// Export seed as hex
88    pub fn to_hex(&self) -> String {
89        hex::encode(self.signing_key.to_bytes())
90    }
91}
92
93impl Signer for Keypair {
94    fn public_key(&self) -> PublicKey {
95        Keypair::public_key(self)
96    }
97
98    fn sign(&self, message: &[u8]) -> Result<Signature> {
99        Ok(Keypair::sign(self, message))
100    }
101}
102
103impl Drop for Keypair {
104    fn drop(&mut self) {
105        // Zero the seed bytes (private key material) on drop.
106        // SigningKey stores the seed as [u8; 32] internally.
107        let mut seed = self.signing_key.to_bytes();
108        seed.zeroize();
109        // Overwrite the signing key with a key derived from the zeroed seed.
110        self.signing_key = SigningKey::from_bytes(&seed);
111    }
112}
113
114/// Ed25519 public key for verification
115#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
116#[serde(transparent)]
117pub struct PublicKey {
118    #[serde(with = "pubkey_serde")]
119    verifying_key: VerifyingKey,
120}
121
122mod pubkey_serde {
123    use super::*;
124    use serde::{Deserializer, Serializer};
125
126    pub fn serialize<S>(key: &VerifyingKey, s: S) -> std::result::Result<S::Ok, S::Error>
127    where
128        S: Serializer,
129    {
130        s.serialize_str(&hex::encode(key.to_bytes()))
131    }
132
133    pub fn deserialize<'de, D>(d: D) -> std::result::Result<VerifyingKey, D::Error>
134    where
135        D: Deserializer<'de>,
136    {
137        let hex_str = String::deserialize(d)?;
138        let hex_str = hex_str.strip_prefix("0x").unwrap_or(&hex_str);
139        let bytes = hex::decode(hex_str).map_err(serde::de::Error::custom)?;
140        let bytes: [u8; 32] = bytes
141            .try_into()
142            .map_err(|_| serde::de::Error::custom("public key must be 32 bytes"))?;
143        VerifyingKey::from_bytes(&bytes).map_err(serde::de::Error::custom)
144    }
145}
146
147impl PublicKey {
148    /// Create from raw bytes (32 bytes)
149    pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self> {
150        let verifying_key =
151            VerifyingKey::from_bytes(bytes).map_err(|e| Error::InvalidPublicKey(e.to_string()))?;
152        Ok(Self { verifying_key })
153    }
154
155    /// Create from hex-encoded bytes
156    pub fn from_hex(hex_str: &str) -> Result<Self> {
157        let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
158        let bytes = hex::decode(hex_str).map_err(|e| Error::InvalidHex(e.to_string()))?;
159
160        if bytes.len() != 32 {
161            return Err(Error::InvalidPublicKey(format!(
162                "expected 32 bytes, got {}",
163                bytes.len()
164            )));
165        }
166
167        let mut arr = [0u8; 32];
168        arr.copy_from_slice(&bytes);
169        Self::from_bytes(&arr)
170    }
171
172    /// Verify a signature.
173    ///
174    /// # Examples
175    ///
176    /// ```rust
177    /// use hush_core::Keypair;
178    ///
179    /// let keypair = Keypair::generate();
180    /// let message = b"hello";
181    /// let signature = keypair.sign(message);
182    ///
183    /// assert!(keypair.public_key().verify(message, &signature));
184    /// assert!(!keypair.public_key().verify(b"wrong", &signature));
185    /// ```
186    pub fn verify(&self, message: &[u8], signature: &Signature) -> bool {
187        self.verifying_key.verify(message, &signature.inner).is_ok()
188    }
189
190    /// Export as hex
191    pub fn to_hex(&self) -> String {
192        hex::encode(self.verifying_key.to_bytes())
193    }
194
195    /// Export as 0x-prefixed hex
196    pub fn to_hex_prefixed(&self) -> String {
197        format!("0x{}", self.to_hex())
198    }
199
200    /// Get raw bytes
201    pub fn as_bytes(&self) -> &[u8; 32] {
202        self.verifying_key.as_bytes()
203    }
204}
205
206/// Ed25519 signature
207#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
208#[serde(transparent)]
209pub struct Signature {
210    #[serde(with = "sig_serde")]
211    inner: DalekSignature,
212}
213
214mod sig_serde {
215    use super::*;
216    use serde::{Deserializer, Serializer};
217
218    pub fn serialize<S>(sig: &DalekSignature, s: S) -> std::result::Result<S::Ok, S::Error>
219    where
220        S: Serializer,
221    {
222        s.serialize_str(&hex::encode(sig.to_bytes()))
223    }
224
225    pub fn deserialize<'de, D>(d: D) -> std::result::Result<DalekSignature, D::Error>
226    where
227        D: Deserializer<'de>,
228    {
229        let hex_str = String::deserialize(d)?;
230        let hex_str = hex_str.strip_prefix("0x").unwrap_or(&hex_str);
231        let bytes = hex::decode(hex_str).map_err(serde::de::Error::custom)?;
232        let bytes: [u8; 64] = bytes
233            .try_into()
234            .map_err(|_| serde::de::Error::custom("signature must be 64 bytes"))?;
235        Ok(DalekSignature::from_bytes(&bytes))
236    }
237}
238
239impl Signature {
240    /// Create from raw bytes (64 bytes)
241    pub fn from_bytes(bytes: &[u8; 64]) -> Self {
242        Self {
243            inner: DalekSignature::from_bytes(bytes),
244        }
245    }
246
247    /// Create from hex-encoded bytes
248    pub fn from_hex(hex_str: &str) -> Result<Self> {
249        let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
250        let bytes = hex::decode(hex_str).map_err(|e| Error::InvalidHex(e.to_string()))?;
251
252        if bytes.len() != 64 {
253            return Err(Error::InvalidSignature);
254        }
255
256        let mut arr = [0u8; 64];
257        arr.copy_from_slice(&bytes);
258        Ok(Self::from_bytes(&arr))
259    }
260
261    /// Export as hex
262    pub fn to_hex(&self) -> String {
263        hex::encode(self.inner.to_bytes())
264    }
265
266    /// Export as 0x-prefixed hex
267    pub fn to_hex_prefixed(&self) -> String {
268        format!("0x{}", self.to_hex())
269    }
270
271    /// Get raw bytes
272    pub fn to_bytes(&self) -> [u8; 64] {
273        self.inner.to_bytes()
274    }
275}
276
277/// Verify a signature (convenience function)
278pub fn verify_signature(public_key: &PublicKey, message: &[u8], signature: &Signature) -> bool {
279    public_key.verify(message, signature)
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285
286    #[test]
287    fn test_sign_verify() {
288        let keypair = Keypair::generate();
289        let message = b"Hello, Clawdstrike!";
290
291        let signature = keypair.sign(message);
292        assert!(keypair.public_key().verify(message, &signature));
293    }
294
295    #[test]
296    fn test_sign_verify_wrong_message() {
297        let keypair = Keypair::generate();
298        let signature = keypair.sign(b"Hello, Clawdstrike!");
299        assert!(!keypair.public_key().verify(b"Wrong message", &signature));
300    }
301
302    #[test]
303    fn test_keypair_from_seed() {
304        let seed = [42u8; 32];
305        let kp1 = Keypair::from_seed(&seed);
306        let kp2 = Keypair::from_seed(&seed);
307
308        assert_eq!(kp1.public_key().to_hex(), kp2.public_key().to_hex());
309    }
310
311    #[test]
312    fn test_hex_roundtrip() {
313        let keypair = Keypair::generate();
314        let pubkey_hex = keypair.public_key().to_hex();
315        let restored = PublicKey::from_hex(&pubkey_hex).unwrap();
316
317        assert_eq!(keypair.public_key(), restored);
318    }
319
320    #[test]
321    fn test_signature_hex_roundtrip() {
322        let keypair = Keypair::generate();
323        let signature = keypair.sign(b"test");
324        let sig_hex = signature.to_hex();
325        let restored = Signature::from_hex(&sig_hex).unwrap();
326
327        assert_eq!(signature.to_bytes(), restored.to_bytes());
328    }
329
330    #[test]
331    fn test_serde_roundtrip() {
332        let keypair = Keypair::generate();
333        let pubkey = keypair.public_key();
334        let signature = keypair.sign(b"test");
335
336        let pubkey_json = serde_json::to_string(&pubkey).unwrap();
337        let sig_json = serde_json::to_string(&signature).unwrap();
338
339        let pubkey_restored: PublicKey = serde_json::from_str(&pubkey_json).unwrap();
340        let sig_restored: Signature = serde_json::from_str(&sig_json).unwrap();
341
342        assert_eq!(pubkey, pubkey_restored);
343        assert!(pubkey.verify(b"test", &sig_restored));
344    }
345}