reth_cli_util/
load_secret_key.rs

1use reth_fs_util::{self as fs, FsPathError};
2use secp256k1::{Error as SecretKeyBaseError, SecretKey};
3use std::{
4    io,
5    path::{Path, PathBuf},
6};
7use thiserror::Error;
8
9/// Convenience function to create a new random [`SecretKey`]
10pub fn rng_secret_key() -> SecretKey {
11    SecretKey::new(&mut rand_08::thread_rng())
12}
13
14/// Errors returned by loading a [`SecretKey`], including IO errors.
15#[derive(Error, Debug)]
16pub enum SecretKeyError {
17    /// Error encountered during decoding of the secret key.
18    #[error(transparent)]
19    SecretKeyDecodeError(#[from] SecretKeyBaseError),
20
21    /// Error related to file system path operations.
22    #[error(transparent)]
23    SecretKeyFsPathError(#[from] FsPathError),
24
25    /// Represents an error when failed to access the key file.
26    #[error("failed to access key file {secret_file:?}: {error}")]
27    FailedToAccessKeyFile {
28        /// The encountered IO error.
29        error: io::Error,
30        /// Path to the secret key file.
31        secret_file: PathBuf,
32    },
33
34    /// Invalid hex string format.
35    #[error("invalid hex string: {0}")]
36    InvalidHexString(String),
37}
38
39/// Attempts to load a [`SecretKey`] from a specified path. If no file exists there, then it
40/// generates a secret key and stores it in the provided path. I/O errors might occur during write
41/// operations in the form of a [`SecretKeyError`]
42pub fn get_secret_key(secret_key_path: &Path) -> Result<SecretKey, SecretKeyError> {
43    let exists = secret_key_path.try_exists();
44
45    match exists {
46        Ok(true) => {
47            let contents = fs::read_to_string(secret_key_path)?;
48            Ok(contents.as_str().parse().map_err(SecretKeyError::SecretKeyDecodeError)?)
49        }
50        Ok(false) => {
51            if let Some(dir) = secret_key_path.parent() {
52                // Create parent directory
53                fs::create_dir_all(dir)?;
54            }
55
56            let secret = rng_secret_key();
57            let hex = alloy_primitives::hex::encode(secret.as_ref());
58            fs::write(secret_key_path, hex)?;
59            Ok(secret)
60        }
61        Err(error) => Err(SecretKeyError::FailedToAccessKeyFile {
62            error,
63            secret_file: secret_key_path.to_path_buf(),
64        }),
65    }
66}
67
68/// Parses a [`SecretKey`] from a hex string.
69///
70/// The hex string can optionally start with "0x".
71pub fn parse_secret_key_from_hex(hex_str: &str) -> Result<SecretKey, SecretKeyError> {
72    // Remove "0x" prefix if present
73    let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
74
75    // Decode the hex string
76    let bytes = alloy_primitives::hex::decode(hex_str)
77        .map_err(|e| SecretKeyError::InvalidHexString(e.to_string()))?;
78
79    // Parse into SecretKey
80    SecretKey::from_slice(&bytes).map_err(SecretKeyError::SecretKeyDecodeError)
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn test_parse_secret_key_from_hex_without_prefix() {
89        // Valid 32-byte hex string (64 characters)
90        let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
91        let result = parse_secret_key_from_hex(hex);
92        assert!(result.is_ok());
93
94        let secret_key = result.unwrap();
95        assert_eq!(alloy_primitives::hex::encode(secret_key.secret_bytes()), hex);
96    }
97
98    #[test]
99    fn test_parse_secret_key_from_hex_with_0x_prefix() {
100        // Valid 32-byte hex string with 0x prefix
101        let hex = "0x4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
102        let result = parse_secret_key_from_hex(hex);
103        assert!(result.is_ok());
104
105        let secret_key = result.unwrap();
106        let expected = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
107        assert_eq!(alloy_primitives::hex::encode(secret_key.secret_bytes()), expected);
108    }
109
110    #[test]
111    fn test_parse_secret_key_from_hex_invalid_length() {
112        // Invalid length (not 32 bytes)
113        let hex = "4c0883a69102937d";
114        let result = parse_secret_key_from_hex(hex);
115        assert!(result.is_err());
116    }
117
118    #[test]
119    fn test_parse_secret_key_from_hex_invalid_chars() {
120        // Invalid hex characters
121        let hex = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
122        let result = parse_secret_key_from_hex(hex);
123        assert!(result.is_err());
124
125        if let Err(SecretKeyError::InvalidHexString(_)) = result {
126            // Expected error type
127        } else {
128            panic!("Expected InvalidHexString error");
129        }
130    }
131
132    #[test]
133    fn test_parse_secret_key_from_hex_empty() {
134        let hex = "";
135        let result = parse_secret_key_from_hex(hex);
136        assert!(result.is_err());
137    }
138}