use std::str::FromStr;
use tap::Tap;
use crate::input::data::source::FileInputSource;
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
#[error(transparent)]
pub(crate) enum Error {
EllipticCurve(#[from] elliptic_curve::Error),
Io(#[from] std::io::Error),
#[error("unsupported curve: {curve}")]
UnsupportedCurve {
curve: String,
},
}
#[derive(Debug, Clone, clap::Parser)]
#[command(about = "derives public keys from private keys")]
#[remain::sorted]
#[rustfmt::skip]
pub(crate) struct Command {
#[command(flatten)]
file: FileInputSource,
#[arg(
env = "OUTPUT_FORMAT",
long = "output-format",
id = "output.format",
value_name = "FORMAT",
default_value = "jwk",
help_heading = "Output Options"
)]
format: Format,
}
impl Command {
pub(crate) async fn run(self, valkey: &crate::integrations::fred::ValkeyOptions) -> Result<Box<dyn crate::Output>, Error> {
let Self {
file: FileInputSource { path },
format,
} = self;
let key = PublicKey::try_from(path.as_path())?;
let output = format.encode(&key);
println!("{output}");
Ok(Box::new(()))
}
}
#[derive(Debug, Clone, Copy, Default, clap::ValueEnum)]
pub(crate) enum Format {
#[clap(name = "did:key")]
DidKey,
#[default]
Jwk,
Multibase,
}
impl Format {
pub(crate) fn encode(&self, key: &PublicKey) -> String {
match self {
Self::Jwk => key.to_jwk_string(),
Self::Multibase => key.to_multibase(),
Self::DidKey => key
.to_multibase()
.tap_mut(|multibase| multibase.insert_str(0, "did:key:")),
}
}
}
pub(crate) enum PublicKey {
K256(k256::PublicKey),
P256(p256::PublicKey),
}
impl From<k256::PublicKey> for PublicKey {
fn from(value: k256::PublicKey) -> Self {
Self::K256(value)
}
}
impl From<p256::PublicKey> for PublicKey {
fn from(value: p256::PublicKey) -> Self {
Self::P256(value)
}
}
impl TryFrom<elliptic_curve::JwkEcKey> for PublicKey {
type Error = Error;
fn try_from(jwk: elliptic_curve::JwkEcKey) -> Result<Self, Self::Error> {
match jwk.crv() {
"k256" | "secp256k1" => Ok(Self::K256(jwk.to_public_key()?)),
"p256" | "P-256" => Ok(Self::P256(jwk.to_public_key()?)),
_ => Err(Error::UnsupportedCurve {
curve: jwk.crv().to_owned(),
}),
}
}
}
impl TryFrom<&camino::Utf8Path> for PublicKey {
type Error = Error;
fn try_from(path: &camino::Utf8Path) -> Result<Self, Self::Error> {
elliptic_curve::JwkEcKey::from_str(&fs_err::read_to_string(path)?)?.try_into()
}
}
trait ToJwkString {
fn to_jwk_string(&self) -> String;
}
impl ToJwkString for PublicKey {
fn to_jwk_string(&self) -> String {
match self {
Self::K256(key) => key.to_jwk_string(),
Self::P256(key) => key.to_jwk_string(),
}
}
}
trait ToMultibase {
fn to_multibase(&self) -> String;
}
impl ToMultibase for k256::PublicKey {
fn to_multibase(&self) -> String {
let mut input = Vec::<u8>::new();
input.extend_from_slice(&[0xE7, 0x01]);
input.extend_from_slice(&self.to_sec1_bytes());
multibase::encode(multibase::Base::Base58Btc, &input)
}
}
impl ToMultibase for p256::PublicKey {
fn to_multibase(&self) -> String {
let mut input = Vec::<u8>::new();
input.extend_from_slice(&[0x80, 0x24]);
input.extend_from_slice(&self.to_sec1_bytes());
multibase::encode(multibase::Base::Base58Btc, &input)
}
}
impl ToMultibase for PublicKey {
fn to_multibase(&self) -> String {
match self {
Self::K256(key) => key.to_multibase(),
Self::P256(key) => key.to_multibase(),
}
}
}