use std::path::PathBuf;
use anyhow::Result;
use async_trait::async_trait;
use clap::Parser;
use colored::Colorize;
use starknet::{
core::{crypto::Signature, types::FieldElement},
signers::{LocalWallet, Signer, SigningKey, VerifyingKey},
};
#[derive(Debug)]
pub enum AnySigner {
LocalWallet(LocalWallet),
}
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub enum AnySignerGetPublicKeyError {
LocalWallet(<LocalWallet as Signer>::GetPublicKeyError),
}
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub enum AnySignerSignError {
LocalWallet(<LocalWallet as Signer>::SignError),
}
#[derive(Debug, Clone, Parser)]
pub struct SignerArgs {
#[clap(long, help = keystore_help())]
keystore: Option<String>,
#[clap(
long,
help = "Supply keystore password from command line option instead of prompt"
)]
keystore_password: Option<String>,
#[clap(long, help = "Private key in hex in plain text")]
private_key: Option<String>,
}
enum StringValue {
FromCommandLine(String),
FromEnvVar(String),
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl Signer for AnySigner {
type GetPublicKeyError = AnySignerGetPublicKeyError;
type SignError = AnySignerSignError;
async fn get_public_key(&self) -> Result<VerifyingKey, Self::GetPublicKeyError> {
match self {
Self::LocalWallet(inner) => Ok(<LocalWallet as Signer>::get_public_key(inner)
.await
.map_err(Self::GetPublicKeyError::LocalWallet)?),
}
}
async fn sign_hash(&self, hash: &FieldElement) -> Result<Signature, Self::SignError> {
match self {
Self::LocalWallet(inner) => Ok(<LocalWallet as Signer>::sign_hash(inner, hash)
.await
.map_err(Self::SignError::LocalWallet)?),
}
}
}
impl SignerArgs {
pub fn into_signer(self) -> Result<AnySigner> {
let keystore = match self.keystore {
Some(value) => Some(StringValue::FromCommandLine(value)),
None => match std::env::var("STARKNET_KEYSTORE") {
Ok(value) => Some(StringValue::FromEnvVar(value)),
Err(_) => None,
},
};
match (keystore, self.keystore_password, self.private_key) {
(Some(StringValue::FromCommandLine(keystore)), keystore_password, None) => {
Self::resolve_keystore(keystore, keystore_password)
}
(None, None, Some(private_key)) => Self::resolve_private_key(private_key),
(Some(StringValue::FromEnvVar(_)), None, Some(private_key)) => {
Self::resolve_private_key(private_key)
}
(Some(StringValue::FromEnvVar(keystore)), keystore_password, None) => {
Self::resolve_keystore(keystore, keystore_password)
}
_ => Err(anyhow::anyhow!(
"no valid signer option provided. \
Consider using a keystore by providing a --keystore option.\
\n\nFor more information, see: https://book.starkli.rs/signers"
)),
}
}
fn resolve_keystore(keystore: String, keystore_password: Option<String>) -> Result<AnySigner> {
let keystore = PathBuf::from(&keystore);
if keystore_password.is_some() {
eprintln!(
"{}",
"WARNING: setting keystore passwords via --password is generally \
considered insecure, as they will be stored in your shell history or other \
log files."
.bright_magenta()
);
}
if !keystore.exists() {
anyhow::bail!("keystore file not found");
}
let password = if let Some(password) = keystore_password {
password
} else {
rpassword::prompt_password("Enter keystore password: ")?
};
let key = SigningKey::from_keystore(keystore, &password)?;
Ok(AnySigner::LocalWallet(LocalWallet::from_signing_key(key)))
}
fn resolve_private_key(private_key: String) -> Result<AnySigner> {
eprintln!(
"{}",
"WARNING: using private key in plain text is highly insecure, and you should \
ONLY do this for development. Consider using an encrypted keystore instead."
.bright_magenta()
);
let private_key = FieldElement::from_hex_be(&private_key)?;
let key = SigningKey::from_secret_scalar(private_key);
Ok(AnySigner::LocalWallet(LocalWallet::from_signing_key(key)))
}
}
fn keystore_help() -> String {
format!(
"Path to keystore JSON file [env: STARKNET_KEYSTORE={}]",
std::env::var("STARKNET_KEYSTORE").unwrap_or_default()
)
}