darkbio_crypto/eddsa/
mod.rs

1// crypto-rs: cryptography primitives and wrappers
2// Copyright 2025 Dark Bio AG. All rights reserved.
3//
4// Use of this source code is governed by a BSD-style
5// license that can be found in the LICENSE file.
6
7//! EdDSA cryptography wrappers and parametrization.
8//!
9//! https://datatracker.ietf.org/doc/html/rfc8032
10
11use crate::pem;
12use base64::Engine;
13use base64::engine::general_purpose::STANDARD as BASE64;
14use der::asn1::OctetString;
15use der::{Decode, Encode};
16use ed25519_dalek::ed25519::signature::rand_core::OsRng;
17use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePublicKey};
18use ed25519_dalek::{SignatureError, Signer, Verifier};
19use pkcs8::PrivateKeyInfo;
20use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
21use sha2::Digest;
22use spki::der::AnyRef;
23use spki::der::asn1::BitStringRef;
24use spki::{AlgorithmIdentifier, ObjectIdentifier, SubjectPublicKeyInfo};
25use std::error::Error;
26
27/// OID is the ASN.1 object identifier for Ed25519.
28pub const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.112");
29
30/// Size of the secret key in bytes.
31pub const SECRET_KEY_SIZE: usize = 32;
32
33/// Size of the public key in bytes.
34pub const PUBLIC_KEY_SIZE: usize = 32;
35
36/// Size of a signature in bytes.
37pub const SIGNATURE_SIZE: usize = 64;
38
39/// Size of a fingerprint in bytes.
40pub const FINGERPRINT_SIZE: usize = 32;
41
42/// SecretKey contains an Ed25519 private key usable for signing.
43#[derive(Clone)]
44pub struct SecretKey {
45    inner: ed25519_dalek::SigningKey,
46}
47
48impl SecretKey {
49    /// generate creates a new, random private key.
50    pub fn generate() -> SecretKey {
51        let mut rng = OsRng;
52
53        let key = ed25519_dalek::SigningKey::generate(&mut rng);
54        Self { inner: key }
55    }
56
57    /// from_bytes converts a 32-byte array into a private key.
58    pub fn from_bytes(bin: &[u8; SECRET_KEY_SIZE]) -> Self {
59        let key = ed25519_dalek::SecretKey::from(*bin);
60        let sig = ed25519_dalek::SigningKey::from(&key);
61        Self { inner: sig }
62    }
63
64    /// from_der parses a DER buffer into a private key.
65    pub fn from_der(der: &[u8]) -> Result<Self, Box<dyn Error>> {
66        let info = PrivateKeyInfo::try_from(der)?;
67
68        // Reject trailing data by verifying re-encoded length matches input
69        if info.encoded_len()?.try_into() != Ok(der.len()) {
70            return Err("trailing data in private key".into());
71        }
72        if info.public_key.is_some() {
73            return Err("embedded public key not supported".into());
74        }
75        let inner = ed25519_dalek::SigningKey::from_pkcs8_der(der)?;
76        Ok(Self { inner })
77    }
78
79    /// from_pem parses a PEM string into a private key.
80    pub fn from_pem(pem_str: &str) -> Result<Self, Box<dyn Error>> {
81        let (kind, data) = pem::decode(pem_str.as_bytes())?;
82        if kind != "PRIVATE KEY" {
83            return Err(format!("invalid PEM tag {}", kind).into());
84        }
85        Self::from_der(&data)
86    }
87
88    /// to_bytes converts a private key into a 32-byte array.
89    pub fn to_bytes(&self) -> [u8; SECRET_KEY_SIZE] {
90        self.inner.to_bytes()
91    }
92
93    /// to_der serializes a private key into a DER buffer.
94    pub fn to_der(&self) -> Vec<u8> {
95        // The private key field contains an OCTET STRING wrapping the seed
96        let seed = OctetString::new(self.inner.to_bytes()).unwrap();
97        let inner = seed.to_der().unwrap();
98
99        let alg = pkcs8::AlgorithmIdentifierRef {
100            oid: OID,
101            parameters: None::<AnyRef>,
102        };
103        let info = PrivateKeyInfo {
104            algorithm: alg,
105            private_key: &inner,
106            public_key: None,
107        };
108        info.to_der().unwrap()
109    }
110
111    /// to_pem serializes a private key into a PEM string.
112    pub fn to_pem(&self) -> String {
113        pem::encode("PRIVATE KEY", &self.to_der())
114    }
115
116    /// public_key retrieves the public counterpart of the secret key.
117    pub fn public_key(&self) -> PublicKey {
118        PublicKey {
119            inner: self.inner.verifying_key(),
120        }
121    }
122
123    /// fingerprint returns a 256bit unique identified for this key. For HPKE,
124    /// that is the SHA256 hash of the raw public key.
125    pub fn fingerprint(&self) -> Fingerprint {
126        self.public_key().fingerprint()
127    }
128
129    /// sign creates a digital signature of the message.
130    pub fn sign(&self, message: &[u8]) -> Signature {
131        Signature(self.inner.sign(message).to_bytes())
132    }
133}
134
135/// PublicKey contains an Ed25519 public key usable for verification.
136#[derive(Debug, Clone)]
137pub struct PublicKey {
138    inner: ed25519_dalek::VerifyingKey,
139}
140
141impl PublicKey {
142    /// from_bytes converts a 32-byte array into a public key.
143    pub fn from_bytes(bin: &[u8; PUBLIC_KEY_SIZE]) -> Result<Self, Box<dyn Error>> {
144        let inner = ed25519_dalek::VerifyingKey::from_bytes(bin)?;
145        Ok(Self { inner })
146    }
147
148    /// from_der parses a DER buffer into a public key.
149    pub fn from_der(der: &[u8]) -> Result<Self, Box<dyn Error>> {
150        // Parse with SPKI to check for trailing data
151        let info: SubjectPublicKeyInfo<AlgorithmIdentifier<AnyRef>, BitStringRef> =
152            SubjectPublicKeyInfo::from_der(der)?;
153        if info.encoded_len()?.try_into() != Ok(der.len()) {
154            return Err("trailing data in public key".into());
155        }
156        let inner = ed25519_dalek::VerifyingKey::from_public_key_der(der)?;
157        Ok(Self { inner })
158    }
159
160    /// from_pem parses a PEM string into a public key.
161    pub fn from_pem(pem_str: &str) -> Result<Self, Box<dyn Error>> {
162        let (kind, data) = pem::decode(pem_str.as_bytes())?;
163        if kind != "PUBLIC KEY" {
164            return Err(format!("invalid PEM tag {}", kind).into());
165        }
166        Self::from_der(&data)
167    }
168
169    /// to_bytes converts a public key into a 32-byte array.
170    pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_SIZE] {
171        self.inner.to_bytes()
172    }
173
174    /// to_der serializes a public key into a DER buffer.
175    pub fn to_der(&self) -> Vec<u8> {
176        self.inner.to_public_key_der().unwrap().as_bytes().to_vec()
177    }
178
179    /// to_pem serializes a public key into a PEM string.
180    pub fn to_pem(&self) -> String {
181        pem::encode("PUBLIC KEY", &self.to_der())
182    }
183
184    /// fingerprint returns a 256bit unique identified for this key. For Ed25519,
185    /// that is the SHA256 hash of the raw public key.
186    pub fn fingerprint(&self) -> Fingerprint {
187        let mut hasher = sha2::Sha256::new();
188        hasher.update(self.to_bytes());
189        Fingerprint(hasher.finalize().into())
190    }
191
192    /// verify verifies a digital signature.
193    pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> {
194        let sig = ed25519_dalek::Signature::from_bytes(&signature.to_bytes());
195        self.inner.verify(message, &sig)
196    }
197}
198
199impl Serialize for PublicKey {
200    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
201        serializer.serialize_str(&BASE64.encode(self.to_bytes()))
202    }
203}
204
205impl<'de> Deserialize<'de> for PublicKey {
206    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
207        let s = String::deserialize(deserializer)?;
208        let bytes = BASE64.decode(&s).map_err(de::Error::custom)?;
209        let arr: [u8; PUBLIC_KEY_SIZE] = bytes
210            .try_into()
211            .map_err(|_| de::Error::custom("invalid public key length"))?;
212        PublicKey::from_bytes(&arr).map_err(de::Error::custom)
213    }
214}
215
216#[cfg(feature = "cbor")]
217impl crate::cbor::Encode for PublicKey {
218    fn encode_cbor(&self) -> Vec<u8> {
219        self.to_bytes().encode_cbor()
220    }
221}
222
223#[cfg(feature = "cbor")]
224impl crate::cbor::Decode for PublicKey {
225    fn decode_cbor(data: &[u8]) -> Result<Self, crate::cbor::Error> {
226        let bytes = <[u8; PUBLIC_KEY_SIZE]>::decode_cbor(data)?;
227        Self::from_bytes(&bytes).map_err(|e| crate::cbor::Error::DecodeFailed(e.to_string()))
228    }
229
230    fn decode_cbor_notrail(
231        decoder: &mut crate::cbor::Decoder<'_>,
232    ) -> Result<Self, crate::cbor::Error> {
233        let bytes = decoder.decode_bytes_fixed::<PUBLIC_KEY_SIZE>()?;
234        Self::from_bytes(&bytes).map_err(|e| crate::cbor::Error::DecodeFailed(e.to_string()))
235    }
236}
237
238/// Signature contains an Ed25519 signature.
239#[derive(Debug, Clone, Copy, PartialEq, Eq)]
240pub struct Signature([u8; SIGNATURE_SIZE]);
241
242impl Signature {
243    /// from_bytes converts a 64-byte array into a signature.
244    pub fn from_bytes(bytes: &[u8; SIGNATURE_SIZE]) -> Self {
245        Self(*bytes)
246    }
247
248    /// to_bytes converts a signature into a 64-byte array.
249    pub fn to_bytes(&self) -> [u8; SIGNATURE_SIZE] {
250        self.0
251    }
252}
253
254impl Serialize for Signature {
255    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
256        serializer.serialize_str(&BASE64.encode(self.to_bytes()))
257    }
258}
259
260impl<'de> Deserialize<'de> for Signature {
261    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
262        let s = String::deserialize(deserializer)?;
263        let bytes = BASE64.decode(&s).map_err(de::Error::custom)?;
264        let arr: [u8; SIGNATURE_SIZE] = bytes
265            .try_into()
266            .map_err(|_| de::Error::custom("invalid signature length"))?;
267        Ok(Signature::from_bytes(&arr))
268    }
269}
270
271#[cfg(feature = "cbor")]
272impl crate::cbor::Encode for Signature {
273    fn encode_cbor(&self) -> Vec<u8> {
274        self.to_bytes().encode_cbor()
275    }
276}
277
278#[cfg(feature = "cbor")]
279impl crate::cbor::Decode for Signature {
280    fn decode_cbor(data: &[u8]) -> Result<Self, crate::cbor::Error> {
281        let bytes = <[u8; SIGNATURE_SIZE]>::decode_cbor(data)?;
282        Ok(Self::from_bytes(&bytes))
283    }
284
285    fn decode_cbor_notrail(
286        decoder: &mut crate::cbor::Decoder<'_>,
287    ) -> Result<Self, crate::cbor::Error> {
288        let bytes = decoder.decode_bytes_fixed::<SIGNATURE_SIZE>()?;
289        Ok(Self::from_bytes(&bytes))
290    }
291}
292
293/// Fingerprint contains an Ed25519 key fingerprint.
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
295pub struct Fingerprint([u8; FINGERPRINT_SIZE]);
296
297impl Fingerprint {
298    /// from_bytes converts a 32-byte array into a fingerprint.
299    pub fn from_bytes(bytes: &[u8; FINGERPRINT_SIZE]) -> Self {
300        Self(*bytes)
301    }
302
303    /// to_bytes converts a fingerprint into a 32-byte array.
304    pub fn to_bytes(&self) -> [u8; FINGERPRINT_SIZE] {
305        self.0
306    }
307}
308
309impl Serialize for Fingerprint {
310    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
311        serializer.serialize_str(&BASE64.encode(self.to_bytes()))
312    }
313}
314
315impl<'de> Deserialize<'de> for Fingerprint {
316    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
317        let s = String::deserialize(deserializer)?;
318        let bytes = BASE64.decode(&s).map_err(de::Error::custom)?;
319        let arr: [u8; FINGERPRINT_SIZE] = bytes
320            .try_into()
321            .map_err(|_| de::Error::custom("invalid fingerprint length"))?;
322        Ok(Fingerprint::from_bytes(&arr))
323    }
324}
325
326#[cfg(feature = "cbor")]
327impl crate::cbor::Encode for Fingerprint {
328    fn encode_cbor(&self) -> Vec<u8> {
329        self.to_bytes().encode_cbor()
330    }
331}
332
333#[cfg(feature = "cbor")]
334impl crate::cbor::Decode for Fingerprint {
335    fn decode_cbor(data: &[u8]) -> Result<Self, crate::cbor::Error> {
336        let bytes = <[u8; FINGERPRINT_SIZE]>::decode_cbor(data)?;
337        Ok(Self::from_bytes(&bytes))
338    }
339
340    fn decode_cbor_notrail(
341        decoder: &mut crate::cbor::Decoder<'_>,
342    ) -> Result<Self, crate::cbor::Error> {
343        let bytes = decoder.decode_bytes_fixed::<FINGERPRINT_SIZE>()?;
344        Ok(Self::from_bytes(&bytes))
345    }
346}
347
348#[cfg(test)]
349mod tests {
350    use super::*;
351
352    // Tests signing and verifying messages. Note, this test is not meant to test
353    // cryptography, it is mostly an API sanity check to verify that everything
354    // seems to work.
355    //
356    // TODO(karalabe): Get some live test vectors for a bit more sanity
357    #[test]
358    fn test_sign_verify() {
359        // Create the keys for Alice
360        let secret = SecretKey::generate();
361        let public = secret.public_key();
362
363        // Run a bunch of different authentication/encryption combinations
364        struct TestCase<'a> {
365            message: &'a [u8],
366        }
367        let tests = [TestCase {
368            message: b"message to authenticate",
369        }];
370
371        for tt in &tests {
372            // Sign the message using the test case data
373            let signature = secret.sign(tt.message);
374
375            // Verify the signature message
376            public
377                .verify(tt.message, &signature)
378                .unwrap_or_else(|e| panic!("failed to verify message: {}", e));
379        }
380    }
381}