soroban-cli 26.0.0

Soroban CLI
Documentation
use std::{fmt::Display, str::FromStr};

use serde::{Deserialize, Serialize};

use super::secret::{self, Secret};
use crate::xdr;

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("failed to extract secret from public key ")]
    SecretPublicKey,
    #[error(transparent)]
    Secret(#[from] secret::Error),
    #[error(transparent)]
    StrKey(#[from] stellar_strkey::DecodeError),
    #[error("failed to parse key")]
    Parse,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum Key {
    #[serde(rename = "public_key")]
    PublicKey(Public),
    #[serde(rename = "muxed_account")]
    MuxedAccount(MuxedAccount),
    #[serde(untagged)]
    Secret(Secret),
}

impl Key {
    pub fn muxed_account(&self, hd_path: Option<usize>) -> Result<xdr::MuxedAccount, Error> {
        let bytes = match self {
            Key::Secret(secret) => secret.public_key(hd_path)?.0,
            Key::PublicKey(Public(key)) => key.0,
            Key::MuxedAccount(MuxedAccount(stellar_strkey::ed25519::MuxedAccount {
                ed25519,
                id,
            })) => {
                return Ok(xdr::MuxedAccount::MuxedEd25519(xdr::MuxedAccountMed25519 {
                    ed25519: xdr::Uint256(*ed25519),
                    id: *id,
                }))
            }
        };
        Ok(xdr::MuxedAccount::Ed25519(xdr::Uint256(bytes)))
    }

    pub fn private_key(
        &self,
        hd_path: Option<usize>,
    ) -> Result<stellar_strkey::ed25519::PrivateKey, Error> {
        match self {
            Key::Secret(secret) => Ok(secret.private_key(hd_path)?),
            _ => Err(Error::SecretPublicKey),
        }
    }
}

impl FromStr for Key {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Ok(secret) = s.parse() {
            return Ok(Key::Secret(secret));
        }
        if let Ok(public_key) = s.parse() {
            return Ok(Key::PublicKey(public_key));
        }
        if let Ok(muxed_account) = s.parse() {
            return Ok(Key::MuxedAccount(muxed_account));
        }
        Err(Error::Parse)
    }
}

impl From<stellar_strkey::ed25519::PublicKey> for Key {
    fn from(value: stellar_strkey::ed25519::PublicKey) -> Self {
        Key::PublicKey(Public(value))
    }
}

impl From<&stellar_strkey::ed25519::PublicKey> for Key {
    fn from(stellar_strkey::ed25519::PublicKey(key): &stellar_strkey::ed25519::PublicKey) -> Self {
        stellar_strkey::ed25519::PublicKey(*key).into()
    }
}

#[derive(Debug, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)]
pub struct Public(pub stellar_strkey::ed25519::PublicKey);

impl FromStr for Public {
    type Err = stellar_strkey::DecodeError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Public(stellar_strkey::ed25519::PublicKey::from_str(s)?))
    }
}

impl Display for Public {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<&Public> for stellar_strkey::ed25519::MuxedAccount {
    fn from(Public(stellar_strkey::ed25519::PublicKey(key)): &Public) -> Self {
        stellar_strkey::ed25519::MuxedAccount {
            id: 0,
            ed25519: *key,
        }
    }
}

#[derive(Debug, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)]
pub struct MuxedAccount(pub stellar_strkey::ed25519::MuxedAccount);

impl FromStr for MuxedAccount {
    type Err = stellar_strkey::DecodeError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(MuxedAccount(
            stellar_strkey::ed25519::MuxedAccount::from_str(s)?,
        ))
    }
}

impl Display for MuxedAccount {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl TryFrom<Key> for Secret {
    type Error = Error;

    fn try_from(key: Key) -> Result<Secret, Self::Error> {
        match key {
            Key::Secret(secret) => Ok(secret),
            _ => Err(Error::SecretPublicKey),
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    fn round_trip(key: &Key) {
        let serialized = toml::to_string(&key).unwrap();
        println!("{serialized}");
        let deserialized: Key = toml::from_str(&serialized).unwrap();
        assert_eq!(key, &deserialized);
    }

    #[test]
    fn public_key() {
        let key = Key::PublicKey(Public(stellar_strkey::ed25519::PublicKey([0; 32])));
        round_trip(&key);
    }
    #[test]
    fn muxed_key() {
        let key: stellar_strkey::ed25519::MuxedAccount =
            "MA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAAAAAAAAAPCICBKU"
                .parse()
                .unwrap();
        let key = Key::MuxedAccount(MuxedAccount(key));
        round_trip(&key);
    }
    #[test]
    fn secret_key() {
        let secret_key = stellar_strkey::ed25519::PrivateKey([0; 32]).to_string();
        let secret = Secret::SecretKey { secret_key };
        let key = Key::Secret(secret);
        round_trip(&key);
    }
    #[test]
    fn secret_seed_phrase() {
        let seed_phrase = "singer swing mango apple singer swing mango apple singer swing mango apple singer swing mango apple".to_string();
        let secret = Secret::SeedPhrase { seed_phrase };
        let key = Key::Secret(secret);
        round_trip(&key);
    }
}