Skip to main content

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::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_secret_bytes());
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_secret_bytes(
81        bytes.try_into().map_err(|_| SecretKeyBaseError::InvalidSecretKey)?,
82    )
83    .map_err(SecretKeyError::SecretKeyDecodeError)
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_parse_secret_key_from_hex_without_prefix() {
92        // Valid 32-byte hex string (64 characters)
93        let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
94        let result = parse_secret_key_from_hex(hex);
95        assert!(result.is_ok());
96
97        let secret_key = result.unwrap();
98        assert_eq!(alloy_primitives::hex::encode(secret_key.to_secret_bytes()), hex);
99    }
100
101    #[test]
102    fn test_parse_secret_key_from_hex_with_0x_prefix() {
103        // Valid 32-byte hex string with 0x prefix
104        let hex = "0x4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
105        let result = parse_secret_key_from_hex(hex);
106        assert!(result.is_ok());
107
108        let secret_key = result.unwrap();
109        let expected = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
110        assert_eq!(alloy_primitives::hex::encode(secret_key.to_secret_bytes()), expected);
111    }
112
113    #[test]
114    fn test_parse_secret_key_from_hex_invalid_length() {
115        // Invalid length (not 32 bytes)
116        let hex = "4c0883a69102937d";
117        let result = parse_secret_key_from_hex(hex);
118        assert!(matches!(
119            result,
120            Err(SecretKeyError::SecretKeyDecodeError(SecretKeyBaseError::InvalidSecretKey))
121        ));
122    }
123
124    #[test]
125    fn test_parse_secret_key_from_hex_invalid_chars() {
126        // Invalid hex characters
127        let hex = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
128        let result = parse_secret_key_from_hex(hex);
129        assert!(result.is_err());
130
131        if let Err(SecretKeyError::InvalidHexString(_)) = result {
132            // Expected error type
133        } else {
134            panic!("Expected InvalidHexString error");
135        }
136    }
137
138    #[test]
139    fn test_parse_secret_key_from_hex_empty() {
140        let hex = "";
141        let result = parse_secret_key_from_hex(hex);
142        assert!(matches!(
143            result,
144            Err(SecretKeyError::SecretKeyDecodeError(SecretKeyBaseError::InvalidSecretKey))
145        ));
146    }
147}