From d7b9c1848e12d90ea20fbe47a14ae9d99376d476 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 2 Dec 2025 17:10:53 -0500 Subject: [PATCH 01/28] Store StellarEntry on SecureStoryEntry and reuse it --- cmd/soroban-cli/src/config/secret.rs | 3 ++- cmd/soroban-cli/src/signer/mod.rs | 13 +++++++------ cmd/soroban-cli/src/signer/secure_store.rs | 12 ++++++++++++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index 09f9770465..f125057bc1 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -7,7 +7,7 @@ use stellar_strkey::ed25519::{PrivateKey, PublicKey}; use crate::{ print::Print, - signer::{self, ledger, secure_store, LocalKey, SecureStoreEntry, Signer, SignerKind}, + signer::{self, ledger, secure_store, LocalKey, SecureStoreEntry, Signer, SignerKind, keyring::StellarEntry}, utils, }; @@ -154,6 +154,7 @@ impl Secret { Secret::SecureStore { entry_name } => SignerKind::SecureStore(SecureStoreEntry { name: entry_name.clone(), hd_path, + entry: StellarEntry::new(&entry_name).unwrap() //fixme! }), }; Ok(Signer { kind, print }) diff --git a/cmd/soroban-cli/src/signer/mod.rs b/cmd/soroban-cli/src/signer/mod.rs index c699b30b33..764b8459bd 100644 --- a/cmd/soroban-cli/src/signer/mod.rs +++ b/cmd/soroban-cli/src/signer/mod.rs @@ -1,10 +1,10 @@ -use crate::xdr::{ +use crate::{signer::keyring::StellarEntry, xdr::{ self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, SorobanAuthorizedFunction, SorobanCredentials, Transaction, TransactionEnvelope, TransactionV1Envelope, Uint256, VecM, WriteXdr, -}; +}}; use ed25519_dalek::{ed25519::signature::Signer as _, Signature as Ed25519Signature}; use sha2::{Digest, Sha256}; @@ -13,7 +13,7 @@ use crate::{config::network::Network, print::Print, utils::transaction_hash}; pub mod ledger; #[cfg(feature = "additional-libs")] -mod keyring; +pub mod keyring; pub mod secure_store; #[derive(thiserror::Error, Debug)] @@ -330,19 +330,20 @@ impl Lab { pub struct SecureStoreEntry { pub name: String, pub hd_path: Option, + pub entry: StellarEntry, } impl SecureStoreEntry { pub fn get_public_key(&self) -> Result { - Ok(secure_store::get_public_key(&self.name, self.hd_path)?) + Ok(secure_store::get_public_key_with_entry(&self.entry, self.hd_path)?) } pub fn sign_tx_hash(&self, tx_hash: [u8; 32]) -> Result { let hint = SignatureHint( - secure_store::get_public_key(&self.name, self.hd_path)?.0[28..].try_into()?, + secure_store::get_public_key_with_entry(&self.entry, self.hd_path)?.0[28..].try_into()?, ); - let signed_tx_hash = secure_store::sign_tx_data(&self.name, self.hd_path, &tx_hash)?; + let signed_tx_hash = secure_store::sign_tx_data_with_entry(&self.entry, self.hd_path, &tx_hash)?; let signature = Signature(signed_tx_hash.clone().try_into()?); Ok(DecoratedSignature { hint, signature }) diff --git a/cmd/soroban-cli/src/signer/secure_store.rs b/cmd/soroban-cli/src/signer/secure_store.rs index d30cf3b501..183e64bdb5 100644 --- a/cmd/soroban-cli/src/signer/secure_store.rs +++ b/cmd/soroban-cli/src/signer/secure_store.rs @@ -36,6 +36,10 @@ mod secure_store_impl { Ok(entry.get_public_key(index)?) } + pub fn get_public_key_with_entry(entry: &StellarEntry, index: Option) -> Result { + Ok(entry.get_public_key(index)?) + } + pub fn delete_secret(print: &Print, entry_name: &str) -> Result<(), Error> { let entry = StellarEntry::new(entry_name)?; Ok(entry.delete_seed_phrase(print)?) @@ -63,6 +67,14 @@ mod secure_store_impl { let entry = StellarEntry::new(entry_name)?; Ok(entry.sign_data(data, hd_path)?) } + + pub fn sign_tx_data_with_entry( + entry: &StellarEntry, + hd_path: Option, + data: &[u8], + ) -> Result, Error> { + Ok(entry.sign_data(data, hd_path)?) + } } #[cfg(not(feature = "additional-libs"))] From 6e56981e18a8190a78473114bdefada0ef249220 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 2 Dec 2025 17:16:07 -0500 Subject: [PATCH 02/28] Create a SecureStoreEntry constructor --- cmd/soroban-cli/src/config/secret.rs | 8 ++------ cmd/soroban-cli/src/signer/mod.rs | 9 +++++++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index f125057bc1..67b0fc8679 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -7,7 +7,7 @@ use stellar_strkey::ed25519::{PrivateKey, PublicKey}; use crate::{ print::Print, - signer::{self, ledger, secure_store, LocalKey, SecureStoreEntry, Signer, SignerKind, keyring::StellarEntry}, + signer::{self, ledger, secure_store, LocalKey, SecureStoreEntry, Signer, SignerKind}, utils, }; @@ -151,11 +151,7 @@ impl Secret { .expect("uszie bigger than u32"); SignerKind::Ledger(ledger::new(hd_path).await?) } - Secret::SecureStore { entry_name } => SignerKind::SecureStore(SecureStoreEntry { - name: entry_name.clone(), - hd_path, - entry: StellarEntry::new(&entry_name).unwrap() //fixme! - }), + Secret::SecureStore { entry_name } => SignerKind::SecureStore(SecureStoreEntry::new(entry_name.to_string(), hd_path)), }; Ok(Signer { kind, print }) } diff --git a/cmd/soroban-cli/src/signer/mod.rs b/cmd/soroban-cli/src/signer/mod.rs index 764b8459bd..5807cb42d8 100644 --- a/cmd/soroban-cli/src/signer/mod.rs +++ b/cmd/soroban-cli/src/signer/mod.rs @@ -333,7 +333,16 @@ pub struct SecureStoreEntry { pub entry: StellarEntry, } +// still need the indirection of the secure_store mod so that we can handle things without the keyring crate impl SecureStoreEntry { + pub fn new(name: String, hd_path: Option) -> Self { + SecureStoreEntry { + name: name.clone(), + hd_path, + entry: StellarEntry::new(&name).unwrap() //fixme! + } + } + pub fn get_public_key(&self) -> Result { Ok(secure_store::get_public_key_with_entry(&self.entry, self.hd_path)?) } From beed676be1f15661ca4d2d382270421090fae0a4 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 2 Dec 2025 17:41:33 -0500 Subject: [PATCH 03/28] Cache the seedphrase in StellarEntry --- cmd/soroban-cli/src/signer/keyring.rs | 35 +++++++++++++++++++++------ cmd/soroban-cli/src/signer/mod.rs | 2 +- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/cmd/soroban-cli/src/signer/keyring.rs b/cmd/soroban-cli/src/signer/keyring.rs index 22b3d68843..9b05837e34 100644 --- a/cmd/soroban-cli/src/signer/keyring.rs +++ b/cmd/soroban-cli/src/signer/keyring.rs @@ -1,3 +1,6 @@ +use std::sync::Arc; +use std::sync::Mutex; + use crate::print::Print; use ed25519_dalek::Signer; use keyring::Entry; @@ -18,16 +21,26 @@ pub enum Error { } pub struct StellarEntry { + inner: Arc, + +} + +pub struct StellarEntryInner { name: String, #[cfg(feature = "additional-libs")] keyring: Entry, + #[allow(dead_code)] + cached_seed: Mutex>, } impl StellarEntry { pub fn new(name: &str) -> Result { Ok(StellarEntry { - name: name.to_string(), - keyring: Entry::new(name, &whoami::username())?, + inner: Arc::new(StellarEntryInner { + name: name.to_string(), + keyring: Entry::new(name, &whoami::username())?, + cached_seed: Mutex::new(None) + }) }) } @@ -35,12 +48,12 @@ impl StellarEntry { if let Ok(key) = self.get_public_key(None) { print.warnln(format!( "A key for {0} already exists in your operating system's secure store: {1}", - self.name, key + self.inner.name, key )); } else { print.infoln(format!( "Saving a new key to your operating system's secure store: {0}", - self.name + self.inner.name )); self.set_seed_phrase(seed_phrase)?; } @@ -50,13 +63,13 @@ impl StellarEntry { fn set_seed_phrase(&self, seed_phrase: SeedPhrase) -> Result<(), Error> { let mut data = seed_phrase.seed_phrase.into_phrase(); - self.keyring.set_password(&data)?; + self.inner.keyring.set_password(&data)?; data.zeroize(); Ok(()) } pub fn delete_seed_phrase(&self, print: &Print) -> Result<(), Error> { - match self.keyring.delete_credential() { + match self.inner.keyring.delete_credential() { Ok(()) => Ok(()), Err(e) => match e { keyring::Error::NoEntry => { @@ -69,7 +82,15 @@ impl StellarEntry { } fn get_seed_phrase(&self) -> Result { - Ok(self.keyring.get_password()?.parse()?) + let mut guard = self.inner.cached_seed.lock().unwrap(); + + if let Some(seed_phrase) = &*guard { + return Ok(seed_phrase.clone()); + } + + let seed_phrase: SeedPhrase = self.inner.keyring.get_password()?.parse()?; + *guard = Some(seed_phrase.clone()); + Ok(seed_phrase) } fn use_key( diff --git a/cmd/soroban-cli/src/signer/mod.rs b/cmd/soroban-cli/src/signer/mod.rs index 5807cb42d8..52294c9ea5 100644 --- a/cmd/soroban-cli/src/signer/mod.rs +++ b/cmd/soroban-cli/src/signer/mod.rs @@ -328,7 +328,7 @@ impl Lab { } pub struct SecureStoreEntry { - pub name: String, + pub name: String, //remove this pub hd_path: Option, pub entry: StellarEntry, } From 019ae150d68ab8d15285cb034bc4c8cb77eb3aa8 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 3 Dec 2025 11:04:37 -0500 Subject: [PATCH 04/28] Cache public key in StellarEntry --- cmd/soroban-cli/src/signer/keyring.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/cmd/soroban-cli/src/signer/keyring.rs b/cmd/soroban-cli/src/signer/keyring.rs index 9b05837e34..4e21afa647 100644 --- a/cmd/soroban-cli/src/signer/keyring.rs +++ b/cmd/soroban-cli/src/signer/keyring.rs @@ -22,7 +22,6 @@ pub enum Error { pub struct StellarEntry { inner: Arc, - } pub struct StellarEntryInner { @@ -31,6 +30,7 @@ pub struct StellarEntryInner { keyring: Entry, #[allow(dead_code)] cached_seed: Mutex>, + cached_public_key: Mutex>, } impl StellarEntry { @@ -39,7 +39,8 @@ impl StellarEntry { inner: Arc::new(StellarEntryInner { name: name.to_string(), keyring: Entry::new(name, &whoami::username())?, - cached_seed: Mutex::new(None) + cached_seed: Mutex::new(None), + cached_public_key: Mutex::new(None) }) }) } @@ -118,14 +119,23 @@ impl StellarEntry { &self, hd_path: Option, ) -> Result { - self.use_key( + let mut guard = self.inner.cached_public_key.lock().unwrap(); + + if let Some(key) = &*guard { + return Ok(key.clone()); + } + + let public_key = self.use_key( |keypair| { Ok(stellar_strkey::ed25519::PublicKey( *keypair.verifying_key().as_bytes(), )) }, hd_path, - ) + )?; + + *guard = Some(public_key.clone()); + Ok(public_key) } pub fn sign_data(&self, data: &[u8], hd_path: Option) -> Result, Error> { From bf61b15721f16c89910ef4ab91b62de4f1fb7039 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 4 Dec 2025 13:19:01 -0500 Subject: [PATCH 05/28] Pull SecureStoreEntry into its own mod --- cmd/soroban-cli/src/config/secret.rs | 2 +- cmd/soroban-cli/src/signer/mod.rs | 47 ++-------------- .../src/signer/secure_store_entry.rs | 54 +++++++++++++++++++ 3 files changed, 60 insertions(+), 43 deletions(-) create mode 100644 cmd/soroban-cli/src/signer/secure_store_entry.rs diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index 67b0fc8679..fcef85bd6c 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -7,7 +7,7 @@ use stellar_strkey::ed25519::{PrivateKey, PublicKey}; use crate::{ print::Print, - signer::{self, ledger, secure_store, LocalKey, SecureStoreEntry, Signer, SignerKind}, + signer::{self, ledger, secure_store, LocalKey, secure_store_entry::SecureStoreEntry, Signer, SignerKind}, utils, }; diff --git a/cmd/soroban-cli/src/signer/mod.rs b/cmd/soroban-cli/src/signer/mod.rs index 52294c9ea5..7ddcf43e34 100644 --- a/cmd/soroban-cli/src/signer/mod.rs +++ b/cmd/soroban-cli/src/signer/mod.rs @@ -1,4 +1,4 @@ -use crate::{signer::keyring::StellarEntry, xdr::{ +use crate::{signer::secure_store_entry::SecureStoreEntry, xdr::{ self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, @@ -15,6 +15,7 @@ pub mod ledger; #[cfg(feature = "additional-libs")] pub mod keyring; pub mod secure_store; +pub mod secure_store_entry; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -39,7 +40,7 @@ pub enum Error { #[error("Returning a signature from Lab is not yet supported; Transaction can be found and submitted in lab")] ReturningSignatureFromLab, #[error(transparent)] - SecureStore(#[from] secure_store::Error), + SecureStoreEntry(#[from] secure_store_entry::Error), #[error(transparent)] Ledger(#[from] ledger::Error), #[error(transparent)] @@ -281,7 +282,7 @@ impl Signer { SignerKind::Local(local_key) => local_key.sign_payload(payload), SignerKind::Ledger(_ledger) => todo!("ledger device is not implemented"), SignerKind::Lab => Err(Error::ReturningSignatureFromLab), - SignerKind::SecureStore(secure_store_entry) => secure_store_entry.sign_payload(payload), + SignerKind::SecureStore(secure_store_entry) => Ok(secure_store_entry.sign_payload(payload)?), } } } @@ -325,42 +326,4 @@ impl Lab { Err(Error::ReturningSignatureFromLab) } -} - -pub struct SecureStoreEntry { - pub name: String, //remove this - pub hd_path: Option, - pub entry: StellarEntry, -} - -// still need the indirection of the secure_store mod so that we can handle things without the keyring crate -impl SecureStoreEntry { - pub fn new(name: String, hd_path: Option) -> Self { - SecureStoreEntry { - name: name.clone(), - hd_path, - entry: StellarEntry::new(&name).unwrap() //fixme! - } - } - - pub fn get_public_key(&self) -> Result { - Ok(secure_store::get_public_key_with_entry(&self.entry, self.hd_path)?) - } - - pub fn sign_tx_hash(&self, tx_hash: [u8; 32]) -> Result { - let hint = SignatureHint( - secure_store::get_public_key_with_entry(&self.entry, self.hd_path)?.0[28..].try_into()?, - ); - - let signed_tx_hash = secure_store::sign_tx_data_with_entry(&self.entry, self.hd_path, &tx_hash)?; - - let signature = Signature(signed_tx_hash.clone().try_into()?); - Ok(DecoratedSignature { hint, signature }) - } - - pub fn sign_payload(&self, payload: [u8; 32]) -> Result { - let signed_bytes = secure_store::sign_tx_data(&self.name, self.hd_path, &payload)?; - let sig = Ed25519Signature::from_bytes(signed_bytes.as_slice().try_into()?); - Ok(sig) - } -} +} \ No newline at end of file diff --git a/cmd/soroban-cli/src/signer/secure_store_entry.rs b/cmd/soroban-cli/src/signer/secure_store_entry.rs new file mode 100644 index 0000000000..3550c4c29a --- /dev/null +++ b/cmd/soroban-cli/src/signer/secure_store_entry.rs @@ -0,0 +1,54 @@ +use crate::{ + signer::{keyring::StellarEntry, secure_store}, + xdr::{self, DecoratedSignature, Signature, SignatureHint} +}; + +use ed25519_dalek::Signature as Ed25519Signature; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + SecureStore(#[from] secure_store::Error), + #[error(transparent)] + TryFromSlice(#[from] std::array::TryFromSliceError), + #[error(transparent)] + Xdr(#[from] xdr::Error), +} + +pub struct SecureStoreEntry { + pub name: String, //remove this + pub hd_path: Option, + pub entry: StellarEntry, +} + +// still need the indirection of the secure_store mod so that we can handle things without the keyring crate +impl SecureStoreEntry { + pub fn new(name: String, hd_path: Option) -> Self { + SecureStoreEntry { + name: name.clone(), + hd_path, + entry: StellarEntry::new(&name).unwrap() //fixme! + } + } + + pub fn get_public_key(&self) -> Result { + Ok(secure_store::get_public_key_with_entry(&self.entry, self.hd_path)?) + } + + pub fn sign_tx_hash(&self, tx_hash: [u8; 32]) -> Result { + let hint = SignatureHint( + secure_store::get_public_key_with_entry(&self.entry, self.hd_path)?.0[28..].try_into()?, + ); + + let signed_tx_hash = secure_store::sign_tx_data_with_entry(&self.entry, self.hd_path, &tx_hash)?; + + let signature = Signature(signed_tx_hash.clone().try_into()?); + Ok(DecoratedSignature { hint, signature }) + } + + pub fn sign_payload(&self, payload: [u8; 32]) -> Result { + let signed_bytes = secure_store::sign_tx_data(&self.name, self.hd_path, &payload)?; + let sig = Ed25519Signature::from_bytes(signed_bytes.as_slice().try_into()?); + Ok(sig) + } +} From ff5bfb8baba94c654204ff6f917d87901377b014 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 4 Dec 2025 14:35:28 -0500 Subject: [PATCH 06/28] And remove the need for separate secure_store mod --- cmd/soroban-cli/src/commands/keys/add.rs | 6 +- cmd/soroban-cli/src/commands/keys/generate.rs | 6 +- cmd/soroban-cli/src/config/locator.rs | 11 +- cmd/soroban-cli/src/config/secret.rs | 9 +- cmd/soroban-cli/src/signer/mod.rs | 1 - cmd/soroban-cli/src/signer/secure_store.rs | 107 ------------------ .../src/signer/secure_store_entry.rs | 85 ++++++++++++-- 7 files changed, 92 insertions(+), 133 deletions(-) delete mode 100644 cmd/soroban-cli/src/signer/secure_store.rs diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs index d8ca60f795..67a82d5750 100644 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -11,7 +11,7 @@ use crate::{ secret::{self, Secret}, }, print::Print, - signer::secure_store, + signer::{secure_store_entry::{self, SecureStoreEntry}}, }; #[derive(thiserror::Error, Debug)] @@ -24,7 +24,7 @@ pub enum Error { Config(#[from] locator::Error), #[error(transparent)] - SecureStore(#[from] secure_store::Error), + SecureStoreEntry(#[from] secure_store_entry::Error), #[error(transparent)] SeedPhrase(#[from] sep5::error::Error), @@ -97,7 +97,7 @@ impl Cmd { let seed_phrase: SeedPhrase = secret_key.parse()?; - let secret = secure_store::save_secret(print, &self.name, &seed_phrase)?; + let secret = SecureStoreEntry::create_and_save(&self.name, &seed_phrase, print)?; Ok(secret.parse()?) } else { let prompt = "Type a secret key or 12/24 word seed phrase:"; diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs index b92b80f82b..73747952ee 100644 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ b/cmd/soroban-cli/src/commands/keys/generate.rs @@ -6,7 +6,7 @@ use super::super::config::{ secret::{self, Secret}, }; -use crate::{commands::global, config::address::KeyName, print::Print, signer::secure_store}; +use crate::{commands::global, config::address::KeyName, print::Print, signer::{secure_store_entry::{self, SecureStoreEntry}}}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -23,7 +23,7 @@ pub enum Error { IdentityAlreadyExists(String), #[error(transparent)] - SecureStore(#[from] secure_store::Error), + SecureStoreEntry(#[from] secure_store_entry::Error), } #[derive(Debug, clap::Parser, Clone)] @@ -111,7 +111,7 @@ impl Cmd { fn secret(&self, print: &Print) -> Result { let seed_phrase = self.seed_phrase()?; if self.secure_store { - let secret = secure_store::save_secret(print, &self.name, &seed_phrase)?; + let secret = SecureStoreEntry::create_and_save(&self.name, &seed_phrase, print)?; Ok(secret.parse()?) } else if self.as_secret { let secret: Secret = seed_phrase.into(); diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 2bc700d176..7ad892e49b 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -13,11 +13,7 @@ use std::{ use stellar_strkey::{Contract, DecodeError}; use crate::{ - commands::{global, HEADING_GLOBAL}, - print::Print, - signer::secure_store, - utils::find_config_dir, - xdr, Pwd, + Pwd, commands::{HEADING_GLOBAL, global}, print::Print, signer::{secure_store_entry::{self, SecureStoreEntry}}, utils::find_config_dir, xdr }; use super::{ @@ -97,7 +93,7 @@ pub enum Error { #[error("Key cannot {0} cannot overlap with contract alias")] KeyCannotOverlapWithContractAlias(String), #[error(transparent)] - SecureStore(#[from] secure_store::Error), + SecureStoreEntry(#[from] secure_store_entry::Error), #[error("Only private keys and seed phrases are supported for getting private keys {0}")] SecretKeyOnly(String), #[error(transparent)] @@ -305,7 +301,8 @@ impl Args { let identity = self.read_identity(name)?; if let Key::Secret(Secret::SecureStore { entry_name }) = identity { - secure_store::delete_secret(&print, &entry_name)?; + let secure_store_entry = SecureStoreEntry::new(entry_name, None); + secure_store_entry.delete_secret(&print)?; } print.infoln("Removing the key's cli config file"); diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index fcef85bd6c..897271588b 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -7,7 +7,7 @@ use stellar_strkey::ed25519::{PrivateKey, PublicKey}; use crate::{ print::Print, - signer::{self, ledger, secure_store, LocalKey, secure_store_entry::SecureStoreEntry, Signer, SignerKind}, + signer::{self, LocalKey, Signer, SignerKind, ledger, secure_store_entry::{self, SecureStoreEntry}}, utils, }; @@ -28,7 +28,7 @@ pub enum Error { #[error("Ledger does not reveal secret key")] LedgerDoesNotRevealSecretKey, #[error(transparent)] - SecureStore(#[from] secure_store::Error), + SecureStore(#[from] secure_store_entry::Error), #[error("Secure Store does not reveal secret key")] SecureStoreDoesNotRevealSecretKey, #[error(transparent)] @@ -78,7 +78,7 @@ impl FromStr for Secret { }) } else if s == "ledger" { Ok(Secret::Ledger) - } else if s.starts_with(secure_store::ENTRY_PREFIX) { + } else if s.starts_with(secure_store_entry::ENTRY_PREFIX) { Ok(Secret::SecureStore { entry_name: s.to_string(), }) @@ -129,7 +129,8 @@ impl Secret { pub fn public_key(&self, index: Option) -> Result { if let Secret::SecureStore { entry_name } = self { - Ok(secure_store::get_public_key(entry_name, index)?) + let entry = SecureStoreEntry::new(entry_name.to_string(), index); + Ok(entry.get_public_key()?) } else { let key = self.key_pair(index)?; Ok(stellar_strkey::ed25519::PublicKey::from_payload( diff --git a/cmd/soroban-cli/src/signer/mod.rs b/cmd/soroban-cli/src/signer/mod.rs index 7ddcf43e34..1bd5f4eff8 100644 --- a/cmd/soroban-cli/src/signer/mod.rs +++ b/cmd/soroban-cli/src/signer/mod.rs @@ -14,7 +14,6 @@ pub mod ledger; #[cfg(feature = "additional-libs")] pub mod keyring; -pub mod secure_store; pub mod secure_store_entry; #[derive(thiserror::Error, Debug)] diff --git a/cmd/soroban-cli/src/signer/secure_store.rs b/cmd/soroban-cli/src/signer/secure_store.rs deleted file mode 100644 index 183e64bdb5..0000000000 --- a/cmd/soroban-cli/src/signer/secure_store.rs +++ /dev/null @@ -1,107 +0,0 @@ -use sep5::SeedPhrase; -use stellar_strkey::ed25519::PublicKey; - -use crate::print::Print; - -#[cfg(feature = "additional-libs")] -use crate::signer::keyring::{self, StellarEntry}; - -pub(crate) const ENTRY_PREFIX: &str = "secure_store:"; - -pub use secure_store_impl::*; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[cfg(feature = "additional-libs")] - #[error(transparent)] - Keyring(#[from] keyring::Error), - - #[error("Storing an existing private key in Secure Store is not supported")] - DoesNotSupportPrivateKey, - - #[error(transparent)] - SeedPhrase(#[from] sep5::Error), - - #[error("Secure Store keys are not allowed: additional-libs feature must be enabled")] - FeatureNotEnabled, -} - -#[cfg(feature = "additional-libs")] -mod secure_store_impl { - use super::{Error, Print, PublicKey, SeedPhrase, StellarEntry, ENTRY_PREFIX}; - const ENTRY_SERVICE: &str = "org.stellar.cli"; - - pub fn get_public_key(entry_name: &str, index: Option) -> Result { - let entry = StellarEntry::new(entry_name)?; - Ok(entry.get_public_key(index)?) - } - - pub fn get_public_key_with_entry(entry: &StellarEntry, index: Option) -> Result { - Ok(entry.get_public_key(index)?) - } - - pub fn delete_secret(print: &Print, entry_name: &str) -> Result<(), Error> { - let entry = StellarEntry::new(entry_name)?; - Ok(entry.delete_seed_phrase(print)?) - } - - pub fn save_secret( - print: &Print, - entry_name: &str, - seed_phrase: &SeedPhrase, - ) -> Result { - // secure_store:org.stellar.cli: - let entry_name_with_prefix = format!("{ENTRY_PREFIX}{ENTRY_SERVICE}-{entry_name}"); - - let entry = StellarEntry::new(&entry_name_with_prefix)?; - entry.write(seed_phrase.clone(), print)?; - - Ok(entry_name_with_prefix) - } - - pub fn sign_tx_data( - entry_name: &str, - hd_path: Option, - data: &[u8], - ) -> Result, Error> { - let entry = StellarEntry::new(entry_name)?; - Ok(entry.sign_data(data, hd_path)?) - } - - pub fn sign_tx_data_with_entry( - entry: &StellarEntry, - hd_path: Option, - data: &[u8], - ) -> Result, Error> { - Ok(entry.sign_data(data, hd_path)?) - } -} - -#[cfg(not(feature = "additional-libs"))] -mod secure_store_impl { - use super::{Error, Print, PublicKey, SeedPhrase}; - - pub fn get_public_key(_entry_name: &str, _index: Option) -> Result { - Err(Error::FeatureNotEnabled) - } - - pub fn delete_secret(_print: &Print, _entry_name: &str) -> Result<(), Error> { - Err(Error::FeatureNotEnabled) - } - - pub fn save_secret( - _print: &Print, - _entry_name: &str, - _seed_phrase: &SeedPhrase, - ) -> Result { - Err(Error::FeatureNotEnabled) - } - - pub fn sign_tx_data( - _entry_name: &str, - _hd_path: Option, - _data: &[u8], - ) -> Result, Error> { - Err(Error::FeatureNotEnabled) - } -} diff --git a/cmd/soroban-cli/src/signer/secure_store_entry.rs b/cmd/soroban-cli/src/signer/secure_store_entry.rs index 3550c4c29a..61359b7f81 100644 --- a/cmd/soroban-cli/src/signer/secure_store_entry.rs +++ b/cmd/soroban-cli/src/signer/secure_store_entry.rs @@ -1,18 +1,31 @@ use crate::{ - signer::{keyring::StellarEntry, secure_store}, + signer::keyring::StellarEntry, xdr::{self, DecoratedSignature, Signature, SignatureHint} }; use ed25519_dalek::Signature as Ed25519Signature; +#[cfg(feature = "additional-libs")] +use sep5::SeedPhrase; +#[cfg(feature = "additional-libs")] +use crate::{print::Print, signer::keyring}; + +pub(crate) const ENTRY_PREFIX: &str = "secure_store:"; +const ENTRY_SERVICE: &str = "org.stellar.cli"; #[derive(thiserror::Error, Debug)] pub enum Error { + #[cfg(feature = "additional-libs")] #[error(transparent)] - SecureStore(#[from] secure_store::Error), + Keyring(#[from] keyring::Error), + #[error(transparent)] TryFromSlice(#[from] std::array::TryFromSliceError), + #[error(transparent)] Xdr(#[from] xdr::Error), + + #[error("Secure Store keys are not allowed: additional-libs feature must be enabled")] + FeatureNotEnabled, } pub struct SecureStoreEntry { @@ -21,10 +34,10 @@ pub struct SecureStoreEntry { pub entry: StellarEntry, } -// still need the indirection of the secure_store mod so that we can handle things without the keyring crate +#[cfg(feature = "additional-libs")] impl SecureStoreEntry { pub fn new(name: String, hd_path: Option) -> Self { - SecureStoreEntry { + Self { name: name.clone(), hd_path, entry: StellarEntry::new(&name).unwrap() //fixme! @@ -32,23 +45,79 @@ impl SecureStoreEntry { } pub fn get_public_key(&self) -> Result { - Ok(secure_store::get_public_key_with_entry(&self.entry, self.hd_path)?) + Ok(self.entry.get_public_key(self.hd_path)?) + } + + + pub fn delete_secret(&self, print: &Print) -> Result<(), Error> { + Ok(self.entry.delete_seed_phrase(print)?) + } + + pub fn create_and_save(entry_name: &str, seed_phrase: &SeedPhrase, print: &Print) -> Result { + let entry_name_with_prefix = format!("{ENTRY_PREFIX}{ENTRY_SERVICE}-{entry_name}"); + + let s = Self::new( entry_name_with_prefix.clone(), None); + s.entry.write(seed_phrase.clone(), print)?; + + Ok(entry_name_with_prefix) + } + + pub fn sign_tx_data( + &self, + data: &[u8], + ) -> Result, Error> { + Ok(self.entry.sign_data(data, self.hd_path)?) } pub fn sign_tx_hash(&self, tx_hash: [u8; 32]) -> Result { let hint = SignatureHint( - secure_store::get_public_key_with_entry(&self.entry, self.hd_path)?.0[28..].try_into()?, + self.get_public_key()?.0[28..].try_into()?, ); - let signed_tx_hash = secure_store::sign_tx_data_with_entry(&self.entry, self.hd_path, &tx_hash)?; + let signed_tx_hash = self.sign_tx_data(&tx_hash)?; let signature = Signature(signed_tx_hash.clone().try_into()?); Ok(DecoratedSignature { hint, signature }) } pub fn sign_payload(&self, payload: [u8; 32]) -> Result { - let signed_bytes = secure_store::sign_tx_data(&self.name, self.hd_path, &payload)?; + let signed_bytes = self.sign_tx_data(&payload)?; + let sig = Ed25519Signature::from_bytes(signed_bytes.as_slice().try_into()?); Ok(sig) } } + +#[cfg(not(feature = "additional-libs"))] +impl SecureStoreEntry { + pub fn new(name: String, hd_path: Option) -> Self { + SecureStoreEntry { + name: name.clone(), + hd_path, + entry: StellarEntry::new(&name).unwrap() //fixme! + } + } + pub fn get_public_key(_entry_name: &str, _index: Option) -> Result { + Err(Error::FeatureNotEnabled) + } + + pub fn delete_secret(_print: &Print, _entry_name: &str) -> Result<(), Error> { + Err(Error::FeatureNotEnabled) + } + + pub fn save_secret( + _print: &Print, + _entry_name: &str, + _seed_phrase: &SeedPhrase, + ) -> Result { + Err(Error::FeatureNotEnabled) + } + + pub fn sign_tx_data( + _entry_name: &str, + _hd_path: Option, + _data: &[u8], + ) -> Result, Error> { + Err(Error::FeatureNotEnabled) + } +} From 3cb4abf9eb429d57edca6d058125026dd1f10f00 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 4 Dec 2025 15:54:20 -0500 Subject: [PATCH 07/28] Cargo fmt --- cmd/soroban-cli/src/commands/keys/add.rs | 2 +- cmd/soroban-cli/src/commands/keys/generate.rs | 7 +++- cmd/soroban-cli/src/config/locator.rs | 6 +++- cmd/soroban-cli/src/config/secret.rs | 10 ++++-- cmd/soroban-cli/src/signer/keyring.rs | 4 +-- cmd/soroban-cli/src/signer/mod.rs | 24 ++++++++----- .../src/signer/secure_store_entry.rs | 36 +++++++++---------- 7 files changed, 54 insertions(+), 35 deletions(-) diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs index 67a82d5750..c64b1a5125 100644 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -11,7 +11,7 @@ use crate::{ secret::{self, Secret}, }, print::Print, - signer::{secure_store_entry::{self, SecureStoreEntry}}, + signer::secure_store_entry::{self, SecureStoreEntry}, }; #[derive(thiserror::Error, Debug)] diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs index 73747952ee..b0b02cdb3e 100644 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ b/cmd/soroban-cli/src/commands/keys/generate.rs @@ -6,7 +6,12 @@ use super::super::config::{ secret::{self, Secret}, }; -use crate::{commands::global, config::address::KeyName, print::Print, signer::{secure_store_entry::{self, SecureStoreEntry}}}; +use crate::{ + commands::global, + config::address::KeyName, + print::Print, + signer::secure_store_entry::{self, SecureStoreEntry}, +}; #[derive(thiserror::Error, Debug)] pub enum Error { diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 7ad892e49b..3251718b68 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -13,7 +13,11 @@ use std::{ use stellar_strkey::{Contract, DecodeError}; use crate::{ - Pwd, commands::{HEADING_GLOBAL, global}, print::Print, signer::{secure_store_entry::{self, SecureStoreEntry}}, utils::find_config_dir, xdr + commands::{global, HEADING_GLOBAL}, + print::Print, + signer::secure_store_entry::{self, SecureStoreEntry}, + utils::find_config_dir, + xdr, Pwd, }; use super::{ diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index 897271588b..a83638b501 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -7,7 +7,11 @@ use stellar_strkey::ed25519::{PrivateKey, PublicKey}; use crate::{ print::Print, - signer::{self, LocalKey, Signer, SignerKind, ledger, secure_store_entry::{self, SecureStoreEntry}}, + signer::{ + self, ledger, + secure_store_entry::{self, SecureStoreEntry}, + LocalKey, Signer, SignerKind, + }, utils, }; @@ -152,7 +156,9 @@ impl Secret { .expect("uszie bigger than u32"); SignerKind::Ledger(ledger::new(hd_path).await?) } - Secret::SecureStore { entry_name } => SignerKind::SecureStore(SecureStoreEntry::new(entry_name.to_string(), hd_path)), + Secret::SecureStore { entry_name } => { + SignerKind::SecureStore(SecureStoreEntry::new(entry_name.to_string(), hd_path)) + } }; Ok(Signer { kind, print }) } diff --git a/cmd/soroban-cli/src/signer/keyring.rs b/cmd/soroban-cli/src/signer/keyring.rs index 4e21afa647..d51fca745b 100644 --- a/cmd/soroban-cli/src/signer/keyring.rs +++ b/cmd/soroban-cli/src/signer/keyring.rs @@ -40,8 +40,8 @@ impl StellarEntry { name: name.to_string(), keyring: Entry::new(name, &whoami::username())?, cached_seed: Mutex::new(None), - cached_public_key: Mutex::new(None) - }) + cached_public_key: Mutex::new(None), + }), }) } diff --git a/cmd/soroban-cli/src/signer/mod.rs b/cmd/soroban-cli/src/signer/mod.rs index 1bd5f4eff8..a701f9ac46 100644 --- a/cmd/soroban-cli/src/signer/mod.rs +++ b/cmd/soroban-cli/src/signer/mod.rs @@ -1,10 +1,14 @@ -use crate::{signer::secure_store_entry::SecureStoreEntry, xdr::{ - self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, - InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, - ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, - SorobanAuthorizedFunction, SorobanCredentials, Transaction, TransactionEnvelope, - TransactionV1Envelope, Uint256, VecM, WriteXdr, -}}; +use crate::{ + signer::secure_store_entry::SecureStoreEntry, + xdr::{ + self, AccountId, DecoratedSignature, Hash, HashIdPreimage, + HashIdPreimageSorobanAuthorization, InvokeHostFunctionOp, Limits, Operation, OperationBody, + PublicKey, ScAddress, ScMap, ScSymbol, ScVal, Signature, SignatureHint, + SorobanAddressCredentials, SorobanAuthorizationEntry, SorobanAuthorizedFunction, + SorobanCredentials, Transaction, TransactionEnvelope, TransactionV1Envelope, Uint256, VecM, + WriteXdr, + }, +}; use ed25519_dalek::{ed25519::signature::Signer as _, Signature as Ed25519Signature}; use sha2::{Digest, Sha256}; @@ -281,7 +285,9 @@ impl Signer { SignerKind::Local(local_key) => local_key.sign_payload(payload), SignerKind::Ledger(_ledger) => todo!("ledger device is not implemented"), SignerKind::Lab => Err(Error::ReturningSignatureFromLab), - SignerKind::SecureStore(secure_store_entry) => Ok(secure_store_entry.sign_payload(payload)?), + SignerKind::SecureStore(secure_store_entry) => { + Ok(secure_store_entry.sign_payload(payload)?) + } } } } @@ -325,4 +331,4 @@ impl Lab { Err(Error::ReturningSignatureFromLab) } -} \ No newline at end of file +} diff --git a/cmd/soroban-cli/src/signer/secure_store_entry.rs b/cmd/soroban-cli/src/signer/secure_store_entry.rs index 61359b7f81..ac4810a3f3 100644 --- a/cmd/soroban-cli/src/signer/secure_store_entry.rs +++ b/cmd/soroban-cli/src/signer/secure_store_entry.rs @@ -1,13 +1,13 @@ use crate::{ signer::keyring::StellarEntry, - xdr::{self, DecoratedSignature, Signature, SignatureHint} + xdr::{self, DecoratedSignature, Signature, SignatureHint}, }; +#[cfg(feature = "additional-libs")] +use crate::{print::Print, signer::keyring}; use ed25519_dalek::Signature as Ed25519Signature; #[cfg(feature = "additional-libs")] use sep5::SeedPhrase; -#[cfg(feature = "additional-libs")] -use crate::{print::Print, signer::keyring}; pub(crate) const ENTRY_PREFIX: &str = "secure_store:"; const ENTRY_SERVICE: &str = "org.stellar.cli"; @@ -38,9 +38,9 @@ pub struct SecureStoreEntry { impl SecureStoreEntry { pub fn new(name: String, hd_path: Option) -> Self { Self { - name: name.clone(), - hd_path, - entry: StellarEntry::new(&name).unwrap() //fixme! + name: name.clone(), + hd_path, + entry: StellarEntry::new(&name).unwrap(), //fixme! } } @@ -48,31 +48,29 @@ impl SecureStoreEntry { Ok(self.entry.get_public_key(self.hd_path)?) } - pub fn delete_secret(&self, print: &Print) -> Result<(), Error> { Ok(self.entry.delete_seed_phrase(print)?) } - pub fn create_and_save(entry_name: &str, seed_phrase: &SeedPhrase, print: &Print) -> Result { + pub fn create_and_save( + entry_name: &str, + seed_phrase: &SeedPhrase, + print: &Print, + ) -> Result { let entry_name_with_prefix = format!("{ENTRY_PREFIX}{ENTRY_SERVICE}-{entry_name}"); - let s = Self::new( entry_name_with_prefix.clone(), None); + let s = Self::new(entry_name_with_prefix.clone(), None); s.entry.write(seed_phrase.clone(), print)?; Ok(entry_name_with_prefix) } - pub fn sign_tx_data( - &self, - data: &[u8], - ) -> Result, Error> { + pub fn sign_tx_data(&self, data: &[u8]) -> Result, Error> { Ok(self.entry.sign_data(data, self.hd_path)?) } pub fn sign_tx_hash(&self, tx_hash: [u8; 32]) -> Result { - let hint = SignatureHint( - self.get_public_key()?.0[28..].try_into()?, - ); + let hint = SignatureHint(self.get_public_key()?.0[28..].try_into()?); let signed_tx_hash = self.sign_tx_data(&tx_hash)?; @@ -92,9 +90,9 @@ impl SecureStoreEntry { impl SecureStoreEntry { pub fn new(name: String, hd_path: Option) -> Self { SecureStoreEntry { - name: name.clone(), - hd_path, - entry: StellarEntry::new(&name).unwrap() //fixme! + name: name.clone(), + hd_path, + entry: StellarEntry::new(&name).unwrap(), //fixme! } } pub fn get_public_key(_entry_name: &str, _index: Option) -> Result { From 426062f668cd22b56b2653a1fa4346b8dda86dd4 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 4 Dec 2025 15:54:40 -0500 Subject: [PATCH 08/28] Fix typo --- cmd/soroban-cli/src/config/secret.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index a83638b501..c8708a8c2b 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -153,7 +153,7 @@ impl Secret { let hd_path: u32 = hd_path .unwrap_or_default() .try_into() - .expect("uszie bigger than u32"); + .expect("usize bigger than u32"); SignerKind::Ledger(ledger::new(hd_path).await?) } Secret::SecureStore { entry_name } => { From 5815575992a4de0e844da76aa9f181f8b9862695 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:58:31 -0500 Subject: [PATCH 09/28] WIP - cache key lookup in locator --- cmd/crates/soroban-test/src/lib.rs | 3 +- cmd/soroban-cli/src/config/key.rs | 6 ++-- cmd/soroban-cli/src/config/locator.rs | 34 +++++++++++++++++-- cmd/soroban-cli/src/config/secret.rs | 16 ++++++--- cmd/soroban-cli/src/config/upgrade_check.rs | 4 ++- cmd/soroban-cli/src/signer/keyring.rs | 2 ++ .../src/signer/secure_store_entry.rs | 1 + 7 files changed, 53 insertions(+), 13 deletions(-) diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index da46b86b8f..a673e5e6f8 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -26,7 +26,7 @@ use std::{ ffi::OsString, fmt::Display, - path::{Path, PathBuf}, + path::{Path, PathBuf}, sync::OnceLock, }; use assert_cmd::{assert::Assert, Command}; @@ -277,6 +277,7 @@ impl TestEnv { locator: config::locator::Args { global: false, config_dir, + cached_keys: OnceLock::new() }, sign_with: config::sign_with::Args { sign_with_key: None, diff --git a/cmd/soroban-cli/src/config/key.rs b/cmd/soroban-cli/src/config/key.rs index 9d21ad0615..b02314c398 100644 --- a/cmd/soroban-cli/src/config/key.rs +++ b/cmd/soroban-cli/src/config/key.rs @@ -17,7 +17,7 @@ pub enum Error { Parse, } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub enum Key { #[serde(rename = "public_key")] PublicKey(Public), @@ -85,7 +85,7 @@ impl From<&stellar_strkey::ed25519::PublicKey> for Key { } } -#[derive(Debug, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)] +#[derive(Debug, Clone, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)] pub struct Public(pub stellar_strkey::ed25519::PublicKey); impl FromStr for Public { @@ -111,7 +111,7 @@ impl From<&Public> for stellar_strkey::ed25519::MuxedAccount { } } -#[derive(Debug, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)] +#[derive(Debug, Clone, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)] pub struct MuxedAccount(pub stellar_strkey::ed25519::MuxedAccount); impl FromStr for MuxedAccount { diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 3251718b68..24dce81e08 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -104,6 +104,8 @@ pub enum Error { Key(#[from] key::Error), } +pub type CachedKeys = std::collections::HashMap; + #[derive(Debug, clap::Args, Default, Clone)] #[group(skip)] pub struct Args { @@ -115,6 +117,9 @@ pub struct Args { /// Contains configuration files, aliases, and other persistent settings. #[arg(long, global = true, help_heading = HEADING_GLOBAL)] pub config_dir: Option, + + #[clap(skip)] + pub cached_keys: std::sync::OnceLock>>, } pub enum Location { @@ -269,11 +274,34 @@ impl Args { } pub fn read_key(&self, key_or_name: &str) -> Result { - key_or_name + // 1. Check cache + if let Some(arc) = self.cached_keys.get() { + let map = arc.lock().unwrap(); + println!("the keys {:?}", map); + if let Some(k) = map.get(key_or_name) { + println!("found the one we want!"); + return Ok(k.clone()); + } + } + + println!("getting the key"); + // 2. Compute key normally + let key = key_or_name .parse() - .or_else(|_| self.read_identity(key_or_name)) + .or_else(|_| self.read_identity(key_or_name))?; + + // 3. Insert into cache + let arc = self.cached_keys.get_or_init(|| { + std::sync::Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())) + }); + + let mut map = arc.lock().unwrap(); + map.insert(key_or_name.to_string(), key.clone()); + + Ok(key) } + // and then we read from config again here... so it is a new instance of a Secret pub fn get_secret_key(&self, key_or_name: &str) -> Result { match self.read_key(key_or_name)? { Key::Secret(s) => Ok(s), @@ -304,7 +332,7 @@ impl Args { let print = Print::new(global_args.quiet); let identity = self.read_identity(name)?; - if let Key::Secret(Secret::SecureStore { entry_name }) = identity { + if let Key::Secret(Secret::SecureStore { entry_name, .. }) = identity { let secure_store_entry = SecureStoreEntry::new(entry_name, None); secure_store_entry.delete_secret(&print)?; } diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index c8708a8c2b..848bb911f2 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -1,6 +1,6 @@ use clap::arg; use serde::{Deserialize, Serialize}; -use std::str::FromStr; +use std::{str::FromStr, sync::{Arc, OnceLock}}; use sep5::SeedPhrase; use stellar_strkey::ed25519::{PrivateKey, PublicKey}; @@ -59,13 +59,18 @@ pub struct Args { pub secure_store: bool, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum Secret { SecretKey { secret_key: String }, SeedPhrase { seed_phrase: String }, Ledger, - SecureStore { entry_name: String }, + SecureStore { + entry_name: String, + #[serde(skip)] + #[serde(default)] + cached_entry: Arc>, + } } impl FromStr for Secret { @@ -85,6 +90,7 @@ impl FromStr for Secret { } else if s.starts_with(secure_store_entry::ENTRY_PREFIX) { Ok(Secret::SecureStore { entry_name: s.to_string(), + cached_entry: OnceLock::new().into(), }) } else { Err(Error::InvalidSecretOrSeedPhrase) @@ -132,7 +138,7 @@ impl Secret { } pub fn public_key(&self, index: Option) -> Result { - if let Secret::SecureStore { entry_name } = self { + if let Secret::SecureStore { entry_name , .. } = self { let entry = SecureStoreEntry::new(entry_name.to_string(), index); Ok(entry.get_public_key()?) } else { @@ -156,7 +162,7 @@ impl Secret { .expect("usize bigger than u32"); SignerKind::Ledger(ledger::new(hd_path).await?) } - Secret::SecureStore { entry_name } => { + Secret::SecureStore { entry_name , .. } => { SignerKind::SecureStore(SecureStoreEntry::new(entry_name.to_string(), hd_path)) } }; diff --git a/cmd/soroban-cli/src/config/upgrade_check.rs b/cmd/soroban-cli/src/config/upgrade_check.rs index ea870b6775..902e81802e 100644 --- a/cmd/soroban-cli/src/config/upgrade_check.rs +++ b/cmd/soroban-cli/src/config/upgrade_check.rs @@ -4,7 +4,7 @@ use jsonrpsee_core::Serialize; use semver::Version; use serde::Deserialize; use serde_json; -use std::fs; +use std::{fs, sync::OnceLock}; const FILE_NAME: &str = "upgrade_check.json"; @@ -38,6 +38,7 @@ impl UpgradeCheck { let locator = locator::Args { global: false, config_dir: None, + cached_keys: OnceLock::new() }; let path = locator.global_config_path()?.join(FILE_NAME); if !path.exists() { @@ -53,6 +54,7 @@ impl UpgradeCheck { let locator = locator::Args { global: false, config_dir: None, + cached_keys: OnceLock::new() }; let path = locator.global_config_path()?.join(FILE_NAME); let path = locator::ensure_directory(path)?; diff --git a/cmd/soroban-cli/src/signer/keyring.rs b/cmd/soroban-cli/src/signer/keyring.rs index d51fca745b..2c1e798316 100644 --- a/cmd/soroban-cli/src/signer/keyring.rs +++ b/cmd/soroban-cli/src/signer/keyring.rs @@ -20,10 +20,12 @@ pub enum Error { FeatureNotEnabled, } +#[derive(Debug)] pub struct StellarEntry { inner: Arc, } +#[derive(Debug)] pub struct StellarEntryInner { name: String, #[cfg(feature = "additional-libs")] diff --git a/cmd/soroban-cli/src/signer/secure_store_entry.rs b/cmd/soroban-cli/src/signer/secure_store_entry.rs index ac4810a3f3..057aae1af5 100644 --- a/cmd/soroban-cli/src/signer/secure_store_entry.rs +++ b/cmd/soroban-cli/src/signer/secure_store_entry.rs @@ -28,6 +28,7 @@ pub enum Error { FeatureNotEnabled, } +#[derive(Debug)] pub struct SecureStoreEntry { pub name: String, //remove this pub hd_path: Option, From f75b9232cd4afbf289473a30a7a7c88fba9b6fc0 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:25:26 -0500 Subject: [PATCH 10/28] Use get_or_init the cached stellar entry when getting the Secret's public key and signer --- cmd/soroban-cli/src/config/locator.rs | 3 --- cmd/soroban-cli/src/config/secret.rs | 24 ++++++++++--------- .../src/signer/secure_store_entry.rs | 8 ++++--- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 24dce81e08..c8dbf44f44 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -277,14 +277,11 @@ impl Args { // 1. Check cache if let Some(arc) = self.cached_keys.get() { let map = arc.lock().unwrap(); - println!("the keys {:?}", map); if let Some(k) = map.get(key_or_name) { - println!("found the one we want!"); return Ok(k.clone()); } } - println!("getting the key"); // 2. Compute key normally let key = key_or_name .parse() diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index 848bb911f2..c62e2baf15 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -6,13 +6,9 @@ use sep5::SeedPhrase; use stellar_strkey::ed25519::{PrivateKey, PublicKey}; use crate::{ - print::Print, - signer::{ - self, ledger, - secure_store_entry::{self, SecureStoreEntry}, - LocalKey, Signer, SignerKind, - }, - utils, + print::Print, signer::{ + self, LocalKey, Signer, SignerKind, ledger, secure_store_entry::{self, SecureStoreEntry} + }, utils }; use super::key::Key; @@ -138,8 +134,10 @@ impl Secret { } pub fn public_key(&self, index: Option) -> Result { - if let Secret::SecureStore { entry_name , .. } = self { - let entry = SecureStoreEntry::new(entry_name.to_string(), index); + if let Secret::SecureStore { entry_name , cached_entry } = self { + let entry = cached_entry.get_or_init(|| { + SecureStoreEntry::new(entry_name.clone(), index) + }); Ok(entry.get_public_key()?) } else { let key = self.key_pair(index)?; @@ -162,8 +160,12 @@ impl Secret { .expect("usize bigger than u32"); SignerKind::Ledger(ledger::new(hd_path).await?) } - Secret::SecureStore { entry_name , .. } => { - SignerKind::SecureStore(SecureStoreEntry::new(entry_name.to_string(), hd_path)) + Secret::SecureStore { entry_name , cached_entry } => { + let entry = cached_entry.get_or_init(|| { + SecureStoreEntry::new(entry_name.clone(), hd_path) + }); + SignerKind::SecureStore(entry.clone()) + // SignerKind::SecureStore(SecureStoreEntry::new(entry_name.to_string(), hd_path)) } }; Ok(Signer { kind, print }) diff --git a/cmd/soroban-cli/src/signer/secure_store_entry.rs b/cmd/soroban-cli/src/signer/secure_store_entry.rs index 057aae1af5..86130d6a86 100644 --- a/cmd/soroban-cli/src/signer/secure_store_entry.rs +++ b/cmd/soroban-cli/src/signer/secure_store_entry.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use crate::{ signer::keyring::StellarEntry, xdr::{self, DecoratedSignature, Signature, SignatureHint}, @@ -28,11 +30,11 @@ pub enum Error { FeatureNotEnabled, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SecureStoreEntry { pub name: String, //remove this pub hd_path: Option, - pub entry: StellarEntry, + pub entry: Arc, } #[cfg(feature = "additional-libs")] @@ -41,7 +43,7 @@ impl SecureStoreEntry { Self { name: name.clone(), hd_path, - entry: StellarEntry::new(&name).unwrap(), //fixme! + entry: Arc::new(StellarEntry::new(&name).unwrap()), //fixme! } } From 0af0a3f9dd38dd0e9ba4b16dece2ba96d6e01c1a Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:10:36 -0500 Subject: [PATCH 11/28] Cleanup --- cmd/crates/soroban-test/src/lib.rs | 5 +- cmd/soroban-cli/src/config/key.rs | 8 +++- cmd/soroban-cli/src/config/locator.rs | 21 +++++---- cmd/soroban-cli/src/config/secret.rs | 51 +++++++++++++-------- cmd/soroban-cli/src/config/upgrade_check.rs | 4 +- 5 files changed, 56 insertions(+), 33 deletions(-) diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index a673e5e6f8..9b5f013579 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -26,7 +26,8 @@ use std::{ ffi::OsString, fmt::Display, - path::{Path, PathBuf}, sync::OnceLock, + path::{Path, PathBuf}, + sync::OnceLock, }; use assert_cmd::{assert::Assert, Command}; @@ -277,7 +278,7 @@ impl TestEnv { locator: config::locator::Args { global: false, config_dir, - cached_keys: OnceLock::new() + cached_keys: OnceLock::new(), }, sign_with: config::sign_with::Args { sign_with_key: None, diff --git a/cmd/soroban-cli/src/config/key.rs b/cmd/soroban-cli/src/config/key.rs index b02314c398..45e7032ac9 100644 --- a/cmd/soroban-cli/src/config/key.rs +++ b/cmd/soroban-cli/src/config/key.rs @@ -85,7 +85,9 @@ impl From<&stellar_strkey::ed25519::PublicKey> for Key { } } -#[derive(Debug, Clone, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)] +#[derive( + Debug, Clone, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr, +)] pub struct Public(pub stellar_strkey::ed25519::PublicKey); impl FromStr for Public { @@ -111,7 +113,9 @@ impl From<&Public> for stellar_strkey::ed25519::MuxedAccount { } } -#[derive(Debug, Clone, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)] +#[derive( + Debug, Clone, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr, +)] pub struct MuxedAccount(pub stellar_strkey::ed25519::MuxedAccount); impl FromStr for MuxedAccount { diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index c8dbf44f44..8eb9ef55d1 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -2,6 +2,8 @@ use clap::arg; use directories::UserDirs; use itertools::Itertools; use serde::de::DeserializeOwned; +use std::collections::HashMap; +use std::sync::{Arc, Mutex, OnceLock}; use std::{ ffi::OsStr, fmt::Display, @@ -104,7 +106,7 @@ pub enum Error { Key(#[from] key::Error), } -pub type CachedKeys = std::collections::HashMap; +pub type CachedKeys = HashMap; #[derive(Debug, clap::Args, Default, Clone)] #[group(skip)] @@ -119,7 +121,8 @@ pub struct Args { pub config_dir: Option, #[clap(skip)] - pub cached_keys: std::sync::OnceLock>>, + // This saves us from reading the same key from the file system more than once for one cmd + pub cached_keys: OnceLock>>, } pub enum Location { @@ -274,7 +277,7 @@ impl Args { } pub fn read_key(&self, key_or_name: &str) -> Result { - // 1. Check cache + // check cache for key & return it if its there if let Some(arc) = self.cached_keys.get() { let map = arc.lock().unwrap(); if let Some(k) = map.get(key_or_name) { @@ -282,17 +285,17 @@ impl Args { } } - // 2. Compute key normally + // if its not in the cache, read it from config let key = key_or_name .parse() .or_else(|_| self.read_identity(key_or_name))?; - // 3. Insert into cache - let arc = self.cached_keys.get_or_init(|| { - std::sync::Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())) - }); - + // get or initialize the cached keys + let arc = self + .cached_keys + .get_or_init(|| Arc::new(Mutex::new(HashMap::new()))); let mut map = arc.lock().unwrap(); + // add the key to cached_keys map.insert(key_or_name.to_string(), key.clone()); Ok(key) diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index c62e2baf15..00123847fc 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -1,14 +1,21 @@ use clap::arg; use serde::{Deserialize, Serialize}; -use std::{str::FromStr, sync::{Arc, OnceLock}}; +use std::{ + str::FromStr, + sync::{Arc, OnceLock}, +}; use sep5::SeedPhrase; use stellar_strkey::ed25519::{PrivateKey, PublicKey}; use crate::{ - print::Print, signer::{ - self, LocalKey, Signer, SignerKind, ledger, secure_store_entry::{self, SecureStoreEntry} - }, utils + print::Print, + signer::{ + self, ledger, + secure_store_entry::{self, SecureStoreEntry}, + LocalKey, Signer, SignerKind, + }, + utils, }; use super::key::Key; @@ -58,15 +65,19 @@ pub struct Args { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum Secret { - SecretKey { secret_key: String }, - SeedPhrase { seed_phrase: String }, + SecretKey { + secret_key: String, + }, + SeedPhrase { + seed_phrase: String, + }, Ledger, - SecureStore { + SecureStore { entry_name: String, #[serde(skip)] #[serde(default)] - cached_entry: Arc>, - } + cached_entry: Arc>, + }, } impl FromStr for Secret { @@ -134,10 +145,13 @@ impl Secret { } pub fn public_key(&self, index: Option) -> Result { - if let Secret::SecureStore { entry_name , cached_entry } = self { - let entry = cached_entry.get_or_init(|| { - SecureStoreEntry::new(entry_name.clone(), index) - }); + if let Secret::SecureStore { + entry_name, + cached_entry, + } = self + { + let entry = + cached_entry.get_or_init(|| SecureStoreEntry::new(entry_name.clone(), index)); Ok(entry.get_public_key()?) } else { let key = self.key_pair(index)?; @@ -160,12 +174,13 @@ impl Secret { .expect("usize bigger than u32"); SignerKind::Ledger(ledger::new(hd_path).await?) } - Secret::SecureStore { entry_name , cached_entry } => { - let entry = cached_entry.get_or_init(|| { - SecureStoreEntry::new(entry_name.clone(), hd_path) - }); + Secret::SecureStore { + entry_name, + cached_entry, + } => { + let entry = + cached_entry.get_or_init(|| SecureStoreEntry::new(entry_name.clone(), hd_path)); SignerKind::SecureStore(entry.clone()) - // SignerKind::SecureStore(SecureStoreEntry::new(entry_name.to_string(), hd_path)) } }; Ok(Signer { kind, print }) diff --git a/cmd/soroban-cli/src/config/upgrade_check.rs b/cmd/soroban-cli/src/config/upgrade_check.rs index 902e81802e..bff4d498b6 100644 --- a/cmd/soroban-cli/src/config/upgrade_check.rs +++ b/cmd/soroban-cli/src/config/upgrade_check.rs @@ -38,7 +38,7 @@ impl UpgradeCheck { let locator = locator::Args { global: false, config_dir: None, - cached_keys: OnceLock::new() + cached_keys: OnceLock::new(), }; let path = locator.global_config_path()?.join(FILE_NAME); if !path.exists() { @@ -54,7 +54,7 @@ impl UpgradeCheck { let locator = locator::Args { global: false, config_dir: None, - cached_keys: OnceLock::new() + cached_keys: OnceLock::new(), }; let path = locator.global_config_path()?.join(FILE_NAME); let path = locator::ensure_directory(path)?; From a7934f81ec0afb9cd97cab290331fc287b5b2910 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:13:57 -0500 Subject: [PATCH 12/28] Remove unused name field on SecureStoreEntry --- cmd/soroban-cli/src/signer/secure_store_entry.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/soroban-cli/src/signer/secure_store_entry.rs b/cmd/soroban-cli/src/signer/secure_store_entry.rs index 86130d6a86..4a7cc29578 100644 --- a/cmd/soroban-cli/src/signer/secure_store_entry.rs +++ b/cmd/soroban-cli/src/signer/secure_store_entry.rs @@ -32,7 +32,6 @@ pub enum Error { #[derive(Debug, Clone)] pub struct SecureStoreEntry { - pub name: String, //remove this pub hd_path: Option, pub entry: Arc, } @@ -41,7 +40,6 @@ pub struct SecureStoreEntry { impl SecureStoreEntry { pub fn new(name: String, hd_path: Option) -> Self { Self { - name: name.clone(), hd_path, entry: Arc::new(StellarEntry::new(&name).unwrap()), //fixme! } From af9d430f36287dc2acfde24292934e28a51392da Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:15:52 -0500 Subject: [PATCH 13/28] Remove unnecessary cached public key in StellarEntry --- cmd/soroban-cli/src/signer/keyring.rs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/cmd/soroban-cli/src/signer/keyring.rs b/cmd/soroban-cli/src/signer/keyring.rs index 2c1e798316..6ad3276e3a 100644 --- a/cmd/soroban-cli/src/signer/keyring.rs +++ b/cmd/soroban-cli/src/signer/keyring.rs @@ -32,7 +32,6 @@ pub struct StellarEntryInner { keyring: Entry, #[allow(dead_code)] cached_seed: Mutex>, - cached_public_key: Mutex>, } impl StellarEntry { @@ -42,7 +41,6 @@ impl StellarEntry { name: name.to_string(), keyring: Entry::new(name, &whoami::username())?, cached_seed: Mutex::new(None), - cached_public_key: Mutex::new(None), }), }) } @@ -121,23 +119,14 @@ impl StellarEntry { &self, hd_path: Option, ) -> Result { - let mut guard = self.inner.cached_public_key.lock().unwrap(); - - if let Some(key) = &*guard { - return Ok(key.clone()); - } - - let public_key = self.use_key( + Ok(self.use_key( |keypair| { Ok(stellar_strkey::ed25519::PublicKey( *keypair.verifying_key().as_bytes(), )) }, hd_path, - )?; - - *guard = Some(public_key.clone()); - Ok(public_key) + )?) } pub fn sign_data(&self, data: &[u8], hd_path: Option) -> Result, Error> { From df8668378a46572d5d24dfba09028f4e520ee4fe Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:21:54 -0500 Subject: [PATCH 14/28] Cleanup --- cmd/soroban-cli/src/config/locator.rs | 1 + cmd/soroban-cli/src/signer/keyring.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 8eb9ef55d1..3f696adc9f 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -276,6 +276,7 @@ impl Args { KeyType::Identity.read_with_global(name, self) } + // read_key caches the Key after reading it from the config pub fn read_key(&self, key_or_name: &str) -> Result { // check cache for key & return it if its there if let Some(arc) = self.cached_keys.get() { diff --git a/cmd/soroban-cli/src/signer/keyring.rs b/cmd/soroban-cli/src/signer/keyring.rs index 6ad3276e3a..b1745d23b5 100644 --- a/cmd/soroban-cli/src/signer/keyring.rs +++ b/cmd/soroban-cli/src/signer/keyring.rs @@ -30,7 +30,6 @@ pub struct StellarEntryInner { name: String, #[cfg(feature = "additional-libs")] keyring: Entry, - #[allow(dead_code)] cached_seed: Mutex>, } From c174c24ba7c2836584f0ccd9cf8f184da7ae8ca3 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:13:04 -0500 Subject: [PATCH 15/28] Cleanup unwraps --- cmd/soroban-cli/src/config/locator.rs | 2 +- cmd/soroban-cli/src/config/secret.rs | 22 +++++++++++++++---- .../src/signer/secure_store_entry.rs | 12 +++++----- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 3f696adc9f..4e20151f95 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -334,7 +334,7 @@ impl Args { let identity = self.read_identity(name)?; if let Key::Secret(Secret::SecureStore { entry_name, .. }) = identity { - let secure_store_entry = SecureStoreEntry::new(entry_name, None); + let secure_store_entry = SecureStoreEntry::new(entry_name, None)?; secure_store_entry.delete_secret(&print)?; } diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index 00123847fc..fd015f758d 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -150,8 +150,7 @@ impl Secret { cached_entry, } = self { - let entry = - cached_entry.get_or_init(|| SecureStoreEntry::new(entry_name.clone(), index)); + let entry = Self::cached_secure_store_entry(index, entry_name, cached_entry)?; Ok(entry.get_public_key()?) } else { let key = self.key_pair(index)?; @@ -178,14 +177,29 @@ impl Secret { entry_name, cached_entry, } => { - let entry = - cached_entry.get_or_init(|| SecureStoreEntry::new(entry_name.clone(), hd_path)); + let entry = Self::cached_secure_store_entry(hd_path, entry_name, cached_entry)?; SignerKind::SecureStore(entry.clone()) } }; Ok(Signer { kind, print }) } + fn cached_secure_store_entry( + hd_path: Option, + entry_name: &String, + cached_entry: &Arc>, + ) -> Result { + let entry = if let Some(e) = cached_entry.get() { + e.clone() + } else { + let e = SecureStoreEntry::new(entry_name.clone(), hd_path)?; + // It's fine if set fails because another thread initialized it concurrently. + let _ = cached_entry.set(e.clone()); + e + }; + Ok(entry) + } + pub fn key_pair(&self, index: Option) -> Result { Ok(utils::into_signing_key(&self.private_key(index)?)) } diff --git a/cmd/soroban-cli/src/signer/secure_store_entry.rs b/cmd/soroban-cli/src/signer/secure_store_entry.rs index 4a7cc29578..690a11fa80 100644 --- a/cmd/soroban-cli/src/signer/secure_store_entry.rs +++ b/cmd/soroban-cli/src/signer/secure_store_entry.rs @@ -38,11 +38,11 @@ pub struct SecureStoreEntry { #[cfg(feature = "additional-libs")] impl SecureStoreEntry { - pub fn new(name: String, hd_path: Option) -> Self { - Self { + pub fn new(name: String, hd_path: Option) -> Result { + Ok(Self { hd_path, - entry: Arc::new(StellarEntry::new(&name).unwrap()), //fixme! - } + entry: Arc::new(StellarEntry::new(&name)?), + }) } pub fn get_public_key(&self) -> Result { @@ -60,7 +60,7 @@ impl SecureStoreEntry { ) -> Result { let entry_name_with_prefix = format!("{ENTRY_PREFIX}{ENTRY_SERVICE}-{entry_name}"); - let s = Self::new(entry_name_with_prefix.clone(), None); + let s = Self::new(entry_name_with_prefix.clone(), None)?; s.entry.write(seed_phrase.clone(), print)?; Ok(entry_name_with_prefix) @@ -93,7 +93,7 @@ impl SecureStoreEntry { SecureStoreEntry { name: name.clone(), hd_path, - entry: StellarEntry::new(&name).unwrap(), //fixme! + entry: StellarEntry::new(&name)? } } pub fn get_public_key(_entry_name: &str, _index: Option) -> Result { From a36c4033a82eb8c889dde4def113c6fae7628085 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 9 Dec 2025 13:44:05 -0500 Subject: [PATCH 16/28] Fix to compile without additional-libs feature --- .../src/signer/secure_store_entry.rs | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/cmd/soroban-cli/src/signer/secure_store_entry.rs b/cmd/soroban-cli/src/signer/secure_store_entry.rs index 690a11fa80..66c7799413 100644 --- a/cmd/soroban-cli/src/signer/secure_store_entry.rs +++ b/cmd/soroban-cli/src/signer/secure_store_entry.rs @@ -1,18 +1,26 @@ -use std::sync::Arc; +use stellar_strkey::ed25519::PublicKey; use crate::{ - signer::keyring::StellarEntry, - xdr::{self, DecoratedSignature, Signature, SignatureHint}, + xdr::{self, DecoratedSignature}, + print::Print }; #[cfg(feature = "additional-libs")] -use crate::{print::Print, signer::keyring}; -use ed25519_dalek::Signature as Ed25519Signature; +use crate::{ + xdr::{Signature, SignatureHint}, + signer::keyring::{self, StellarEntry} +}; #[cfg(feature = "additional-libs")] +use std::sync::Arc; + +use ed25519_dalek::Signature as Ed25519Signature; + use sep5::SeedPhrase; -pub(crate) const ENTRY_PREFIX: &str = "secure_store:"; + +#[cfg(feature = "additional-libs")] const ENTRY_SERVICE: &str = "org.stellar.cli"; +pub(crate) const ENTRY_PREFIX: &str = "secure_store:"; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -33,6 +41,7 @@ pub enum Error { #[derive(Debug, Clone)] pub struct SecureStoreEntry { pub hd_path: Option, + #[cfg(feature = "additional-libs")] pub entry: Arc, } @@ -45,7 +54,7 @@ impl SecureStoreEntry { }) } - pub fn get_public_key(&self) -> Result { + pub fn get_public_key(&self) -> Result { Ok(self.entry.get_public_key(self.hd_path)?) } @@ -89,34 +98,37 @@ impl SecureStoreEntry { #[cfg(not(feature = "additional-libs"))] impl SecureStoreEntry { - pub fn new(name: String, hd_path: Option) -> Self { - SecureStoreEntry { - name: name.clone(), - hd_path, - entry: StellarEntry::new(&name)? - } + pub fn new(_name: String, _hd_path: Option) -> Result { + Err(Error::FeatureNotEnabled) } - pub fn get_public_key(_entry_name: &str, _index: Option) -> Result { + + pub fn get_public_key(&self) -> Result { Err(Error::FeatureNotEnabled) } - pub fn delete_secret(_print: &Print, _entry_name: &str) -> Result<(), Error> { + pub fn delete_secret(&self, _print: &Print) -> Result<(), Error> { Err(Error::FeatureNotEnabled) } - pub fn save_secret( - _print: &Print, + pub fn create_and_save( _entry_name: &str, _seed_phrase: &SeedPhrase, + _print: &Print, ) -> Result { Err(Error::FeatureNotEnabled) } pub fn sign_tx_data( - _entry_name: &str, - _hd_path: Option, _data: &[u8], ) -> Result, Error> { Err(Error::FeatureNotEnabled) } + + pub fn sign_tx_hash(&self, _tx_hash: [u8; 32]) -> Result { + Err(Error::FeatureNotEnabled) + } + + pub fn sign_payload(&self, _payload: [u8; 32]) -> Result { + Err(Error::FeatureNotEnabled) + } } From fb770cfb3f1b51d31b03d942e385164eb24ae6c0 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 9 Dec 2025 14:37:51 -0500 Subject: [PATCH 17/28] Fixup after merge --- cmd/soroban-cli/src/config/upgrade_check.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/config/upgrade_check.rs b/cmd/soroban-cli/src/config/upgrade_check.rs index b929ac29e2..23db047279 100644 --- a/cmd/soroban-cli/src/config/upgrade_check.rs +++ b/cmd/soroban-cli/src/config/upgrade_check.rs @@ -4,7 +4,7 @@ use jsonrpsee_core::Serialize; use semver::Version; use serde::Deserialize; use serde_json; -use std::{fs, sync::OnceLock}; +use std::fs; use super::data::project_dir; From 4c3073014f84339270adbd15d0c3c9d1385385de Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 9 Dec 2025 15:05:20 -0500 Subject: [PATCH 18/28] Clippy --- cmd/soroban-cli/src/commands/keys/generate.rs | 3 +++ cmd/soroban-cli/src/config/secret.rs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs index aabd90620c..0261b91dc8 100644 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ b/cmd/soroban-cli/src/commands/keys/generate.rs @@ -138,6 +138,8 @@ impl Cmd { #[cfg(test)] mod tests { + use std::sync::OnceLock; + use crate::config::{address::KeyName, key::Key, secret::Secret}; fn set_up_test() -> (super::locator::Args, super::Cmd) { @@ -145,6 +147,7 @@ mod tests { let locator = super::locator::Args { global: false, config_dir: Some(temp_dir.path().to_path_buf()), + cached_keys: OnceLock::new(), }; let cmd = super::Cmd { diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index fd015f758d..adc43979fb 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -186,13 +186,13 @@ impl Secret { fn cached_secure_store_entry( hd_path: Option, - entry_name: &String, + entry_name: &str, cached_entry: &Arc>, ) -> Result { let entry = if let Some(e) = cached_entry.get() { e.clone() } else { - let e = SecureStoreEntry::new(entry_name.clone(), hd_path)?; + let e = SecureStoreEntry::new(entry_name.to_owned(), hd_path)?; // It's fine if set fails because another thread initialized it concurrently. let _ = cached_entry.set(e.clone()); e From 9a38824360a6a82ed242c7dd728207c333697cd5 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:14:48 -0500 Subject: [PATCH 19/28] Update key test to account for not implementing PartialEq --- cmd/soroban-cli/src/config/key.rs | 36 +++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/cmd/soroban-cli/src/config/key.rs b/cmd/soroban-cli/src/config/key.rs index 45e7032ac9..31cb8bd24f 100644 --- a/cmd/soroban-cli/src/config/key.rs +++ b/cmd/soroban-cli/src/config/key.rs @@ -147,13 +147,45 @@ impl TryFrom for Secret { #[cfg(test)] mod test { + use std::sync::Arc; + 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); + + assert_key_equality(key, &deserialized); + } + + // using this fn instead of just doing assert_eq!(key, deserialized) because Secret::SecureStore keys contain a StellarEntry which contains a keyring::Entry + // keyring::Entry comes from the keyring crate which does not implement PartialEq + fn assert_key_equality(expected: &Key, actual: &Key) { + match (expected, actual) { + (Key::PublicKey(e), Key::PublicKey(a)) => { + assert_eq!(e, a); + }, + (Key::MuxedAccount(e ), Key::MuxedAccount(a)) => { + assert_eq!(e, a); + }, + (Key::Secret(e), Key::Secret(a)) => { + match (e, a) { + (Secret::SecretKey { secret_key: e_secret_key }, Secret::SecretKey { secret_key: a_secret_key }) => { + assert_eq!(e_secret_key, a_secret_key); + }, + (Secret::SeedPhrase { seed_phrase: e_seed_phrase }, Secret::SeedPhrase { seed_phrase: a_seed_phrase }) => { + assert_eq!(e_seed_phrase, a_seed_phrase); + }, + (Secret::Ledger, Secret::Ledger) => todo!(), + (Secret::SecureStore { entry_name: e_entry_name, cached_entry: e_cached_entry }, Secret::SecureStore { entry_name: a_entry_name, cached_entry: a_cached_entry }) => { + assert_eq!(e_entry_name, a_entry_name); + assert!(Arc::ptr_eq(e_cached_entry, a_cached_entry)); + }, + _ => panic!("keys are not equal {expected:?} != {actual:?}") + } + }, + _ => panic!("keys are not equal {expected:?} != {actual:?}") + } } #[test] From db79631ea008139d5c1bdf4f0970f9dbc82c6d37 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:15:12 -0500 Subject: [PATCH 20/28] Fmt --- cmd/soroban-cli/src/config/key.rs | 51 +++++++++++++------ .../src/signer/secure_store_entry.rs | 9 ++-- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/cmd/soroban-cli/src/config/key.rs b/cmd/soroban-cli/src/config/key.rs index 31cb8bd24f..27ed9ab853 100644 --- a/cmd/soroban-cli/src/config/key.rs +++ b/cmd/soroban-cli/src/config/key.rs @@ -164,27 +164,48 @@ mod test { match (expected, actual) { (Key::PublicKey(e), Key::PublicKey(a)) => { assert_eq!(e, a); - }, - (Key::MuxedAccount(e ), Key::MuxedAccount(a)) => { + } + (Key::MuxedAccount(e), Key::MuxedAccount(a)) => { assert_eq!(e, a); - }, - (Key::Secret(e), Key::Secret(a)) => { - match (e, a) { - (Secret::SecretKey { secret_key: e_secret_key }, Secret::SecretKey { secret_key: a_secret_key }) => { - assert_eq!(e_secret_key, a_secret_key); + } + (Key::Secret(e), Key::Secret(a)) => match (e, a) { + ( + Secret::SecretKey { + secret_key: e_secret_key, + }, + Secret::SecretKey { + secret_key: a_secret_key, }, - (Secret::SeedPhrase { seed_phrase: e_seed_phrase }, Secret::SeedPhrase { seed_phrase: a_seed_phrase }) => { - assert_eq!(e_seed_phrase, a_seed_phrase); + ) => { + assert_eq!(e_secret_key, a_secret_key); + } + ( + Secret::SeedPhrase { + seed_phrase: e_seed_phrase, + }, + Secret::SeedPhrase { + seed_phrase: a_seed_phrase, + }, + ) => { + assert_eq!(e_seed_phrase, a_seed_phrase); + } + (Secret::Ledger, Secret::Ledger) => todo!(), + ( + Secret::SecureStore { + entry_name: e_entry_name, + cached_entry: e_cached_entry, }, - (Secret::Ledger, Secret::Ledger) => todo!(), - (Secret::SecureStore { entry_name: e_entry_name, cached_entry: e_cached_entry }, Secret::SecureStore { entry_name: a_entry_name, cached_entry: a_cached_entry }) => { - assert_eq!(e_entry_name, a_entry_name); - assert!(Arc::ptr_eq(e_cached_entry, a_cached_entry)); + Secret::SecureStore { + entry_name: a_entry_name, + cached_entry: a_cached_entry, }, - _ => panic!("keys are not equal {expected:?} != {actual:?}") + ) => { + assert_eq!(e_entry_name, a_entry_name); + assert!(Arc::ptr_eq(e_cached_entry, a_cached_entry)); } + _ => panic!("keys are not equal {expected:?} != {actual:?}"), }, - _ => panic!("keys are not equal {expected:?} != {actual:?}") + _ => panic!("keys are not equal {expected:?} != {actual:?}"), } } diff --git a/cmd/soroban-cli/src/signer/secure_store_entry.rs b/cmd/soroban-cli/src/signer/secure_store_entry.rs index 66c7799413..0cc4995295 100644 --- a/cmd/soroban-cli/src/signer/secure_store_entry.rs +++ b/cmd/soroban-cli/src/signer/secure_store_entry.rs @@ -1,14 +1,14 @@ use stellar_strkey::ed25519::PublicKey; use crate::{ + print::Print, xdr::{self, DecoratedSignature}, - print::Print }; #[cfg(feature = "additional-libs")] use crate::{ + signer::keyring::{self, StellarEntry}, xdr::{Signature, SignatureHint}, - signer::keyring::{self, StellarEntry} }; #[cfg(feature = "additional-libs")] use std::sync::Arc; @@ -17,7 +17,6 @@ use ed25519_dalek::Signature as Ed25519Signature; use sep5::SeedPhrase; - #[cfg(feature = "additional-libs")] const ENTRY_SERVICE: &str = "org.stellar.cli"; pub(crate) const ENTRY_PREFIX: &str = "secure_store:"; @@ -118,9 +117,7 @@ impl SecureStoreEntry { Err(Error::FeatureNotEnabled) } - pub fn sign_tx_data( - _data: &[u8], - ) -> Result, Error> { + pub fn sign_tx_data(_data: &[u8]) -> Result, Error> { Err(Error::FeatureNotEnabled) } From 379d40887116521fc4109b3b5cc6578659be293f Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:21:08 -0500 Subject: [PATCH 21/28] Clippy for ubuntu builds --- cmd/soroban-cli/src/signer/keyring.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/soroban-cli/src/signer/keyring.rs b/cmd/soroban-cli/src/signer/keyring.rs index b1745d23b5..1684ac8777 100644 --- a/cmd/soroban-cli/src/signer/keyring.rs +++ b/cmd/soroban-cli/src/signer/keyring.rs @@ -118,14 +118,14 @@ impl StellarEntry { &self, hd_path: Option, ) -> Result { - Ok(self.use_key( + self.use_key( |keypair| { Ok(stellar_strkey::ed25519::PublicKey( *keypair.verifying_key().as_bytes(), )) }, hd_path, - )?) + ) } pub fn sign_data(&self, data: &[u8], hd_path: Option) -> Result, Error> { From 1b928c6ebd0e11a30a511a77365bcf9f3741e39d Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 10 Dec 2025 13:22:57 -0500 Subject: [PATCH 22/28] Clear the cached seed when a key is deleted --- cmd/soroban-cli/src/signer/keyring.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/signer/keyring.rs b/cmd/soroban-cli/src/signer/keyring.rs index 1684ac8777..bb73593304 100644 --- a/cmd/soroban-cli/src/signer/keyring.rs +++ b/cmd/soroban-cli/src/signer/keyring.rs @@ -70,7 +70,11 @@ impl StellarEntry { pub fn delete_seed_phrase(&self, print: &Print) -> Result<(), Error> { match self.inner.keyring.delete_credential() { - Ok(()) => Ok(()), + Ok(()) => { + // clear the cached seed + self.inner.cached_seed.lock().unwrap().take(); + Ok(()) + }, Err(e) => match e { keyring::Error::NoEntry => { print.infoln("This key was already removed from the secure store."); From 569432bc5e2d805d6d0fbe88653d8e37f939f049 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:42:22 -0500 Subject: [PATCH 23/28] Fix integration test --- cmd/crates/soroban-test/tests/it/integration/hello_world.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs index b0e667a3f0..9b27fe25f1 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -6,7 +6,7 @@ use soroban_cli::{ config::{locator, secret}, }; use soroban_test::{AssertExt, TestEnv, LOCAL_NETWORK_PASSPHRASE}; - +use std::sync::OnceLock; use super::util::{deploy_hello, extend}; use crate::integration::util::extend_contract; @@ -101,6 +101,7 @@ async fn invoke_contract() { let config_locator = locator::Args { global: false, config_dir: Some(dir.to_path_buf()), + cached_keys: OnceLock::new() }; config_locator From bd4b85622d127c1ed13c9c651e25b22434e353dd Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:35:02 -0500 Subject: [PATCH 24/28] Cargo check fixes --- cmd/crates/soroban-test/tests/it/integration/hello_world.rs | 6 +++--- cmd/soroban-cli/src/signer/keyring.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs index 9b27fe25f1..db818e4b08 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -1,3 +1,5 @@ +use super::util::{deploy_hello, extend}; +use crate::integration::util::extend_contract; use soroban_cli::{ commands::{ contract::{self, fetch}, @@ -7,8 +9,6 @@ use soroban_cli::{ }; use soroban_test::{AssertExt, TestEnv, LOCAL_NETWORK_PASSPHRASE}; use std::sync::OnceLock; -use super::util::{deploy_hello, extend}; -use crate::integration::util::extend_contract; #[allow(clippy::too_many_lines)] #[tokio::test] @@ -101,7 +101,7 @@ async fn invoke_contract() { let config_locator = locator::Args { global: false, config_dir: Some(dir.to_path_buf()), - cached_keys: OnceLock::new() + cached_keys: OnceLock::new(), }; config_locator diff --git a/cmd/soroban-cli/src/signer/keyring.rs b/cmd/soroban-cli/src/signer/keyring.rs index bb73593304..5f95e03fe0 100644 --- a/cmd/soroban-cli/src/signer/keyring.rs +++ b/cmd/soroban-cli/src/signer/keyring.rs @@ -74,7 +74,7 @@ impl StellarEntry { // clear the cached seed self.inner.cached_seed.lock().unwrap().take(); Ok(()) - }, + } Err(e) => match e { keyring::Error::NoEntry => { print.infoln("This key was already removed from the secure store."); From 9e13756ecb3cd028c45b58f633246af8fbe832ef Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:50:07 -0500 Subject: [PATCH 25/28] Cleanup --- cmd/soroban-cli/src/config/locator.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 098df9bf56..5f7d9a9285 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -304,7 +304,6 @@ impl Args { Ok(key) } - // and then we read from config again here... so it is a new instance of a Secret pub fn get_secret_key(&self, key_or_name: &str) -> Result { match self.read_key(key_or_name)? { Key::Secret(s) => Ok(s), From 97c83a5dcacff6459c51e41001f85f0b0808af1c Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 10 Dec 2025 18:15:18 -0500 Subject: [PATCH 26/28] Handle an unwrap --- cmd/soroban-cli/src/signer/keyring.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/signer/keyring.rs b/cmd/soroban-cli/src/signer/keyring.rs index 5f95e03fe0..acf6f823e8 100644 --- a/cmd/soroban-cli/src/signer/keyring.rs +++ b/cmd/soroban-cli/src/signer/keyring.rs @@ -18,6 +18,9 @@ pub enum Error { #[error("Secure Store keys are not allowed: additional-libs feature must be enabled")] FeatureNotEnabled, + + #[error("Mutex poisoned")] + MutexPoison, } #[derive(Debug)] @@ -72,7 +75,7 @@ impl StellarEntry { match self.inner.keyring.delete_credential() { Ok(()) => { // clear the cached seed - self.inner.cached_seed.lock().unwrap().take(); + self.inner.cached_seed.lock().map_err(|_| Error::MutexPoison)?.take(); Ok(()) } Err(e) => match e { From f57be37a041df73e0b638386df565eb700e2aa2f Mon Sep 17 00:00:00 2001 From: Leigh <351529+leighmcculloch@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:50:45 +1000 Subject: [PATCH 27/28] Embed strkey CLI command for encoding and decoding address strings (#2324) ### What Upgrade stellar-strkey from 0.0.13 to 0.0.15, enable the cli feature, and add a new `strkey` subcommand to the CLI. ### Why Expose strkey encoding and decoding functionality directly in the CLI, matching the existing pattern used for XDR. For advanced use cases it is helpful to be able to extract components out of addresses. The use cases this is most relevant is cross-chain protocols who prefer to work with the raw payloads within the addresses. --- Cargo.lock | 23 +++++++++++++--- Cargo.toml | 2 +- cmd/crates/soroban-test/tests/it/main.rs | 1 + cmd/crates/soroban-test/tests/it/strkey.rs | 31 ++++++++++++++++++++++ cmd/soroban-cli/Cargo.toml | 2 +- cmd/soroban-cli/src/commands/mod.rs | 7 +++++ 6 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 cmd/crates/soroban-test/tests/it/strkey.rs diff --git a/Cargo.lock b/Cargo.lock index 4fa6e38da7..952531593a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4972,7 +4972,7 @@ dependencies = [ "stellar-asset-spec", "stellar-ledger", "stellar-rpc-client", - "stellar-strkey 0.0.13", + "stellar-strkey 0.0.15", "stellar-xdr", "strsim", "strum 0.17.1", @@ -5192,7 +5192,7 @@ dependencies = [ "itertools 0.10.5", "serde_json", "soroban-spec", - "stellar-strkey 0.0.13", + "stellar-strkey 0.0.15", "stellar-xdr", "thiserror 1.0.69", "tokio", @@ -5246,7 +5246,7 @@ dependencies = [ "soroban-spec-tools", "stellar-ledger", "stellar-rpc-client", - "stellar-strkey 0.0.13", + "stellar-strkey 0.0.15", "test-case", "testcontainers", "thiserror 1.0.69", @@ -5366,7 +5366,7 @@ dependencies = [ "serial_test", "sha2 0.10.9", "slipped10", - "stellar-strkey 0.0.13", + "stellar-strkey 0.0.15", "stellar-xdr", "test-case", "testcontainers", @@ -5432,6 +5432,21 @@ dependencies = [ "data-encoding", ] +[[package]] +name = "stellar-strkey" +version = "0.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97e1a364048067bfcd24e6f1a93bba43eeb79c16b854596c841c3e8bab0bfa0c" +dependencies = [ + "clap", + "crate-git-revision", + "data-encoding", + "serde", + "serde_json", + "serde_with", + "thiserror 1.0.69", +] + [[package]] name = "stellar-xdr" version = "23.0.0" diff --git a/Cargo.toml b/Cargo.toml index a1df61d4f9..348e7153a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ version = "23.2.1" # Dependencies from elsewhere shared by crates: [workspace.dependencies] -stellar-strkey = "0.0.13" +stellar-strkey = "0.0.15" sep5 = "0.0.4" base64 = "0.21.2" thiserror = "1.0.46" diff --git a/cmd/crates/soroban-test/tests/it/main.rs b/cmd/crates/soroban-test/tests/it/main.rs index aff760b4c1..6cbf1a60d7 100644 --- a/cmd/crates/soroban-test/tests/it/main.rs +++ b/cmd/crates/soroban-test/tests/it/main.rs @@ -9,5 +9,6 @@ mod integration; mod log; mod plugin; mod rpc_provider; +mod strkey; mod util; mod version; diff --git a/cmd/crates/soroban-test/tests/it/strkey.rs b/cmd/crates/soroban-test/tests/it/strkey.rs new file mode 100644 index 0000000000..77e5948483 --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/strkey.rs @@ -0,0 +1,31 @@ +use soroban_test::TestEnv; + +#[test] +fn decode() { + let sandbox = TestEnv::default(); + sandbox + .new_assert_cmd("strkey") + .args([ + "decode", + "GAZTAML6YJA5PGKDXONKPSHKL6FYT6OG5I2R7YB7R3B5CETRG7KIJONK", + ]) + .assert() + .success() + .stdout( + r#"{ + "public_key_ed25519": "3330317ec241d79943bb9aa7c8ea5f8b89f9c6ea351fe03f8ec3d1127137d484" +} +"#, + ); +} + +#[test] +fn encode() { + let sandbox = TestEnv::default(); + sandbox + .new_assert_cmd("strkey") + .args(["encode", r#"{"public_key_ed25519":"3330317ec241d79943bb9aa7c8ea5f8b89f9c6ea351fe03f8ec3d1127137d484"}"#]) + .assert() + .success() + .stdout("GAZTAML6YJA5PGKDXONKPSHKL6FYT6OG5I2R7YB7R3B5CETRG7KIJONK\n"); +} diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index 63b9a28be7..c3868c91c6 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -42,7 +42,7 @@ soroban-spec-rust = { workspace = true } soroban-spec-tools = { workspace = true } soroban-spec-typescript = { workspace = true } soroban-ledger-snapshot = { workspace = true } -stellar-strkey = { workspace = true } +stellar-strkey = { workspace = true, features = ["cli"] } soroban-sdk = { workspace = true } stellar-asset-spec = { workspace = true } soroban-rpc = { workspace = true } diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index a6aa805618..0363e8db95 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -130,6 +130,7 @@ impl Root { Cmd::Config(config) => config.run()?, Cmd::Events(events) => events.run().await?, Cmd::Xdr(xdr) => xdr.run()?, + Cmd::Strkey(strkey) => strkey.run()?, Cmd::Network(network) => network.run(&self.global_args).await?, Cmd::Container(container) => container.run(&self.global_args).await?, Cmd::Snapshot(snapshot) => snapshot.run(&self.global_args).await?, @@ -202,6 +203,9 @@ pub enum Cmd { /// Decode and encode XDR Xdr(stellar_xdr::cli::Root), + /// Decode and encode strkey + Strkey(stellar_strkey::cli::Root), + /// Print shell completion code for the specified shell. #[command(long_about = completion::LONG_ABOUT)] Completion(completion::Cmd), @@ -243,6 +247,9 @@ pub enum Error { #[error(transparent)] Xdr(#[from] stellar_xdr::cli::Error), + #[error(transparent)] + Strkey(#[from] stellar_strkey::cli::Error), + #[error(transparent)] Clap(#[from] clap::error::Error), From dc3f5bb8a5182714a5b11ee5bf08f1e69386bc29 Mon Sep 17 00:00:00 2001 From: elizabethengelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon, 15 Dec 2025 11:15:23 -0500 Subject: [PATCH 28/28] Cargo fmt --- cmd/soroban-cli/src/signer/keyring.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/signer/keyring.rs b/cmd/soroban-cli/src/signer/keyring.rs index acf6f823e8..9e34a05dea 100644 --- a/cmd/soroban-cli/src/signer/keyring.rs +++ b/cmd/soroban-cli/src/signer/keyring.rs @@ -75,7 +75,11 @@ impl StellarEntry { match self.inner.keyring.delete_credential() { Ok(()) => { // clear the cached seed - self.inner.cached_seed.lock().map_err(|_| Error::MutexPoison)?.take(); + self.inner + .cached_seed + .lock() + .map_err(|_| Error::MutexPoison)? + .take(); Ok(()) } Err(e) => match e {