casper_client/
keygen.rs

1//! Cryptographic key generation.
2
3use std::{fs, path::Path};
4
5use casper_types::{AsymmetricType, PublicKey, SecretKey};
6
7use crate::Error;
8
9/// Default filename for the PEM-encoded secret key file.
10pub const SECRET_KEY_PEM: &str = "secret_key.pem";
11/// Default filename for the hex-encoded public key file.
12pub const PUBLIC_KEY_HEX: &str = "public_key_hex";
13/// Default filename for the PEM-encoded public key file.
14pub const PUBLIC_KEY_PEM: &str = "public_key.pem";
15
16/// List of keygen related filenames: "secret_key.pem", "public_key.pem" and "public_key_hex".
17pub const FILES: [&str; 3] = [SECRET_KEY_PEM, PUBLIC_KEY_PEM, PUBLIC_KEY_HEX];
18
19/// Name of Ed25519 algorithm.
20pub const ED25519: &str = "Ed25519";
21/// Name of secp256k1 algorithm.
22pub const SECP256K1: &str = "secp256k1";
23
24/// Generates a new asymmetric key pair using the specified algorithm, and writes them to files in
25/// the specified directory.
26///
27/// The secret key is written to "secret_key.pem", and the public key is written to "public_key.pem"
28/// and also in hex format to "public_key_hex". For the hex format, the algorithm's tag is
29/// prepended, e.g. `01` for Ed25519, `02` for secp256k1.
30///
31/// If `force` is true, existing files will be overwritten. If `force` is false and any of the
32/// files exist, [`Error::FileAlreadyExists`] is returned and no files are written.
33pub fn generate_files(output_dir: &str, algorithm: &str, force: bool) -> Result<(), Error> {
34    if output_dir.is_empty() {
35        return Err(Error::EmptyKeygenPath);
36    }
37    fs::create_dir_all(output_dir).map_err(move |error| Error::IoError {
38        context: format!("unable to create directory at '{}'", output_dir),
39        error,
40    })?;
41    let output_dir = Path::new(output_dir)
42        .canonicalize()
43        .map_err(|error| Error::IoError {
44            context: format!("unable get canonical path at '{}'", output_dir),
45            error,
46        })?;
47
48    if !force {
49        for file in FILES.iter().map(|filename| output_dir.join(filename)) {
50            if file.exists() {
51                return Err(Error::FileAlreadyExists(file));
52            }
53        }
54    }
55
56    let secret_key = if algorithm.eq_ignore_ascii_case(ED25519) {
57        SecretKey::generate_ed25519().unwrap()
58    } else if algorithm.eq_ignore_ascii_case(SECP256K1) {
59        SecretKey::generate_secp256k1().unwrap()
60    } else {
61        return Err(Error::UnsupportedAlgorithm(algorithm.to_string()));
62    };
63
64    let public_key = PublicKey::from(&secret_key);
65
66    let public_key_hex_path = output_dir.join(PUBLIC_KEY_HEX);
67    fs::write(public_key_hex_path, public_key.to_hex()).map_err(|error| Error::IoError {
68        context: format!(
69            "unable to write public key hex file at {:?}",
70            output_dir.join(PUBLIC_KEY_HEX)
71        ),
72        error,
73    })?;
74
75    let secret_key_path = output_dir.join(SECRET_KEY_PEM);
76    secret_key
77        .to_file(secret_key_path)
78        .map_err(|error| Error::CryptoError {
79            context: "secret_key",
80            error,
81        })?;
82
83    let public_key_path = output_dir.join(PUBLIC_KEY_PEM);
84    public_key
85        .to_file(public_key_path)
86        .map_err(|error| Error::CryptoError {
87            context: "public_key",
88            error,
89        })?;
90
91    Ok(())
92}