Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
02b294b
Start to rework invoke to allow for a Signer or a SigningKey
elizabethengelman Sep 24, 2025
136efdf
Start to check needle match depending on the signerkey type
elizabethengelman Sep 26, 2025
f1cd73d
Change map in sign_soroban_authorizations to for loop so we can call …
elizabethengelman Sep 26, 2025
bb05550
Await matches_verifying_key result
elizabethengelman Sep 26, 2025
e688f9b
Sign payload with secure store
elizabethengelman Sep 26, 2025
d7f19ce
Merge branch 'main' into fix/soroban-auth-signing-updated
elizabethengelman Sep 30, 2025
e3cd18e
Cleanup / remove unsued code
elizabethengelman Sep 30, 2025
cdbd99d
Resolve arg_parsing signer as a SignerKey and signing invoke tx with …
elizabethengelman Sep 30, 2025
bf6dfe8
Start to remove SignerKey enum
elizabethengelman Oct 1, 2025
49c39ce
Continue removing SignerKey enum in favor of just a Signer
elizabethengelman Oct 1, 2025
a5308f6
Remove SignerKey - we can just use a Signer
elizabethengelman Oct 1, 2025
ec2f70d
Clippy & cleanup
elizabethengelman Oct 1, 2025
27be163
Cleanup
elizabethengelman Oct 1, 2025
eb5ba54
Clippy
elizabethengelman Oct 1, 2025
c8f3d64
Allow source account signer to be the auth entry signer
elizabethengelman Oct 1, 2025
4fa246e
Merge branch 'main' into fix/soroban-auth-signing-updated
elizabethengelman Oct 2, 2025
f76206a
Cargo fmt
elizabethengelman Oct 2, 2025
8ca7619
Return a nicer error for signing with lab
elizabethengelman Oct 2, 2025
c3cae2e
Merge branch 'main' into fix/soroban-auth-signing-updated
elizabethengelman Oct 6, 2025
6d77543
Merge branch 'main' into fix/soroban-auth-signing-updated
elizabethengelman Oct 6, 2025
deb3089
Fix after merging main
elizabethengelman Oct 7, 2025
697cb5f
Wire up signer get_public_key for ledger
elizabethengelman Oct 7, 2025
b9abd64
Implement sign payload for ledger
elizabethengelman Oct 7, 2025
9b5bc72
Merge branch 'main' into fix/soroban-auth-signing-updated
elizabethengelman Oct 9, 2025
381f9be
Merge branch 'main' into fix/soroban-auth-signing-updated
elizabethengelman Oct 14, 2025
4bb573c
Use ledger as source account for invoke contract txs
elizabethengelman Oct 15, 2025
4c2721d
Refactor secret.public_key to use a match instead of if block
elizabethengelman Oct 15, 2025
ad13335
wip
elizabethengelman Oct 15, 2025
50481c3
Merge branch 'main' into fix/soroban-auth-signing-updated
elizabethengelman Oct 15, 2025
c89fcb8
Make arg_parsing resolve_address async
elizabethengelman Oct 15, 2025
9222e0f
Merge branch 'main' into fix/soroban-auth-signing-updated
elizabethengelman Oct 15, 2025
a2a1a55
Merge branch 'main' into fix/soroban-auth-signing-updated
elizabethengelman Oct 28, 2025
e9b0685
Merge branch 'fix/soroban-auth-signing-updated' into fix/soroban-auth…
elizabethengelman Oct 28, 2025
8b9cdb7
Merge branch 'main' into fix/soroban-auth-signing-with-ledger
elizabethengelman Dec 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions cmd/soroban-cli/src/commands/contract/arg_parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,17 +218,20 @@ async fn parse_single_argument(
ScSpecTypeDef::Address | ScSpecTypeDef::MuxedAddress
) {
let trimmed_s = s.trim_matches('"');
let addr = resolve_address(trimmed_s, config)?;
if let Some(signer) = resolve_signer(trimmed_s, config).await {
signers.push(signer);
}
let addr = resolve_address(trimmed_s, config).await?;

let signer = resolve_signer(&s, config).await;
if let Some(signer) = signer {
signers.push(signer);
}

parsed_args.push(parse_argument_with_validation(
&name,
&addr,
&input.type_,
spec,
config,
)?);
).await?);
return Ok(());
}

Expand All @@ -238,7 +241,7 @@ async fn parse_single_argument(
&input.type_,
spec,
config,
)?);
).await?);
Ok(())
} else if matches!(input.type_, ScSpecTypeDef::Option(_)) {
parsed_args.push(ScVal::Void);
Expand All @@ -251,7 +254,7 @@ async fn parse_single_argument(
expected_type_name,
spec,
config,
)?);
).await?);
Ok(())
} else {
Err(Error::MissingArgument {
Expand All @@ -261,7 +264,7 @@ async fn parse_single_argument(
}
}

fn parse_file_argument(
async fn parse_file_argument(
name: &str,
arg_path: &PathBuf,
type_def: &ScSpecTypeDef,
Expand Down Expand Up @@ -293,7 +296,7 @@ fn parse_file_argument(
type_def,
file_contents.len()
);
parse_argument_with_validation(name, &file_contents, type_def, spec, config)
parse_argument_with_validation(name, &file_contents, type_def, spec, config).await
}
}

Expand Down Expand Up @@ -428,12 +431,12 @@ pub fn output_to_string(
Ok(TxnResult::Res(res_str))
}

fn resolve_address(addr_or_alias: &str, config: &config::Args) -> Result<String, Error> {
async fn resolve_address(addr_or_alias: &str, config: &config::Args) -> Result<String, Error> {
let sc_address: UnresolvedScAddress = addr_or_alias.parse().unwrap();
let account = match sc_address {
UnresolvedScAddress::Resolved(addr) => addr.to_string(),
addr @ UnresolvedScAddress::Alias(_) => {
let addr = addr.resolve(&config.locator, &config.get_network()?.network_passphrase)?;
let addr = addr.resolve_async(&config.locator, &config.get_network()?.network_passphrase).await?;
match addr {
xdr::ScAddress::Account(account) => account.to_string(),
contract @ xdr::ScAddress::Contract(_) => contract.to_string(),
Expand Down Expand Up @@ -594,7 +597,7 @@ fn get_context_suggestions(expected_type: &ScSpecTypeDef, received_value: &str)
}

/// Enhanced argument parsing with better error handling
fn parse_argument_with_validation(
async fn parse_argument_with_validation(
arg_name: &str,
value: &str,
expected_type: &ScSpecTypeDef,
Expand All @@ -614,7 +617,7 @@ fn parse_argument_with_validation(
ScSpecTypeDef::Address | ScSpecTypeDef::MuxedAddress
) {
let trimmed_value = value.trim_matches('"');
let addr = resolve_address(trimmed_value, config)?;
let addr = resolve_address(trimmed_value, config).await?;
return spec
.from_string(&addr, expected_type)
.map_err(|error| Error::CannotParseArg {
Expand Down
4 changes: 3 additions & 1 deletion cmd/soroban-cli/src/config/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ impl UnresolvedMuxedAccount {
UnresolvedMuxedAccount::AliasOrSecret(alias_or_secret) => {
Ok(locator.read_key(alias_or_secret)?.try_into()?)
}
UnresolvedMuxedAccount::Ledger(_) => Err(Error::LedgerPrivateKeyRevealNotSupported),
UnresolvedMuxedAccount::Ledger(_) => {
Ok(locator.read_key("ledger")?.try_into()?)
}
}
}
}
Expand Down
18 changes: 18 additions & 0 deletions cmd/soroban-cli/src/config/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,26 @@ pub enum Key {
}

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

pub fn muxed_account(&self, hd_path: Option<usize>) -> Result<xdr::MuxedAccount, Error> {
let bytes = match self {
// this is what calls public key
Key::Secret(secret) => secret.public_key(hd_path)?.0,
Key::PublicKey(Public(key)) => key.0,
Key::MuxedAccount(MuxedAccount(stellar_strkey::ed25519::MuxedAccount {
Expand Down
3 changes: 2 additions & 1 deletion cmd/soroban-cli/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ impl Args {
signers,
seq_num,
&network.network_passphrase,
)?)
)
.await?)
}

pub fn get_network(&self) -> Result<Network, Error> {
Expand Down
29 changes: 29 additions & 0 deletions cmd/soroban-cli/src/config/sc_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,35 @@ impl FromStr for UnresolvedScAddress {
}

impl UnresolvedScAddress {
pub async fn resolve_async(self,
locator: &locator::Args,
network_passphrase: &str,
) -> Result<xdr::ScAddress, Error> {
let alias = match self {
UnresolvedScAddress::Resolved(addr) => return Ok(addr),
UnresolvedScAddress::Alias(alias) => alias,
};
let contract = UnresolvedContract::resolve_alias(&alias, locator, network_passphrase);
let key = locator.read_key(&alias);
match (contract, key) {
(Ok(contract), Ok(_)) => {
eprintln!(
"Warning: ScAddress alias {alias} is ambiguous, assuming it is a contract"
);
Ok(xdr::ScAddress::Contract(stellar_xdr::curr::ContractId(
xdr::Hash(contract.0),
)))
}
(Ok(contract), _) => Ok(xdr::ScAddress::Contract(stellar_xdr::curr::ContractId(
xdr::Hash(contract.0),
))),
(_, Ok(key)) => Ok(xdr::ScAddress::Account(
key.muxed_account_async(None).await?.account_id(),
)),
_ => Err(Error::AccountAliasNotFound(alias)),
}
}

pub fn resolve(
self,
locator: &locator::Args,
Expand Down
34 changes: 27 additions & 7 deletions cmd/soroban-cli/src/config/secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,34 @@ impl Secret {
})
}


pub async fn public_key_async(&self, index: Option<usize>) -> Result<PublicKey, Error> {
match &self {
Secret::Ledger => {
//TODO: handle unwrap
let hd_path: u32 = index.unwrap_or(0).try_into().unwrap();
let ledger = ledger::new(hd_path).await.unwrap();
Ok(ledger.public_key().await.unwrap())
},
_ => {
self.public_key(index)
}
}
}

pub fn public_key(&self, index: Option<usize>) -> Result<PublicKey, Error> {
if let Secret::SecureStore { entry_name } = self {
Ok(secure_store::get_public_key(entry_name, index)?)
} else {
let key = self.key_pair(index)?;
Ok(stellar_strkey::ed25519::PublicKey::from_payload(
key.verifying_key().as_bytes(),
)?)
match &self {
Secret::SecureStore { entry_name } => {
Ok(secure_store::get_public_key(entry_name, index)?)
},
_ => {
let key = self.key_pair(index)?;
Ok(stellar_strkey::ed25519::PublicKey::from_payload(
key.verifying_key().as_bytes(),
)?)
}
// Secret::SecretKey { secret_key } => todo!(),
// Secret::SeedPhrase { seed_phrase } => todo!(),
}
}

Expand Down
7 changes: 7 additions & 0 deletions cmd/soroban-cli/src/signer/ledger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub enum Error {
mod ledger_impl {
use super::Error;
use crate::xdr::{DecoratedSignature, Hash, Signature, SignatureHint, Transaction};
use ed25519_dalek::Signature as Ed25519Signature;
use sha2::{Digest, Sha256};
use stellar_ledger::{Blob as _, Exchange, LedgerSigner};

Expand Down Expand Up @@ -96,6 +97,12 @@ mod ledger_impl {
Ok(DecoratedSignature { hint, signature })
}

pub async fn sign_payload(&self, payload: [u8; 32]) -> Result<Ed25519Signature, Error> {
let signed_bytes = self.signer.sign_blob(&self.index.into(), &payload).await?;
let sig = Ed25519Signature::from_bytes(signed_bytes.as_slice().try_into()?);
Ok(sig)
}

pub async fn public_key(&self) -> Result<stellar_strkey::ed25519::PublicKey, Error> {
Ok(self.signer.get_public_key(&self.index.into()).await?)
}
Expand Down
48 changes: 31 additions & 17 deletions cmd/soroban-cli/src/signer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ fn requires_auth(txn: &Transaction) -> Option<xdr::Operation> {

// Use the given source_key and signers, to sign all SorobanAuthorizationEntry's in the given
// transaction. If unable to sign, return an error.
pub fn sign_soroban_authorizations(
pub async fn sign_soroban_authorizations(
raw: &Transaction,
source_signer: &Signer,
signers: &[Signer],
Expand Down Expand Up @@ -118,23 +118,24 @@ pub fn sign_soroban_authorizations(

let mut signer: Option<&Signer> = None;
for s in signers {
if needle == &s.get_public_key()? {
if needle == &s.get_public_key().await? {
signer = Some(s);
}
}

if needle == &source_signer.get_public_key()? {
if needle == &source_signer.get_public_key().await? {
signer = Some(source_signer);
}

match signer {
Some(signer) => {
let signed_entry = sign_soroban_authorization_entry(
raw_auth,
signer,
signer, // handle this
signature_expiration_ledger,
&network_id,
)?;
)
.await?;
signed_auths.push(signed_entry);
}
None => {
Expand All @@ -153,7 +154,7 @@ pub fn sign_soroban_authorizations(
Ok(Some(tx))
}

fn sign_soroban_authorization_entry(
async fn sign_soroban_authorization_entry(
raw: &SorobanAuthorizationEntry,
signer: &Signer,
signature_expiration_ledger: u32,
Expand All @@ -179,9 +180,8 @@ fn sign_soroban_authorization_entry(
.to_xdr(Limits::none())?;

let payload = Sha256::digest(preimage);
let p: [u8; 32] = payload.as_slice().try_into()?;
let signature = signer.sign_payload(p)?;
let public_key_vec = signer.get_public_key()?.to_vec();
let signature = signer.sign_payload(&payload).await?;
let public_key_vec = signer.get_public_key().await?.to_vec();

let map = ScMap::sorted_from(vec![
(
Expand Down Expand Up @@ -262,11 +262,15 @@ impl Signer {
}
}

// when we implement this for ledger we'll need it to be async so we can await for the ledger's public key
pub fn get_public_key(&self) -> Result<[u8; 32], Error> {
// when we implement this for ledger we'll need it to be awaited
#[allow(clippy::unused_async)]
pub async fn get_public_key(&self) -> Result<[u8; 32], Error> {
match &self.kind {
SignerKind::Local(local_key) => Ok(*local_key.key.verifying_key().as_bytes()),
SignerKind::Ledger(_ledger) => todo!("ledger device is not implemented"),
SignerKind::Ledger(ledger) => {
let pk = ledger.public_key().await?;
Ok(pk.0)
}
SignerKind::Lab => Err(Error::ReturningSignatureFromLab),
SignerKind::SecureStore(secure_store_entry) => {
let pk = secure_store_entry.get_public_key()?;
Expand All @@ -275,13 +279,23 @@ impl Signer {
}
}

// when we implement this for ledger we'll need it to be async so we can await the user approved the tx on the ledger device
pub fn sign_payload(&self, payload: [u8; 32]) -> Result<Ed25519Signature, Error> {
// when we implement this for ledger we'll need it to be awaited
#[allow(clippy::unused_async)]
pub async fn sign_payload(&self, payload: &[u8]) -> Result<Ed25519Signature, Error> {
match &self.kind {
SignerKind::Local(local_key) => local_key.sign_payload(payload),
SignerKind::Ledger(_ledger) => todo!("ledger device is not implemented"),
SignerKind::Local(local_key) => {
let p = <[u8; 32]>::try_from(payload)?;
local_key.sign_payload(p)
}
SignerKind::Ledger(ledger) => Ok({
let p = <[u8; 32]>::try_from(payload)?;
ledger.sign_payload(p).await?
}),
SignerKind::Lab => Err(Error::ReturningSignatureFromLab),
SignerKind::SecureStore(secure_store_entry) => secure_store_entry.sign_payload(payload),
SignerKind::SecureStore(secure_store_entry) => {
let p = <[u8; 32]>::try_from(payload)?;
secure_store_entry.sign_payload(p)
}
}
}
}
Expand Down
Loading