Skip to main content

pkarr/types/
keys.rs

1//! Utility structs for Ed25519 keys.
2
3use ed25519_dalek::{
4    SecretKey, Signature, SignatureError, Signer, SigningKey, Verifier, VerifyingKey,
5};
6use std::{
7    fmt::{self, Debug, Display, Formatter},
8    hash::Hash,
9    str::FromStr,
10};
11
12use serde::{Deserialize, Serialize};
13
14#[derive(Clone, PartialEq, Eq)]
15/// Ed25519 keypair to sign dns [Packet](crate::SignedPacket)s.
16pub struct Keypair(pub(crate) SigningKey);
17
18impl Keypair {
19    /// Generates a new random `Keypair` using the operating system's CSPRNG.
20    pub fn random() -> Keypair {
21        let mut bytes = [0u8; 32];
22
23        getrandom::fill(&mut bytes).expect("getrandom failed");
24
25        let signing_key: SigningKey = SigningKey::from_bytes(&bytes);
26
27        Keypair(signing_key)
28    }
29
30    /// Creates a `Keypair` from a given `SecretKey`.
31    pub fn from_secret_key(secret_key: &SecretKey) -> Keypair {
32        Keypair(SigningKey::from_bytes(secret_key))
33    }
34
35    /// Signs a message with the private key of this `Keypair`.
36    pub fn sign(&self, message: &[u8]) -> Signature {
37        self.0.sign(message)
38    }
39
40    /// Verifies a message against a given signature using this `Keypair`.
41    pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> {
42        self.0.verify(message, signature)
43    }
44
45    /// Returns the secret part of this `Keypair`.
46    pub fn secret_key(&self) -> SecretKey {
47        self.0.to_bytes()
48    }
49
50    /// Returns the [PublicKey] of this `Keypair`.
51    pub fn public_key(&self) -> PublicKey {
52        PublicKey(self.0.verifying_key())
53    }
54
55    /// Converts the public key of this `Keypair` to a z-base32 encoded string.
56    pub fn to_z32(&self) -> String {
57        self.public_key().to_string()
58    }
59
60    /// Converts the public key of this `Keypair` to a URI string.
61    pub fn to_uri_string(&self) -> String {
62        self.public_key().to_uri_string()
63    }
64}
65
66// Filesystem-related operations, which are not available for WASM
67#[cfg(not(wasm_browser))]
68impl Keypair {
69    /// Reads the `SecretKey` from a hex file and derives the `Keypair` from it.
70    pub fn from_secret_key_file(
71        secret_file_path: &std::path::Path,
72    ) -> Result<Keypair, std::io::Error> {
73        let hex_string = std::fs::read_to_string(secret_file_path)?;
74        let hex_string = hex_string.trim();
75
76        let invalid_data_err = |e: &str| std::io::Error::new(std::io::ErrorKind::InvalidData, e);
77
78        if hex_string.len() % 2 != 0 {
79            return Err(invalid_data_err("Invalid hex string length"));
80        }
81
82        let mut secret_key_bytes_vec = vec![];
83        for i in (0..hex_string.len()).step_by(2) {
84            let byte_str = &hex_string[i..i + 2];
85            let byte = u8::from_str_radix(byte_str, 16)
86                .map_err(|_| invalid_data_err("Invalid hex string"))?;
87            secret_key_bytes_vec.push(byte);
88        }
89
90        let secret_key_bytes: [u8; 32] = secret_key_bytes_vec
91            .try_into()
92            .map_err(|_| invalid_data_err("Invalid secret key length"))?;
93
94        Ok(Keypair::from_secret_key(&secret_key_bytes))
95    }
96
97    /// Writes the secret of the keypair to a file, as a hex encoded string.
98    /// If the file already exists, it will be overwritten.
99    /// In unix like operating systems, the file permission `600` is set.
100    pub fn write_secret_key_file(
101        &self,
102        secret_file_path: &std::path::Path,
103    ) -> Result<(), std::io::Error> {
104        let secret = self.secret_key();
105        let hex_string: String = secret.iter().map(|b| format!("{b:02x}")).collect();
106        std::fs::write(secret_file_path, hex_string)?;
107        #[cfg(unix)]
108        {
109            use std::os::unix::fs::PermissionsExt;
110
111            std::fs::set_permissions(secret_file_path, std::fs::Permissions::from_mode(0o600))?;
112        }
113        Ok(())
114    }
115}
116
117/// Ed25519 public key to verify a signature over dns [Packet](crate::SignedPacket)s.
118///
119/// It can formatted to and parsed from a z-base32 string.
120#[derive(Clone, Eq, PartialEq, Hash)]
121pub struct PublicKey(pub(crate) VerifyingKey);
122
123impl PublicKey {
124    /// Format the public key as z-base32 string.
125    pub fn to_z32(&self) -> String {
126        self.to_string()
127    }
128
129    /// Format the public key as `pk:` URI string.
130    pub fn to_uri_string(&self) -> String {
131        format!("pk:{self}")
132    }
133
134    /// Verify a signature over a message.
135    pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> {
136        self.0.verify(message, signature)
137    }
138
139    /// Return a reference to the underlying [VerifyingKey]
140    pub fn verifying_key(&self) -> &VerifyingKey {
141        &self.0
142    }
143
144    /// Return a the underlying [u8; 32] bytes.
145    pub fn to_bytes(&self) -> [u8; 32] {
146        self.0.to_bytes()
147    }
148
149    /// Return a reference to the underlying [u8; 32] bytes.
150    pub fn as_bytes(&self) -> &[u8; 32] {
151        self.0.as_bytes()
152    }
153}
154
155impl AsRef<Keypair> for Keypair {
156    fn as_ref(&self) -> &Keypair {
157        self
158    }
159}
160
161impl AsRef<PublicKey> for PublicKey {
162    fn as_ref(&self) -> &PublicKey {
163        self
164    }
165}
166
167impl TryFrom<&[u8]> for PublicKey {
168    type Error = PublicKeyError;
169
170    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
171        let bytes_32: &[u8; 32] = bytes
172            .try_into()
173            .map_err(|_| PublicKeyError::InvalidPublicKeyLength(bytes.len()))?;
174
175        Ok(Self(
176            VerifyingKey::from_bytes(bytes_32)
177                .map_err(|_| PublicKeyError::InvalidEd25519PublicKey)?,
178        ))
179    }
180}
181
182impl TryFrom<&[u8; 32]> for PublicKey {
183    type Error = PublicKeyError;
184
185    fn try_from(public: &[u8; 32]) -> Result<Self, Self::Error> {
186        Ok(Self(
187            VerifyingKey::from_bytes(public)
188                .map_err(|_| PublicKeyError::InvalidEd25519PublicKey)?,
189        ))
190    }
191}
192
193impl From<VerifyingKey> for PublicKey {
194    fn from(verifying_key: VerifyingKey) -> Self {
195        Self(verifying_key)
196    }
197}
198
199impl FromStr for PublicKey {
200    type Err = PublicKeyError;
201
202    /// Convert the TLD in a `&str` to a [PublicKey].
203    ///
204    /// # Examples
205    ///
206    /// - `o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy`
207    /// - `pk:o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy`
208    /// - `http://o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy`
209    /// - `https://o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy`
210    /// - `https://o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy/foo/bar`
211    /// - `https://foo.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy.`
212    /// - `https://foo.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy.#hash`
213    /// - `https://foo@bar.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy.?q=v`
214    /// - `https://foo@bar.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy.:8888?q=v`
215    /// - `https://yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy`
216    fn from_str(s: &str) -> Result<Self, Self::Err> {
217        let mut s = s;
218
219        if s.len() > 52 {
220            // Remove scheme
221            s = s.split_once(':').map(|tuple| tuple.1).unwrap_or(s);
222
223            if s.len() > 52 {
224                // Remove `//
225                s = s.strip_prefix("//").unwrap_or(s);
226
227                if s.len() > 52 {
228                    // Remove username
229                    s = s.split_once('@').map(|tuple| tuple.1).unwrap_or(s);
230
231                    if s.len() > 52 {
232                        // Remove port
233                        s = s.split_once(':').map(|tuple| tuple.0).unwrap_or(s);
234
235                        if s.len() > 52 {
236                            // Remove trailing path
237                            s = s.split_once('/').map(|tuple| tuple.0).unwrap_or(s);
238
239                            if s.len() > 52 {
240                                // Remove query
241                                s = s.split_once('?').map(|tuple| tuple.0).unwrap_or(s);
242
243                                if s.len() > 52 {
244                                    // Remove hash
245                                    s = s.split_once('#').map(|tuple| tuple.0).unwrap_or(s);
246
247                                    if s.len() > 52 {
248                                        if s.ends_with('.') {
249                                            // Remove trailing dot
250                                            s = s.trim_matches('.');
251                                        }
252
253                                        s = s.rsplit_once('.').map(|tuple| tuple.1).unwrap_or(s);
254                                    }
255                                }
256                            }
257                        }
258                    }
259                }
260            }
261        }
262
263        let bytes = if let Some(v) = base32::decode(base32::Alphabet::Z, s) {
264            Ok(v)
265        } else {
266            Err(PublicKeyError::InvalidPublicKeyEncoding)
267        }?;
268
269        bytes.as_slice().try_into()
270    }
271}
272
273impl TryFrom<&str> for PublicKey {
274    type Error = PublicKeyError;
275
276    fn try_from(s: &str) -> Result<PublicKey, PublicKeyError> {
277        PublicKey::from_str(s)
278    }
279}
280
281impl TryFrom<String> for PublicKey {
282    type Error = PublicKeyError;
283
284    fn try_from(s: String) -> Result<PublicKey, PublicKeyError> {
285        s.as_str().try_into()
286    }
287}
288
289impl TryFrom<&String> for PublicKey {
290    type Error = PublicKeyError;
291
292    fn try_from(s: &String) -> Result<PublicKey, PublicKeyError> {
293        s.as_str().try_into()
294    }
295}
296
297impl Display for PublicKey {
298    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
299        write!(
300            f,
301            "{}",
302            base32::encode(base32::Alphabet::Z, self.0.as_bytes())
303        )
304    }
305}
306
307impl Display for Keypair {
308    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
309        write!(f, "{}", self.public_key())
310    }
311}
312
313impl Debug for Keypair {
314    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315        write!(f, "Keypair({})", self.public_key())
316    }
317}
318
319impl Debug for PublicKey {
320    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321        write!(f, "PublicKey({self})")
322    }
323}
324
325impl Serialize for PublicKey {
326    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
327    where
328        S: serde::Serializer,
329    {
330        let bytes = self.to_bytes();
331        bytes.serialize(serializer)
332    }
333}
334
335impl<'de> Deserialize<'de> for PublicKey {
336    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
337    where
338        D: serde::Deserializer<'de>,
339    {
340        let bytes: [u8; 32] = Deserialize::deserialize(deserializer)?;
341
342        (&bytes).try_into().map_err(serde::de::Error::custom)
343    }
344}
345
346#[derive(thiserror::Error, Debug, PartialEq, Eq)]
347/// Errors while trying to create a [PublicKey]
348pub enum PublicKeyError {
349    #[error("Invalid PublicKey length, expected 32 bytes but got: {0}")]
350    /// Invalid PublicKey length.
351    InvalidPublicKeyLength(usize),
352
353    #[error("Invalid Ed25519 publickey; Cannot decompress Edwards point")]
354    /// Cannot decompress Edwards point
355    InvalidEd25519PublicKey,
356
357    #[error("Invalid PublicKey encoding")]
358    /// Invalid PublicKey encoding
359    InvalidPublicKeyEncoding,
360}
361
362#[cfg(test)]
363mod tests {
364    use super::*;
365
366    #[test]
367    fn test_from_string_ref() {
368        let string = "yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no".to_string();
369        let expected = [
370            1, 180, 103, 163, 183, 145, 58, 178, 122, 4, 168, 237, 242, 243, 251, 7, 76, 254, 14,
371            207, 75, 171, 225, 8, 214, 123, 227, 133, 59, 15, 38, 197,
372        ];
373        let public_key: PublicKey = (&string).try_into().unwrap();
374        assert_eq!(public_key.verifying_key().as_bytes(), &expected);
375    }
376
377    #[test]
378    fn pkarr_key_generate() {
379        let key1 = Keypair::random();
380        let key2 = Keypair::from_secret_key(&key1.secret_key());
381
382        assert_eq!(key1.public_key(), key2.public_key())
383    }
384
385    #[test]
386    fn zbase32() {
387        let key1 = Keypair::random();
388        let _z32 = key1.public_key().to_string();
389
390        let key2 = Keypair::from_secret_key(&key1.secret_key());
391
392        assert_eq!(key1.public_key(), key2.public_key())
393    }
394
395    #[test]
396    fn sign_verify() {
397        let keypair = Keypair::random();
398
399        let message = b"Hello, world!";
400        let signature = keypair.sign(message);
401
402        assert!(keypair.verify(message, &signature).is_ok());
403
404        let public_key = keypair.public_key();
405        assert!(public_key.verify(message, &signature).is_ok());
406    }
407
408    #[test]
409    fn from_string() {
410        let str = "yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no";
411        let expected = [
412            1, 180, 103, 163, 183, 145, 58, 178, 122, 4, 168, 237, 242, 243, 251, 7, 76, 254, 14,
413            207, 75, 171, 225, 8, 214, 123, 227, 133, 59, 15, 38, 197,
414        ];
415
416        let public_key: PublicKey = str.try_into().unwrap();
417        assert_eq!(public_key.verifying_key().as_bytes(), &expected);
418    }
419
420    #[test]
421    fn to_uri() {
422        let bytes = [
423            1, 180, 103, 163, 183, 145, 58, 178, 122, 4, 168, 237, 242, 243, 251, 7, 76, 254, 14,
424            207, 75, 171, 225, 8, 214, 123, 227, 133, 59, 15, 38, 197,
425        ];
426        let expected = "pk:yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no";
427
428        let public_key: PublicKey = (&bytes).try_into().unwrap();
429
430        assert_eq!(public_key.to_uri_string(), expected);
431    }
432
433    #[test]
434    fn from_uri() {
435        let str = "pk:yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no";
436        let expected = [
437            1, 180, 103, 163, 183, 145, 58, 178, 122, 4, 168, 237, 242, 243, 251, 7, 76, 254, 14,
438            207, 75, 171, 225, 8, 214, 123, 227, 133, 59, 15, 38, 197,
439        ];
440
441        let public_key: PublicKey = str.try_into().unwrap();
442        assert_eq!(public_key.verifying_key().as_bytes(), &expected);
443    }
444
445    #[test]
446    fn from_uri_with_path() {
447        let str = "https://yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no///foo/bar";
448        let expected = [
449            1, 180, 103, 163, 183, 145, 58, 178, 122, 4, 168, 237, 242, 243, 251, 7, 76, 254, 14,
450            207, 75, 171, 225, 8, 214, 123, 227, 133, 59, 15, 38, 197,
451        ];
452
453        let public_key: PublicKey = str.try_into().unwrap();
454        assert_eq!(public_key.verifying_key().as_bytes(), &expected);
455    }
456
457    #[test]
458    fn from_uri_with_query() {
459        let str = "https://yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no?foo=bar";
460        let expected = [
461            1, 180, 103, 163, 183, 145, 58, 178, 122, 4, 168, 237, 242, 243, 251, 7, 76, 254, 14,
462            207, 75, 171, 225, 8, 214, 123, 227, 133, 59, 15, 38, 197,
463        ];
464
465        let public_key: PublicKey = str.try_into().unwrap();
466        assert_eq!(public_key.verifying_key().as_bytes(), &expected);
467    }
468
469    #[test]
470    fn from_uri_with_hash() {
471        let str = "https://yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no#foo";
472        let expected = [
473            1, 180, 103, 163, 183, 145, 58, 178, 122, 4, 168, 237, 242, 243, 251, 7, 76, 254, 14,
474            207, 75, 171, 225, 8, 214, 123, 227, 133, 59, 15, 38, 197,
475        ];
476
477        let public_key: PublicKey = str.try_into().unwrap();
478        assert_eq!(public_key.verifying_key().as_bytes(), &expected);
479    }
480
481    #[test]
482    fn from_uri_with_subdomain() {
483        let str = "https://foo.bar.yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no#foo";
484        let expected = [
485            1, 180, 103, 163, 183, 145, 58, 178, 122, 4, 168, 237, 242, 243, 251, 7, 76, 254, 14,
486            207, 75, 171, 225, 8, 214, 123, 227, 133, 59, 15, 38, 197,
487        ];
488
489        let public_key: PublicKey = str.try_into().unwrap();
490        assert_eq!(public_key.verifying_key().as_bytes(), &expected);
491    }
492
493    #[test]
494    fn from_uri_with_trailing_dot() {
495        let str = "https://foo.yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no.";
496        let expected = [
497            1, 180, 103, 163, 183, 145, 58, 178, 122, 4, 168, 237, 242, 243, 251, 7, 76, 254, 14,
498            207, 75, 171, 225, 8, 214, 123, 227, 133, 59, 15, 38, 197,
499        ];
500
501        let public_key: PublicKey = str.try_into().unwrap();
502        assert_eq!(public_key.verifying_key().as_bytes(), &expected);
503    }
504
505    #[test]
506    fn from_uri_with_username() {
507        let str = "https://foo@yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no#foo";
508        let expected = [
509            1, 180, 103, 163, 183, 145, 58, 178, 122, 4, 168, 237, 242, 243, 251, 7, 76, 254, 14,
510            207, 75, 171, 225, 8, 214, 123, 227, 133, 59, 15, 38, 197,
511        ];
512
513        let public_key: PublicKey = str.try_into().unwrap();
514        assert_eq!(public_key.verifying_key().as_bytes(), &expected);
515    }
516
517    #[test]
518    fn from_uri_with_port() {
519        let str = "https://yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no:8888";
520        let expected = [
521            1, 180, 103, 163, 183, 145, 58, 178, 122, 4, 168, 237, 242, 243, 251, 7, 76, 254, 14,
522            207, 75, 171, 225, 8, 214, 123, 227, 133, 59, 15, 38, 197,
523        ];
524
525        let public_key: PublicKey = str.try_into().unwrap();
526        assert_eq!(public_key.verifying_key().as_bytes(), &expected);
527    }
528
529    #[test]
530    fn from_uri_complex() {
531        let str = "https://foo@bar.yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no.:8888?q=v&a=b#foo";
532        let expected = [
533            1, 180, 103, 163, 183, 145, 58, 178, 122, 4, 168, 237, 242, 243, 251, 7, 76, 254, 14,
534            207, 75, 171, 225, 8, 214, 123, 227, 133, 59, 15, 38, 197,
535        ];
536
537        let public_key: PublicKey = str.try_into().unwrap();
538        assert_eq!(public_key.verifying_key().as_bytes(), &expected);
539    }
540
541    #[test]
542    fn serde() {
543        let str = "yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no";
544        let expected = [
545            1, 180, 103, 163, 183, 145, 58, 178, 122, 4, 168, 237, 242, 243, 251, 7, 76, 254, 14,
546            207, 75, 171, 225, 8, 214, 123, 227, 133, 59, 15, 38, 197,
547        ];
548
549        let public_key: PublicKey = str.try_into().unwrap();
550
551        let bytes = postcard::to_allocvec(&public_key).unwrap();
552
553        assert_eq!(bytes, expected)
554    }
555
556    #[test]
557    fn from_uri_multiple_pkarr() {
558        // Should only catch the TLD.
559
560        let str = "https://o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy.yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no";
561        let expected = [
562            1, 180, 103, 163, 183, 145, 58, 178, 122, 4, 168, 237, 242, 243, 251, 7, 76, 254, 14,
563            207, 75, 171, 225, 8, 214, 123, 227, 133, 59, 15, 38, 197,
564        ];
565
566        let public_key: PublicKey = str.try_into().unwrap();
567        assert_eq!(public_key.verifying_key().as_bytes(), &expected);
568    }
569
570    #[cfg(all(not(target_family = "wasm"), feature = "tls"))]
571    #[test]
572    fn pkcs8() {
573        let str = "yg4gxe7z1r7mr6orids9fh95y7gxhdsxjqi6nngsxxtakqaxr5no";
574        let public_key: PublicKey = str.try_into().unwrap();
575
576        let der = public_key.to_public_key_der();
577
578        assert_eq!(
579            der.as_bytes(),
580            [
581                // Algorithm and other stuff.
582                48, 42, 48, 5, 6, 3, 43, 101, 112, 3, 33, 0, //
583                // Key
584                1, 180, 103, 163, 183, 145, 58, 178, 122, 4, 168, 237, 242, 243, 251, 7, 76, 254,
585                14, 207, 75, 171, 225, 8, 214, 123, 227, 133, 59, 15, 38, 197,
586            ]
587        )
588    }
589
590    #[cfg(all(not(target_family = "wasm"), feature = "tls"))]
591    #[test]
592    fn certificate() {
593        use rustls::SignatureAlgorithm;
594
595        let keypair = Keypair::from_secret_key(&[0; 32]);
596
597        let certified_key = keypair.to_rpk_certified_key();
598
599        assert_eq!(certified_key.key.algorithm(), SignatureAlgorithm::ED25519);
600
601        assert_eq!(
602            certified_key.end_entity_cert().unwrap().as_ref(),
603            [
604                48, 42, 48, 5, 6, 3, 43, 101, 112, 3, 33, 0, 59, 106, 39, 188, 206, 182, 164, 45,
605                98, 163, 168, 208, 42, 111, 13, 115, 101, 50, 21, 119, 29, 226, 67, 166, 58, 192,
606                72, 161, 139, 89, 218, 41,
607            ]
608        )
609    }
610
611    #[test]
612    fn invalid_key() {
613        let key = "c1bkg8tfsyy8wcedtmw4fwhdmm7bbzhgg3z58tf43m5ow8w9mbus";
614
615        assert_eq!(
616            PublicKey::try_from(key),
617            Err(PublicKeyError::InvalidEd25519PublicKey)
618        );
619    }
620
621    #[cfg(not(wasm_browser))]
622    mod fs_ops {
623        use std::fs::write;
624
625        use tempfile::NamedTempFile;
626
627        use crate::Keypair;
628
629        #[test]
630        fn test_write_and_read_keypair() {
631            let temp_file_path = NamedTempFile::new().unwrap().path().to_path_buf();
632
633            let generated_keypair = Keypair::random();
634
635            let write_keypair_result = generated_keypair.write_secret_key_file(&temp_file_path);
636            assert!(write_keypair_result.is_ok());
637            assert!(temp_file_path.exists());
638
639            let read_keypair_result = Keypair::from_secret_key_file(&temp_file_path);
640            assert!(read_keypair_result.is_ok());
641
642            let read_keypair = read_keypair_result.unwrap();
643            assert_eq!(generated_keypair.secret_key(), read_keypair.secret_key());
644        }
645
646        #[test]
647        fn test_read_keypair_invalid_hex() {
648            let temp_file_path = NamedTempFile::new().unwrap().path().to_path_buf();
649
650            write(&temp_file_path, "invalidhex").unwrap();
651
652            // Try to read file with invalid hex data
653            let read_keypair_result = Keypair::from_secret_key_file(&temp_file_path);
654            assert!(read_keypair_result.is_err());
655        }
656
657        #[test]
658        fn test_read_keypair_invalid_length() {
659            let temp_file_path = NamedTempFile::new().unwrap().path().to_path_buf();
660
661            write(&temp_file_path, "abcd").unwrap();
662
663            // Try to read file with valid hex, but invalid length
664            let read_keypair_result = Keypair::from_secret_key_file(&temp_file_path);
665            assert!(read_keypair_result.is_err());
666        }
667    }
668}