diff --git a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs index 3c2456e9e9..29cf992ef3 100644 --- a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs +++ b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs @@ -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(()); } @@ -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); @@ -251,7 +254,7 @@ async fn parse_single_argument( expected_type_name, spec, config, - )?); + ).await?); Ok(()) } else { Err(Error::MissingArgument { @@ -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, @@ -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 } } @@ -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 { +async fn resolve_address(addr_or_alias: &str, config: &config::Args) -> Result { 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(), @@ -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, @@ -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 { diff --git a/cmd/soroban-cli/src/config/address.rs b/cmd/soroban-cli/src/config/address.rs index a60c676729..0213c3fa2f 100644 --- a/cmd/soroban-cli/src/config/address.rs +++ b/cmd/soroban-cli/src/config/address.rs @@ -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()?) + } } } } diff --git a/cmd/soroban-cli/src/config/key.rs b/cmd/soroban-cli/src/config/key.rs index 9d21ad0615..222a68339a 100644 --- a/cmd/soroban-cli/src/config/key.rs +++ b/cmd/soroban-cli/src/config/key.rs @@ -28,8 +28,26 @@ pub enum Key { } impl Key { + pub async fn muxed_account_async(&self, hd_path: Option) -> Result { + 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) -> Result { 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 { diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index 432d7b2d78..d2d5316edf 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -123,7 +123,8 @@ impl Args { signers, seq_num, &network.network_passphrase, - )?) + ) + .await?) } pub fn get_network(&self) -> Result { diff --git a/cmd/soroban-cli/src/config/sc_address.rs b/cmd/soroban-cli/src/config/sc_address.rs index f5ac8ba7a9..4a958c301f 100644 --- a/cmd/soroban-cli/src/config/sc_address.rs +++ b/cmd/soroban-cli/src/config/sc_address.rs @@ -40,6 +40,35 @@ impl FromStr for UnresolvedScAddress { } impl UnresolvedScAddress { + pub async fn resolve_async(self, + locator: &locator::Args, + network_passphrase: &str, + ) -> Result { + 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, diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index 6784f03fa1..b8feaf6aaa 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -126,14 +126,34 @@ impl Secret { }) } + + pub async fn public_key_async(&self, index: Option) -> Result { + 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) -> Result { - 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!(), } } diff --git a/cmd/soroban-cli/src/signer/ledger.rs b/cmd/soroban-cli/src/signer/ledger.rs index 534cebec06..85f9e5ce1c 100644 --- a/cmd/soroban-cli/src/signer/ledger.rs +++ b/cmd/soroban-cli/src/signer/ledger.rs @@ -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}; @@ -96,6 +97,12 @@ mod ledger_impl { Ok(DecoratedSignature { hint, signature }) } + pub async fn sign_payload(&self, payload: [u8; 32]) -> Result { + 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 { Ok(self.signer.get_public_key(&self.index.into()).await?) } diff --git a/cmd/soroban-cli/src/signer/mod.rs b/cmd/soroban-cli/src/signer/mod.rs index c699b30b33..4df9739fa8 100644 --- a/cmd/soroban-cli/src/signer/mod.rs +++ b/cmd/soroban-cli/src/signer/mod.rs @@ -63,7 +63,7 @@ fn requires_auth(txn: &Transaction) -> Option { // 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], @@ -118,12 +118,12 @@ 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); } @@ -131,10 +131,11 @@ pub fn sign_soroban_authorizations( 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 => { @@ -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, @@ -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![ ( @@ -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()?; @@ -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 { + // 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 { 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) + } } } }