casper-client 5.0.1

A client library and binary for interacting with the Casper network
Documentation
//! Cryptographic key generation.

use std::{fs, path::Path};

use casper_types::{AsymmetricType, PublicKey, SecretKey};

use crate::Error;

/// Default filename for the PEM-encoded secret key file.
pub const SECRET_KEY_PEM: &str = "secret_key.pem";
/// Default filename for the hex-encoded public key file.
pub const PUBLIC_KEY_HEX: &str = "public_key_hex";
/// Default filename for the PEM-encoded public key file.
pub const PUBLIC_KEY_PEM: &str = "public_key.pem";

/// List of keygen related filenames: "secret_key.pem", "public_key.pem" and "public_key_hex".
pub const FILES: [&str; 3] = [SECRET_KEY_PEM, PUBLIC_KEY_PEM, PUBLIC_KEY_HEX];

/// Name of Ed25519 algorithm.
pub const ED25519: &str = "Ed25519";
/// Name of secp256k1 algorithm.
pub const SECP256K1: &str = "secp256k1";

/// Generates a new asymmetric key pair using the specified algorithm, and writes them to files in
/// the specified directory.
///
/// The secret key is written to "secret_key.pem", and the public key is written to "public_key.pem"
/// and also in hex format to "public_key_hex". For the hex format, the algorithm's tag is
/// prepended, e.g. `01` for Ed25519, `02` for secp256k1.
///
/// If `force` is true, existing files will be overwritten. If `force` is false and any of the
/// files exist, [`Error::FileAlreadyExists`] is returned and no files are written.
pub fn generate_files(output_dir: &str, algorithm: &str, force: bool) -> Result<(), Error> {
    if output_dir.is_empty() {
        return Err(Error::EmptyKeygenPath);
    }
    fs::create_dir_all(output_dir).map_err(move |error| Error::IoError {
        context: format!("unable to create directory at '{}'", output_dir),
        error,
    })?;
    let output_dir = Path::new(output_dir)
        .canonicalize()
        .map_err(|error| Error::IoError {
            context: format!("unable get canonical path at '{}'", output_dir),
            error,
        })?;

    if !force {
        for file in FILES.iter().map(|filename| output_dir.join(filename)) {
            if file.exists() {
                return Err(Error::FileAlreadyExists(file));
            }
        }
    }

    let secret_key = if algorithm.eq_ignore_ascii_case(ED25519) {
        SecretKey::generate_ed25519().unwrap()
    } else if algorithm.eq_ignore_ascii_case(SECP256K1) {
        SecretKey::generate_secp256k1().unwrap()
    } else {
        return Err(Error::UnsupportedAlgorithm(algorithm.to_string()));
    };

    let public_key = PublicKey::from(&secret_key);

    let public_key_hex_path = output_dir.join(PUBLIC_KEY_HEX);
    fs::write(public_key_hex_path, public_key.to_hex()).map_err(|error| Error::IoError {
        context: format!(
            "unable to write public key hex file at {:?}",
            output_dir.join(PUBLIC_KEY_HEX)
        ),
        error,
    })?;

    let secret_key_path = output_dir.join(SECRET_KEY_PEM);
    secret_key
        .to_file(secret_key_path)
        .map_err(|error| Error::CryptoError {
            context: "secret_key",
            error,
        })?;

    let public_key_path = output_dir.join(PUBLIC_KEY_PEM);
    public_key
        .to_file(public_key_path)
        .map_err(|error| Error::CryptoError {
            context: "public_key",
            error,
        })?;

    Ok(())
}