Skip to content

Commit

Permalink
feat: Add new Key type; allowing public keys to be named keys
Browse files Browse the repository at this point in the history
This will allow users to add public keys with `keys add` so that they can be referenced in commands that need public addresses and contract invoke's that take address arguments.
  • Loading branch information
willemneal authored and gitbutler-client committed Nov 5, 2024
1 parent e44b89f commit 06714b0
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 43 deletions.
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cmd/soroban-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ fqdn = "0.3.12"
open = "5.3.0"
url = "2.5.2"
wasm-gen = "0.1.4"
serde_with = "3.11.0"

[build-dependencies]
crate-git-revision = "0.0.4"
Expand Down
6 changes: 3 additions & 3 deletions cmd/soroban-cli/src/commands/keys/add.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use clap::command;

use crate::config::{locator, secret};
use crate::config::{key, locator, secret};

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Secret(#[from] secret::Error),
Key(#[from] key::Error),

#[error(transparent)]
Config(#[from] locator::Error),
Expand All @@ -28,6 +28,6 @@ impl Cmd {
pub fn run(&self) -> Result<(), Error> {
Ok(self
.config_locator
.write_identity(&self.name, &self.secrets.read_secret()?)?)
.write_key(&self.name, &self.secrets.read_key()?)?)
}
}
18 changes: 10 additions & 8 deletions cmd/soroban-cli/src/commands/keys/address.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use crate::commands::config::secret;

use super::super::config::locator;
use clap::arg;

use crate::commands::config::{key, locator};

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Config(#[from] locator::Error),

#[error(transparent)]
Secret(#[from] secret::Error),
Key(#[from] key::Error),

#[error(transparent)]
StrKey(#[from] stellar_strkey::DecodeError),
Expand All @@ -36,10 +35,13 @@ impl Cmd {
}

pub fn private_key(&self) -> Result<ed25519_dalek::SigningKey, Error> {
Ok(self
.locator
.read_identity(&self.name)?
.key_pair(self.hd_path)?)
Ok(ed25519_dalek::SigningKey::from_bytes(
&self
.locator
.read_identity(&self.name)?
.private_key(self.hd_path)?
.0,
))
}

pub fn public_key(&self) -> Result<stellar_strkey::ed25519::PublicKey, Error> {
Expand Down
7 changes: 2 additions & 5 deletions cmd/soroban-cli/src/commands/keys/show.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
use clap::arg;

use crate::config::{locator, secret};
use crate::config::{key, locator};

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Config(#[from] locator::Error),

#[error(transparent)]
Secret(#[from] secret::Error),

#[error(transparent)]
StrKey(#[from] stellar_strkey::DecodeError),
Key(#[from] key::Error),
}

#[derive(Debug, clap::Parser, Clone)]
Expand Down
14 changes: 6 additions & 8 deletions cmd/soroban-cli/src/config/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::str::FromStr;

use crate::xdr;

use super::{locator, secret};
use super::{key, locator, secret};

/// Address can be either a public key or eventually an alias of a address.
#[derive(Clone, Debug)]
Expand All @@ -22,7 +22,7 @@ pub enum Error {
#[error(transparent)]
Locator(#[from] locator::Error),
#[error(transparent)]
Secret(#[from] secret::Error),
Key(#[from] key::Error),
#[error("Address cannot be used to sign {0}")]
CannotSign(xdr::MuxedAccount),
}
Expand All @@ -46,18 +46,16 @@ impl Address {
) -> Result<xdr::MuxedAccount, Error> {
match self {
Address::MuxedAccount(muxed_account) => Ok(muxed_account.clone()),
Address::AliasOrSecret(alias) => alias.parse().or_else(|_| {
Ok(xdr::MuxedAccount::Ed25519(
locator.read_identity(alias)?.public_key(hd_path)?.0.into(),
))
}),
Address::AliasOrSecret(alias) => alias
.parse()
.or_else(|_| Ok(locator.read_identity(alias)?.public_key(hd_path)?)),
}
}

pub fn resolve_secret(&self, locator: &locator::Args) -> Result<secret::Secret, Error> {
match &self {
Address::AliasOrSecret(alias) => Ok(locator.get_private_key(alias)?),
Address::MuxedAccount(muxed_account) => Err(Error::CannotSign(muxed_account.clone())),
Address::AliasOrSecret(alias) => Ok(locator.read_identity(alias)?),
}
}
}
146 changes: 146 additions & 0 deletions cmd/soroban-cli/src/config/key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
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 public key from secret")]
SecretPublicKey,
#[error(transparent)]
Secret(#[from] secret::Error),
#[error(transparent)]
StrKey(#[from] stellar_strkey::DecodeError),
}

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

impl Key {
pub fn public_key(&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: PublicKey(key),
} => key.0,
Key::MuxedAccount {
muxed_account: 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 });
}
todo!("Error handling for invalid key format");
}
}

impl From<stellar_strkey::ed25519::PublicKey> for Key {
fn from(value: stellar_strkey::ed25519::PublicKey) -> Self {
Key::PublicKey {
public_key: PublicKey(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 PublicKey(pub stellar_strkey::ed25519::PublicKey);

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

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

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

impl From<&PublicKey> for stellar_strkey::ed25519::MuxedAccount {
fn from(PublicKey(stellar_strkey::ed25519::PublicKey(key)): &PublicKey) -> 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)
}
}

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

#[test]
fn public_key() {
let key = Key::PublicKey {
public_key: PublicKey(stellar_strkey::ed25519::PublicKey([0; 32])),
};
let serialized = toml::to_string(&key).unwrap();
println!("{serialized}");
let deserialized: Key = toml::from_str(&serialized).unwrap();
assert_eq!(key, deserialized);
}
}
30 changes: 23 additions & 7 deletions cmd/soroban-cli/src/config/locator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::{commands::HEADING_GLOBAL, utils::find_config_dir, Pwd};

use super::{
alias,
key::Key,
network::{self, Network},
secret::Secret,
};
Expand Down Expand Up @@ -156,7 +157,19 @@ impl Args {
}

pub fn write_identity(&self, name: &str, secret: &Secret) -> Result<(), Error> {
KeyType::Identity.write(name, secret, &self.config_dir()?)
self.write_key(name, &Key::Secret(secret.clone()))
}

pub fn write_public_key(
&self,
name: &str,
public_key: &stellar_strkey::ed25519::PublicKey,
) -> Result<(), Error> {
self.write_key(name, &public_key.into())
}

pub fn write_key(&self, name: &str, public_key: &Key) -> Result<(), Error> {
KeyType::Identity.write(name, public_key, &self.config_dir()?)
}

pub fn write_network(&self, name: &str, network: &Network) -> Result<(), Error> {
Expand Down Expand Up @@ -213,17 +226,20 @@ impl Args {
Ok(saved_networks.chain(default_networks).collect())
}

pub fn read_identity(&self, name: &str) -> Result<Secret, Error> {
Ok(KeyType::Identity
.read_with_global(name, &self.local_config()?)
.or_else(|_| name.parse())?)
pub fn read_identity(&self, name: &str) -> Result<Key, Error> {
Ok(KeyType::Identity.read_with_global(name, &self.local_config()?)?)
}

pub fn key(&self, key_or_name: &str) -> Result<Secret, Error> {
pub fn get_private_key(&self, key_or_name: &str) -> Result<Secret, Error> {
if let Ok(signer) = key_or_name.parse::<Secret>() {
Ok(signer)
} else {
self.read_identity(key_or_name)
match self.read_identity(key_or_name)? {
Key::Secret(s) => Ok(s),
_ => Err(Error::SecretFileRead {
path: self.alias_path(key_or_name)?,
}),
}
}
}

Expand Down
1 change: 1 addition & 0 deletions cmd/soroban-cli/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use network::Network;
pub mod address;
pub mod alias;
pub mod data;
pub mod key;
pub mod locator;
pub mod network;
pub mod secret;
Expand Down
Loading

0 comments on commit 06714b0

Please sign in to comment.