From 8c080cef8d471a52bf84809e07f06eb42d8f9c27 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 24 Jun 2024 14:53:35 -0400 Subject: [PATCH 01/50] feat: add stellar_ledger as workspace dep Also update test files to add needed deps --- .github/workflows/binaries.yml | 3 +++ .github/workflows/e2e.yml | 3 +++ .github/workflows/full-help-docs.yml | 3 +++ .github/workflows/rpc-tests.yml | 3 +++ Cargo.lock | 1 + Cargo.toml | 4 ++++ cmd/soroban-cli/Cargo.toml | 2 ++ 7 files changed, 19 insertions(+) diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml index 97304b7bd..4d8440cd5 100644 --- a/.github/workflows/binaries.yml +++ b/.github/workflows/binaries.yml @@ -39,6 +39,9 @@ jobs: - run: rustup target add ${{ matrix.sys.target }} - if: matrix.sys.target == 'aarch64-unknown-linux-gnu' run: sudo apt-get update && sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + - if: matrix.sys.target == 'aarch64-unknown-linux-gnu' + run: | + sudo apt install -y libudev-dev - name: Setup vars run: | version="$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[] | select(.name == "stellar-cli") | .version')" diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 0704d4a55..2b02b4f5c 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -84,6 +84,9 @@ jobs: path: system-test/js-stellar-sdk - uses: stellar/actions/rust-cache@main + - name: install libudev-dev + run: | + sudo apt install -y libudev-dev - name: Build system test with component versions run: | cd $GITHUB_WORKSPACE/system-test diff --git a/.github/workflows/full-help-docs.yml b/.github/workflows/full-help-docs.yml index 4893a88fd..f8f61521c 100644 --- a/.github/workflows/full-help-docs.yml +++ b/.github/workflows/full-help-docs.yml @@ -14,6 +14,9 @@ jobs: - uses: actions/checkout@v3 - uses: stellar/actions/rust-cache@main - run: rustup update + - name: install libudev-dev + run: | + sudo apt install -y libudev-dev - name: Generate help doc # this looks goofy to get GITHUB_OUTPUT to work with multi-line return values; # see https://stackoverflow.com/a/74266196/249801 diff --git a/.github/workflows/rpc-tests.yml b/.github/workflows/rpc-tests.yml index ad999830a..b9279ab95 100644 --- a/.github/workflows/rpc-tests.yml +++ b/.github/workflows/rpc-tests.yml @@ -35,6 +35,9 @@ jobs: target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - run: rustup update + - name: install libudev-dev + run: | + sudo apt install -y libudev-dev - run: cargo build - run: rustup target add wasm32-unknown-unknown - run: make build-test-wasms diff --git a/Cargo.lock b/Cargo.lock index 546f478fc..bb711de28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4712,6 +4712,7 @@ dependencies = [ "soroban-spec-rust", "soroban-spec-tools", "soroban-spec-typescript", + "stellar-ledger", "stellar-rpc-client", "stellar-strkey", "stellar-xdr", diff --git a/Cargo.toml b/Cargo.toml index f6b09d527..f965ac636 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,10 @@ version = "=21.1.0-rc.1" version = "=21.0.0" path = "cmd/soroban-cli" +[workspace.dependencies.stellar-ledger] +version = "=21.0.0" +path = "cmd/crates/stellar-ledger" + [workspace.dependencies.soroban-rpc] package = "stellar-rpc-client" version = "21.3.1" diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index a48ad2994..075a1ccb7 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -50,6 +50,8 @@ soroban-ledger-snapshot = { workspace = true } stellar-strkey = { workspace = true } soroban-sdk = { workspace = true } soroban-rpc = { workspace = true } +stellar-ledger = { workspace = true } + clap = { workspace = true, features = [ "derive", "env", From e60012735d79ca4379863db8f0243e1c002b54d7 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 24 Jun 2024 15:17:08 -0400 Subject: [PATCH 02/50] feat: add Stellar; tx sign/send --- Cargo.lock | 38 +- .../tests/it/integration/custom_types.rs | 8 +- .../tests/it/integration/hello_world.rs | 11 +- .../soroban-test/tests/it/integration/tx.rs | 50 ++- cmd/crates/stellar-ledger/src/hd_path.rs | 21 +- cmd/crates/stellar-ledger/src/lib.rs | 69 ++-- cmd/soroban-cli/Cargo.toml | 1 + cmd/soroban-cli/src/commands/tx/mod.rs | 13 +- cmd/soroban-cli/src/commands/tx/send.rs | 53 +++ cmd/soroban-cli/src/commands/tx/sign.rs | 39 ++ cmd/soroban-cli/src/signer.rs | 374 +++++++++++------- 11 files changed, 475 insertions(+), 202 deletions(-) create mode 100644 cmd/soroban-cli/src/commands/tx/send.rs create mode 100644 cmd/soroban-cli/src/commands/tx/sign.rs diff --git a/Cargo.lock b/Cargo.lock index bb711de28..59cace36e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3193,6 +3193,17 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "libredox" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall", +] + [[package]] name = "link-cplusplus" version = "1.0.9" @@ -3406,6 +3417,12 @@ dependencies = [ "libc", ] +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + [[package]] name = "object" version = "0.32.2" @@ -3881,6 +3898,12 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_termios" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" + [[package]] name = "redox_users" version = "0.4.4" @@ -3888,7 +3911,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", - "libredox", + "libredox 0.0.1", "thiserror", ] @@ -4722,6 +4745,7 @@ dependencies = [ "tempfile", "termcolor", "termcolor_output", + "termion", "thiserror", "tokio", "toml 0.5.11", @@ -5298,6 +5322,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f34dde0bb841eb3762b42bdff8db11bbdbc0a3bd7b32012955f5ce1d081f86c1" +[[package]] +name = "termion" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ccce68e518d1173e80876edd54760b60b792750d0cab6444a79101c6ea03848" +dependencies = [ + "libc", + "libredox 0.0.2", + "numtoa", + "redox_termios", +] + [[package]] name = "termtree" version = "0.4.1" diff --git a/cmd/crates/soroban-test/tests/it/integration/custom_types.rs b/cmd/crates/soroban-test/tests/it/integration/custom_types.rs index 6739ff3da..668a91aa7 100644 --- a/cmd/crates/soroban-test/tests/it/integration/custom_types.rs +++ b/cmd/crates/soroban-test/tests/it/integration/custom_types.rs @@ -9,7 +9,13 @@ use super::util::invoke_with_roundtrip; fn invoke_custom(e: &TestEnv, id: &str, func: &str) -> assert_cmd::Command { let mut s = e.new_assert_cmd("contract"); - s.arg("invoke").arg("--id").arg(id).arg("--").arg(func); + s.env("RUST_LOG", "soroban_cli::log::diagnostic_event=off") + .arg("invoke") + .arg("--id") + .arg(id) + .arg("--is-view") + .arg("--") + .arg(func); s } 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 16c13dd25..fc0c56bd8 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -73,8 +73,8 @@ async fn invoke() { .stdout_as_str(); let dir = sandbox.dir(); let seed_phrase = std::fs::read_to_string(dir.join(".soroban/identity/test.toml")).unwrap(); - let s = toml::from_str::(&seed_phrase).unwrap(); - let secret::Secret::SeedPhrase { seed_phrase } = s else { + let s = toml::from_str::(&seed_phrase).unwrap(); + let secret::Signer::SeedPhrase { seed_phrase } = s else { panic!("Expected seed phrase") }; let id = &deploy_hello(sandbox).await; @@ -106,7 +106,7 @@ async fn invoke() { config_locator .write_identity( "testone", - &secret::Secret::SecretKey { + &secret::Signer::SecretKey { secret_key: secret_key_1.clone(), }, ) @@ -222,6 +222,7 @@ async fn invoke_auth_with_different_test_account_fail(sandbox: &TestEnv, id: &st "--hd-path=0", "--id", id, + "--fee=1000000", "--", "auth", &format!("--addr={addr}"), @@ -230,8 +231,8 @@ async fn invoke_auth_with_different_test_account_fail(sandbox: &TestEnv, id: &st .await; let e = res.unwrap_err(); assert!( - matches!(e, contract::invoke::Error::Config(_)), - "Expected config error got {e:?}" + matches!(e, contract::invoke::Error::Rpc(_)), + "Expected rpc error got {e:?}" ); } diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 9f5204a8d..450c459b4 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -1,4 +1,6 @@ -use soroban_sdk::xdr::{Limits, ReadXdr, TransactionEnvelope, WriteXdr}; +use soroban_sdk::xdr::{ + Limits, ReadXdr, TransactionEnvelope, TransactionV1Envelope, VecM, WriteXdr, +}; use soroban_test::{AssertExt, TestEnv}; use crate::integration::util::{deploy_contract, DeployKind, HELLO_WORLD}; @@ -30,3 +32,49 @@ async fn txn_simulate() { assembled_str ); } + +#[tokio::test] +async fn txn_send() { + let sandbox = &TestEnv::new(); + sandbox + .new_assert_cmd("contract") + .arg("install") + .args(["--wasm", HELLO_WORLD.path().as_os_str().to_str().unwrap()]) + .assert() + .success(); + + let xdr_base64 = deploy_contract(sandbox, HELLO_WORLD, DeployKind::SimOnly).await; + println!("{xdr_base64}"); + let tx_env = TransactionEnvelope::from_xdr_base64(&xdr_base64, Limits::none()).unwrap(); + let tx_env = sign(sandbox, tx_env); + + println!( + "Transaction to send:\n{}", + tx_env.to_xdr_base64(Limits::none()).unwrap() + ); + + let assembled_str = sandbox + .new_assert_cmd("tx") + .arg("send") + .arg("--source=test") + .write_stdin(tx_env.to_xdr_base64(Limits::none()).unwrap()) + .assert() + .success() + .stdout_as_str(); + println!("Transaction sent: {assembled_str}"); +} + +fn sign(sandbox: &TestEnv, tx_env: TransactionEnvelope) -> TransactionEnvelope { + TransactionEnvelope::from_xdr_base64( + sandbox + .new_assert_cmd("tx") + .arg("sign") + .arg("--source=test") + .write_stdin(tx_env.to_xdr_base64(Limits::none()).unwrap().as_bytes()) + .assert() + .success() + .stdout_as_str(), + Limits::none(), + ) + .unwrap() +} diff --git a/cmd/crates/stellar-ledger/src/hd_path.rs b/cmd/crates/stellar-ledger/src/hd_path.rs index 07ed133f1..4a207d644 100644 --- a/cmd/crates/stellar-ledger/src/hd_path.rs +++ b/cmd/crates/stellar-ledger/src/hd_path.rs @@ -1,9 +1,8 @@ -use crate::Error; - #[derive(Clone, Copy)] pub struct HdPath(pub u32); impl HdPath { + #[must_use] pub fn depth(&self) -> u8 { let path: slip10::BIP32Path = self.into(); path.depth() @@ -23,7 +22,8 @@ impl From<&u32> for HdPath { } impl HdPath { - pub fn to_vec(&self) -> Result, Error> { + #[must_use] + pub fn to_vec(&self) -> Vec { hd_path_to_bytes(&self.into()) } } @@ -35,16 +35,11 @@ impl From<&HdPath> for slip10::BIP32Path { } } -fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Result, Error> { +fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { let hd_path_indices = 0..hd_path.depth(); - let result = hd_path_indices + // Unsafe unwrap is safe because the depth is the length of interneal vector + hd_path_indices .into_iter() - .map(|index| { - Ok(hd_path - .index(index) - .ok_or_else(|| Error::Bip32PathError(format!("{hd_path}")))? - .to_be_bytes()) - }) - .collect::, Error>>()?; - Ok(result.into_iter().flatten().collect()) + .flat_map(|index| unsafe { hd_path.index(index).unwrap_unchecked().to_be_bytes() }) + .collect() } diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index c74365af2..69789b735 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -1,8 +1,9 @@ use hd_path::HdPath; -use ledger_transport::{APDUCommand, Exchange}; +use ledger_transport::APDUCommand; +pub use ledger_transport_hid::TransportNativeHID; use ledger_transport_hid::{ hidapi::{HidApi, HidError}, - LedgerHIDError, TransportNativeHID, + LedgerHIDError, }; use soroban_env_host::xdr::{Hash, Transaction}; @@ -15,6 +16,8 @@ use stellar_xdr::curr::{ pub use crate::signer::Blob; pub mod hd_path; +pub use ledger_transport::Exchange; + mod signer; // this is from https://github.com/LedgerHQ/ledger-live/blob/36cfbf3fa3300fd99bcee2ab72e1fd8f280e6280/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L181 @@ -80,6 +83,11 @@ pub struct LedgerSigner { unsafe impl Send for LedgerSigner where T: Exchange {} unsafe impl Sync for LedgerSigner where T: Exchange {} +/// Returns a new `LedgerSigner` with a native HID transport, e.i. the transport is connected to the Ledger device +/// +/// # Errors +/// +/// Returns an error if there is an issue with connecting with the device pub fn native() -> Result, Error> { Ok(LedgerSigner { transport: get_transport()?, @@ -93,11 +101,7 @@ where pub fn new(transport: T) -> Self { Self { transport } } - pub fn native() -> Result, Error> { - Ok(LedgerSigner { - transport: get_transport()?, - }) - } + /// Get the device app's configuration /// # Errors /// Returns an error if there is an issue with connecting with the device or getting the config from the device @@ -141,7 +145,7 @@ where }; let mut signature_payload_as_bytes = signature_payload.to_xdr(Limits::none())?; - let mut hd_path_to_bytes = hd_path.into().to_vec()?; + let mut hd_path_to_bytes = hd_path.into().to_vec(); let capacity = 1 + hd_path_to_bytes.len() + signature_payload_as_bytes.len(); let mut data: Vec = Vec::with_capacity(capacity); @@ -182,7 +186,9 @@ where } /// The `display_and_confirm` bool determines if the Ledger will display the public key on its screen and requires user approval to share - async fn get_public_key_with_display_flag( + /// # Errors + /// Returns an error if there is an issue with connecting with the device or getting the public key from the device + pub async fn get_public_key_with_display_flag( &self, hd_path: impl Into, display_and_confirm: bool, @@ -191,7 +197,7 @@ where // the first element of the data should be the number of elements in the path let hd_path = hd_path.into(); let hd_path_elements_count = hd_path.depth(); - let mut hd_path_to_bytes = hd_path.to_vec()?; + let mut hd_path_to_bytes = hd_path.to_vec(); hd_path_to_bytes.insert(0, hd_path_elements_count); let p2 = if display_and_confirm { @@ -241,6 +247,30 @@ where )), } } + + /// Sign a blob of data with the account on the Ledger device + /// # Errors + /// Returns an error if there is an issue with connecting with the device or signing the given tx on the device + pub async fn sign_data(&self, index: &HdPath, blob: &[u8]) -> Result, Error> { + let mut hd_path_to_bytes = index.to_vec(); + + let capacity = 1 + hd_path_to_bytes.len() + blob.len(); + let mut data: Vec = Vec::with_capacity(capacity); + + data.insert(0, HD_PATH_ELEMENTS_COUNT); + data.append(&mut hd_path_to_bytes); + data.extend_from_slice(blob); + + let command = APDUCommand { + cla: CLA, + ins: SIGN_TX_HASH, + p1: P1_SIGN_TX_HASH, + p2: P2_SIGN_TX_HASH, + data, + }; + + self.send_command_to_ledger(command).await + } } #[async_trait::async_trait] @@ -265,24 +295,7 @@ where /// # Errors /// Returns an error if there is an issue with connecting with the device or signing the given tx on the device. Or, if the device has not enabled hash signing async fn sign_blob(&self, index: &Self::Key, blob: &[u8]) -> Result, Error> { - let mut hd_path_to_bytes = index.to_vec()?; - - let capacity = 1 + hd_path_to_bytes.len() + blob.len(); - let mut data: Vec = Vec::with_capacity(capacity); - - data.insert(0, HD_PATH_ELEMENTS_COUNT); - data.append(&mut hd_path_to_bytes); - data.extend_from_slice(blob); - - let command = APDUCommand { - cla: CLA, - ins: SIGN_TX_HASH, - p1: P1_SIGN_TX_HASH, - p2: P2_SIGN_TX_HASH, - data, - }; - - self.send_command_to_ledger(command).await + self.sign_data(index, blob).await } } diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index 075a1ccb7..d8eb044de 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -116,6 +116,7 @@ rust-embed = { version = "8.2.0", features = ["debug-embed"] } bollard = { workspace=true } futures-util = "0.3.30" home = "0.5.9" +termion = "4.0.0" # For hyper-tls [target.'cfg(unix)'.dependencies] openssl = { version = "=0.10.55", features = ["vendored"] } diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index 48161e3c7..cedfaa322 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -2,6 +2,8 @@ use clap::Parser; use super::global; +pub mod send; +pub mod sign; pub mod simulate; pub mod xdr; @@ -9,19 +11,28 @@ pub mod xdr; pub enum Cmd { /// Simulate a transaction envelope from stdin Simulate(simulate::Cmd), + /// Sign a transaction with a ledger or local key + Sign(sign::Cmd), + /// Send a transaction envelope to the network + Send(send::Cmd), } #[derive(thiserror::Error, Debug)] pub enum Error { - /// An error during the simulation #[error(transparent)] Simulate(#[from] simulate::Error), + #[error(transparent)] + Send(#[from] send::Error), + #[error(transparent)] + Sign(#[from] sign::Error), } impl Cmd { pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { match self { Cmd::Simulate(cmd) => cmd.run(global_args).await?, + Cmd::Sign(cmd) => cmd.run().await?, + Cmd::Send(cmd) => cmd.run(global_args).await?, }; Ok(()) } diff --git a/cmd/soroban-cli/src/commands/tx/send.rs b/cmd/soroban-cli/src/commands/tx/send.rs new file mode 100644 index 000000000..08c32c2da --- /dev/null +++ b/cmd/soroban-cli/src/commands/tx/send.rs @@ -0,0 +1,53 @@ +use async_trait::async_trait; +use soroban_rpc::GetTransactionResponse; + +use crate::commands::{config, global, NetworkRunnable}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrArgs(#[from] super::xdr::Error), + #[error(transparent)] + Config(#[from] super::super::config::Error), + #[error(transparent)] + Rpc(#[from] crate::rpc::Error), + #[error(transparent)] + SerdeJson(#[from] serde_json::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +/// Command to send a transaction envelope to the network +/// e.g. `cat file.txt | soroban tx send` +pub struct Cmd { + #[clap(flatten)] + pub config: super::super::config::Args, +} + +impl Cmd { + pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let response = self + .run_against_rpc_server(Some(global_args), Some(&self.config)) + .await?; + println!("{}", serde_json::to_string_pretty(&response)?); + Ok(()) + } +} + +#[async_trait] +impl NetworkRunnable for Cmd { + type Error = Error; + + type Result = GetTransactionResponse; + async fn run_against_rpc_server( + &self, + _: Option<&global::Args>, + config: Option<&config::Args>, + ) -> Result { + let config = config.unwrap_or(&self.config); + let network = config.get_network()?; + let client = crate::rpc::Client::new(&network.rpc_url)?; + let tx_env = super::xdr::tx_envelope_from_stdin()?; + Ok(client.send_transaction_polling(&tx_env).await?) + } +} diff --git a/cmd/soroban-cli/src/commands/tx/sign.rs b/cmd/soroban-cli/src/commands/tx/sign.rs new file mode 100644 index 000000000..d4a201244 --- /dev/null +++ b/cmd/soroban-cli/src/commands/tx/sign.rs @@ -0,0 +1,39 @@ +use crate::xdr::{self, Limits, Transaction, TransactionEnvelope, WriteXdr}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + XdrArgs(#[from] super::xdr::Error), + #[error(transparent)] + Config(#[from] super::super::config::Error), + #[error(transparent)] + Xdr(#[from] xdr::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + #[clap(flatten)] + pub config: super::super::config::Args, +} + +impl Cmd { + #[allow(clippy::unused_async)] + pub async fn run(&self) -> Result<(), Error> { + let txn_env = super::xdr::tx_envelope_from_stdin()?; + let envelope = self.sign_env(txn_env).await?; + println!("{}", envelope.to_xdr_base64(Limits::none())?.trim()); + Ok(()) + } + + pub async fn sign(&self, tx: Transaction) -> Result { + Ok(self.config.sign(tx).await?) + } + + pub async fn sign_env( + &self, + tx_env: TransactionEnvelope, + ) -> Result { + self.sign(super::xdr::unwrap_envelope_v1(tx_env)?).await + } +} diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index 580a61a5e..af865b110 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -1,7 +1,8 @@ use ed25519_dalek::ed25519::signature::Signer; use sha2::{Digest, Sha256}; +use termion::{event::Key, get_tty, input::TermRead}; -use soroban_env_host::xdr::{ +use crate::xdr::{ self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, @@ -9,6 +10,7 @@ use soroban_env_host::xdr::{ TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, Uint256, WriteXdr, }; +use stellar_ledger::{Exchange, LedgerSigner}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -19,11 +21,11 @@ pub enum Error { #[error("Missing signing key for account {address}")] MissingSignerForAddress { address: String }, #[error(transparent)] - TryFromSlice(#[from] std::array::TryFromSliceError), - #[error("User cancelled signing, perhaps need to add -y")] - UserCancelledSigning, + Ledger(#[from] stellar_ledger::Error), #[error(transparent)] Xdr(#[from] xdr::Error), + #[error("User cancelled signing, perhaps need to remove --check")] + UserCancelledSigning, } fn requires_auth(txn: &Transaction) -> Option { @@ -41,53 +43,154 @@ fn requires_auth(txn: &Transaction) -> Option { .then(move || op.clone()) } -// 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( - raw: &Transaction, - source_key: &ed25519_dalek::SigningKey, - signers: &[ed25519_dalek::SigningKey], - signature_expiration_ledger: u32, - network_passphrase: &str, -) -> Result, Error> { - let mut tx = raw.clone(); - let Some(mut op) = requires_auth(&tx) else { - return Ok(None); - }; +/// A trait for signing Stellar transactions and Soroban authorization entries +#[allow(async_fn_in_trait)] +pub trait Stellar { + async fn get_public_key(&self) -> Result; - let Operation { - body: OperationBody::InvokeHostFunction(ref mut body), - .. - } = op - else { - return Ok(None); - }; + async fn sign_blob(&self, blob: &[u8]) -> Result, Error>; - let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into()); - - let verification_key = source_key.verifying_key(); - let source_address = verification_key.as_bytes(); - - let signed_auths = body - .auth - .as_slice() - .iter() - .map(|raw_auth| { - let mut auth = raw_auth.clone(); - let SorobanAuthorizationEntry { - credentials: SorobanCredentials::Address(ref mut credentials), - .. - } = auth - else { - // Doesn't need special signing - return Ok(auth); - }; - let SorobanAddressCredentials { ref address, .. } = credentials; + /// Sign a transaction hash with the given source account + /// # Errors + /// Returns an error if the source account is not found + async fn sign_txn_hash(&self, txn: [u8; 32]) -> Result { + let source_account = self.get_public_key().await?; + eprintln!( + "{} about to sign hash: {}", + source_account.to_string(), + hex::encode(txn) + ); + let tx_signature = self.sign_blob(&txn).await?; + Ok(DecoratedSignature { + // TODO: remove this unwrap. It's safe because we know the length of the array + hint: SignatureHint(source_account.0[28..].try_into().unwrap()), + signature: Signature(tx_signature.try_into()?), + }) + } + + /// Sign a Soroban authorization entry with the given address + /// # Errors + /// Returns an error if the address is not found + async fn sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + network_passphrase: &str, + ) -> Result { + let address = self.get_public_key().await?; + let mut auth = unsigned_entry.clone(); + let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(ref mut credentials), + .. + } = auth + else { + // Doesn't need special signing + return Ok(auth); + }; + let SorobanAddressCredentials { + nonce, + signature_expiration_ledger, + .. + } = credentials; + + let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { + network_id: hash(network_passphrase), + invocation: auth.root_invocation.clone(), + nonce: *nonce, + signature_expiration_ledger: *signature_expiration_ledger, + }) + .to_xdr(Limits::none())?; + + let payload = Sha256::digest(preimage); + let signature = self.sign_blob(&payload).await?; + + let map = ScMap::sorted_from(vec![ + ( + ScVal::Symbol(ScSymbol("public_key".try_into()?)), + ScVal::Bytes(address.0.to_vec().try_into()?), + ), + ( + ScVal::Symbol(ScSymbol("signature".try_into()?)), + ScVal::Bytes(signature.try_into()?), + ), + ])?; + credentials.signature = ScVal::Vec(Some(vec![ScVal::Map(Some(map))].try_into()?)); + auth.credentials = SorobanCredentials::Address(credentials.clone()); + + Ok(auth) + } + + /// Sign a Stellar transaction with the given source account + /// This is a default implementation that signs the transaction hash and returns a decorated signature + /// # Errors + /// Returns an error if the source account is not found + async fn sign_txn( + &self, + txn: Transaction, + network_passphrase: &str, + ) -> Result { + let signature_payload = TransactionSignaturePayload { + network_id: hash(network_passphrase), + tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(txn.clone()), + }; + let hash = Sha256::digest(signature_payload.to_xdr(Limits::none())?).into(); + let decorated_signature = self.sign_txn_hash(hash).await?; + Ok(TransactionEnvelope::Tx(TransactionV1Envelope { + tx: txn, + signatures: vec![decorated_signature].try_into()?, + })) + } + + /// Sign a Soroban authorization entries for a given transaction and set the expiration ledger + /// # Errors + /// Returns an error if the address is not found + async fn sign_soroban_authorizations( + &self, + raw: &Transaction, + network_passphrase: &str, + ) -> Result, Error> { + let mut tx = raw.clone(); + let Some(mut op) = requires_auth(&tx) else { + return Ok(None); + }; + + let xdr::Operation { + body: OperationBody::InvokeHostFunction(ref mut body), + .. + } = op + else { + return Ok(None); + }; + + let mut auths = body.auth.to_vec(); + for auth in &mut auths { + *auth = self + .maybe_sign_soroban_authorization_entry(auth, network_passphrase) + .await?; + } + body.auth = auths.try_into()?; + tx.operations = vec![op].try_into()?; + Ok(Some(tx)) + } + /// Sign a Soroban authorization entry if the address is public key + /// # Errors + /// Returns an error if the address in entry is a contract + async fn maybe_sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + network_passphrase: &str, + ) -> Result { + if let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(SorobanAddressCredentials { ref address, .. }), + .. + } = unsigned_entry + { // See if we have a signer for this authorizationEntry // If not, then we Error let needle = match address { - ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(ref a)))) => a, + ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(a)))) => { + stellar_strkey::ed25519::PublicKey(*a) + } ScAddress::Contract(Hash(c)) => { // This address is for a contract. This means we're using a custom // smart-contract account. Currently the CLI doesn't support that yet. @@ -97,121 +200,88 @@ pub fn sign_soroban_authorizations( }); } }; - let signer = if let Some(s) = signers - .iter() - .find(|s| needle == s.verifying_key().as_bytes()) - { - s - } else if needle == source_address { - // This is the source address, so we can sign it - source_key - } else { - // We don't have a signer for this address - return Err(Error::MissingSignerForAddress { - address: stellar_strkey::Strkey::PublicKeyEd25519( - stellar_strkey::ed25519::PublicKey(*needle), - ) - .to_string(), - }); + if needle == self.get_public_key().await? { + return Ok(unsigned_entry.clone()); + } + self.sign_soroban_authorization_entry(unsigned_entry, network_passphrase) + .await + } else { + Ok(unsigned_entry.clone()) + } + } +} + +fn hash(network_passphrase: &str) -> xdr::Hash { + xdr::Hash(Sha256::digest(network_passphrase.as_bytes()).into()) +} + +pub struct LocalKey { + key: ed25519_dalek::SigningKey, + prompt: bool, +} + +impl LocalKey { + pub fn new(key: ed25519_dalek::SigningKey, prompt: bool) -> Self { + Self { key, prompt } + } +} + +impl Stellar for LocalKey { + async fn sign_blob(&self, data: &[u8]) -> Result, Error> { + if self.prompt { + eprintln!("Press 'y' or 'Y' for yes, any other key for no:"); + match read_key() { + 'y' | 'Y' => { + eprintln!("Signing now..."); + } + _ => return Err(Error::UserCancelledSigning), }; + } + let sig = self.key.sign(data); + Ok(sig.to_bytes().to_vec()) + } - sign_soroban_authorization_entry( - raw_auth, - signer, - signature_expiration_ledger, - &network_id, - ) - }) - .collect::, Error>>()?; + async fn get_public_key(&self) -> Result { + Ok(stellar_strkey::ed25519::PublicKey( + self.key.verifying_key().to_bytes(), + )) + } +} - body.auth = signed_auths.try_into()?; - tx.operations = vec![op].try_into()?; - Ok(Some(tx)) +pub struct Ledger { + index: u32, + signer: LedgerSigner, } -fn sign_soroban_authorization_entry( - raw: &SorobanAuthorizationEntry, - signer: &ed25519_dalek::SigningKey, - signature_expiration_ledger: u32, - network_id: &Hash, -) -> Result { - let mut auth = raw.clone(); - let SorobanAuthorizationEntry { - credentials: SorobanCredentials::Address(ref mut credentials), - .. - } = auth - else { - // Doesn't need special signing - return Ok(auth); - }; - let SorobanAddressCredentials { nonce, .. } = credentials; - - let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { - network_id: network_id.clone(), - invocation: auth.root_invocation.clone(), - nonce: *nonce, - signature_expiration_ledger, - }) - .to_xdr(Limits::none())?; - - let payload = Sha256::digest(preimage); - let signature = signer.sign(&payload); - - let map = ScMap::sorted_from(vec![ - ( - ScVal::Symbol(ScSymbol("public_key".try_into()?)), - ScVal::Bytes( - signer - .verifying_key() - .to_bytes() - .to_vec() - .try_into() - .map_err(Error::Xdr)?, - ), - ), - ( - ScVal::Symbol(ScSymbol("signature".try_into()?)), - ScVal::Bytes( - signature - .to_bytes() - .to_vec() - .try_into() - .map_err(Error::Xdr)?, - ), - ), - ]) - .map_err(Error::Xdr)?; - credentials.signature = ScVal::Vec(Some( - vec![ScVal::Map(Some(map))].try_into().map_err(Error::Xdr)?, - )); - credentials.signature_expiration_ledger = signature_expiration_ledger; - auth.credentials = SorobanCredentials::Address(credentials.clone()); - Ok(auth) +pub fn native(index: u32) -> Result, Error> { + let signer = stellar_ledger::native()?; + Ok(Ledger { index, signer }) } -pub fn sign_tx( - key: &ed25519_dalek::SigningKey, - tx: &Transaction, - network_passphrase: &str, -) -> Result { - let tx_hash = hash(tx, network_passphrase)?; - let tx_signature = key.sign(&tx_hash); - - let decorated_signature = DecoratedSignature { - hint: SignatureHint(key.verifying_key().to_bytes()[28..].try_into()?), - signature: Signature(tx_signature.to_bytes().try_into()?), - }; +impl Stellar for Ledger +where + T: Exchange, +{ + async fn get_public_key(&self) -> Result { + Ok(self + .signer + .get_public_key_with_display_flag(self.index, false) + .await?) + } - Ok(TransactionEnvelope::Tx(TransactionV1Envelope { - tx: tx.clone(), - signatures: [decorated_signature].try_into()?, - })) + async fn sign_blob(&self, blob: &[u8]) -> Result, Error> { + Ok(self.signer.sign_data(&self.index.into(), blob).await?) + } } -pub fn hash(tx: &Transaction, network_passphrase: &str) -> Result<[u8; 32], xdr::Error> { - let signature_payload = TransactionSignaturePayload { - network_id: Hash(Sha256::digest(network_passphrase).into()), - tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(tx.clone()), - }; - Ok(Sha256::digest(signature_payload.to_xdr(Limits::none())?).into()) +pub fn read_key() -> char { + let tty = get_tty().unwrap(); + if let Some(key) = tty.keys().next() { + match key.unwrap() { + Key::Char(c) => c, + _ => '_', + } + } else { + ' ' + } } From 8134f3e6346a4127fbf9823fc7c5faa2c7d808d2 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 24 Jun 2024 16:12:23 -0400 Subject: [PATCH 03/50] feat: add signer to other commands and ban keys named "ledger" --- FULL_HELP_DOCS.md | 100 +++++++++++- cmd/crates/soroban-test/src/lib.rs | 4 +- cmd/crates/soroban-test/tests/it/config.rs | 30 ++++ cmd/crates/soroban-test/tests/it/util.rs | 6 +- .../src/commands/config/locator.rs | 40 ++++- cmd/soroban-cli/src/commands/config/mod.rs | 68 ++++---- cmd/soroban-cli/src/commands/config/secret.rs | 85 +++++++--- .../src/commands/contract/deploy/asset.rs | 2 +- .../src/commands/contract/deploy/wasm.rs | 2 +- .../src/commands/contract/extend.rs | 2 +- .../src/commands/contract/install.rs | 2 +- .../src/commands/contract/invoke.rs | 149 ++++++++++-------- .../src/commands/contract/restore.rs | 2 +- cmd/soroban-cli/src/commands/keys/add.rs | 2 +- cmd/soroban-cli/src/commands/keys/address.rs | 21 +-- cmd/soroban-cli/src/commands/keys/fund.rs | 2 +- cmd/soroban-cli/src/commands/keys/generate.rs | 11 +- cmd/soroban-cli/src/commands/keys/mod.rs | 2 +- 18 files changed, 373 insertions(+), 157 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 0842b6d98..ad1aa3435 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -58,6 +58,8 @@ This document contains the help content for the `stellar` command-line program. * [`stellar version`↴](#stellar-version) * [`stellar tx`↴](#stellar-tx) * [`stellar tx simulate`↴](#stellar-tx-simulate) +* [`stellar tx sign`↴](#stellar-tx-sign) +* [`stellar tx send`↴](#stellar-tx-send) * [`stellar cache`↴](#stellar-cache) * [`stellar cache clean`↴](#stellar-cache-clean) * [`stellar cache path`↴](#stellar-cache-path) @@ -217,6 +219,10 @@ Get Id of builtin Soroban Asset Contract. Deprecated, use `stellar contract id a Possible values: `true`, `false` * `--config-dir ` — Location of config directory, default is "." +* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes + + Possible values: `true`, `false` + @@ -239,6 +245,10 @@ Deploy builtin Soroban Asset Contract Possible values: `true`, `false` * `--config-dir ` — Location of config directory, default is "." +* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes + + Possible values: `true`, `false` + * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -398,6 +408,10 @@ If no keys are specified the contract itself is extended. Possible values: `true`, `false` * `--config-dir ` — Location of config directory, default is "." +* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes + + Possible values: `true`, `false` + * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -438,6 +452,10 @@ Deploy a wasm contract Possible values: `true`, `false` * `--config-dir ` — Location of config directory, default is "." +* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes + + Possible values: `true`, `false` + * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -517,6 +535,10 @@ Deploy builtin Soroban Asset Contract Possible values: `true`, `false` * `--config-dir ` — Location of config directory, default is "." +* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes + + Possible values: `true`, `false` + @@ -539,6 +561,10 @@ Deploy normal Wasm Contract Possible values: `true`, `false` * `--config-dir ` — Location of config directory, default is "." +* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes + + Possible values: `true`, `false` + @@ -611,6 +637,10 @@ Install a WASM file to the ledger without creating a contract instance Possible values: `true`, `false` * `--config-dir ` — Location of config directory, default is "." +* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes + + Possible values: `true`, `false` + * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -668,6 +698,10 @@ stellar contract invoke ... -- --help Possible values: `true`, `false` * `--config-dir ` — Location of config directory, default is "." +* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes + + Possible values: `true`, `false` + * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -745,6 +779,10 @@ Print the current value of a contract-data ledger entry Possible values: `true`, `false` * `--config-dir ` — Location of config directory, default is "." +* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes + + Possible values: `true`, `false` + @@ -788,6 +826,10 @@ If no keys are specificed the contract itself is restored. Possible values: `true`, `false` * `--config-dir ` — Location of config directory, default is "." +* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes + + Possible values: `true`, `false` + * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -1436,6 +1478,8 @@ Sign, Simulate, and Send transactions ###### **Subcommands:** * `simulate` — Simulate a transaction envelope from stdin +* `sign` — Sign a transaction with a ledger or local key +* `send` — Send a transaction envelope to the network @@ -1450,13 +1494,67 @@ Simulate a transaction envelope from stdin * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--global` — Use global config + + Possible values: `true`, `false` + +* `--config-dir ` — Location of config directory, default is "." +* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes + + Possible values: `true`, `false` + + + + +## `stellar tx sign` + +Sign a transaction with a ledger or local key + +**Usage:** `stellar tx sign [OPTIONS] --source-account ` + +###### **Options:** + +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config Possible values: `true`, `false` * `--config-dir ` — Location of config directory, default is "." +* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes + + Possible values: `true`, `false` + + + + +## `stellar tx send` + +Send a transaction envelope to the network + +**Usage:** `stellar tx send [OPTIONS] --source-account ` + +###### **Options:** + +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--global` — Use global config + + Possible values: `true`, `false` + +* `--config-dir ` — Location of config directory, default is "." +* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes + + Possible values: `true`, `false` + diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index 8df6a97c0..9f45decba 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -232,6 +232,7 @@ impl TestEnv { config_dir, }, hd_path: None, + check: false, } } @@ -263,9 +264,10 @@ impl TestEnv { } /// Returns the public key corresponding to the test keys's `hd_path` - pub fn test_address(&self, hd_path: usize) -> String { + pub async fn test_address(&self, hd_path: usize) -> String { self.cmd::(&format!("--hd-path={hd_path}")) .public_key() + .await .unwrap() .to_string() } diff --git a/cmd/crates/soroban-test/tests/it/config.rs b/cmd/crates/soroban-test/tests/it/config.rs index dd33713aa..598f65ec0 100644 --- a/cmd/crates/soroban-test/tests/it/config.rs +++ b/cmd/crates/soroban-test/tests/it/config.rs @@ -153,6 +153,36 @@ fn read_key() { .stdout(predicates::str::contains("test_id\n")); } +#[test] +fn cannot_generate_ledger_key() { + let sandbox = TestEnv::default(); + sandbox + .new_assert_cmd("keys") + .arg("generate") + .arg("ledger") + .assert() + .stdout("") + .stderr("error: Cannot name a Key ledger\n") + .failure(); +} + +#[test] +fn cannot_add_ledger_key() { + let sandbox = TestEnv::default(); + sandbox + .new_assert_cmd("keys") + .env( + "SOROBAN_SECRET_KEY", + "SDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCQYFD", + ) + .arg("add") + .arg("ledger") + .assert() + .stdout("") + .stderr("error: Cannot name a Key ledger\n") + .failure(); +} + #[test] fn generate_key() { let sandbox = TestEnv::default(); diff --git a/cmd/crates/soroban-test/tests/it/util.rs b/cmd/crates/soroban-test/tests/it/util.rs index 6bf5f8c22..b09c9657e 100644 --- a/cmd/crates/soroban-test/tests/it/util.rs +++ b/cmd/crates/soroban-test/tests/it/util.rs @@ -1,7 +1,7 @@ use std::path::Path; use soroban_cli::commands::{ - config::{locator::KeyType, secret::Secret}, + config::{locator::KeyType, secret::SignerKind}, contract, }; use soroban_test::{TestEnv, Wasm, TEST_ACCOUNT}; @@ -17,10 +17,10 @@ pub enum SecretKind { #[allow(clippy::needless_pass_by_value)] pub fn add_key(dir: &Path, name: &str, kind: SecretKind, data: &str) { let secret = match kind { - SecretKind::Seed => Secret::SeedPhrase { + SecretKind::Seed => SignerKind::SeedPhrase { seed_phrase: data.to_string(), }, - SecretKind::Key => Secret::SecretKey { + SecretKind::Key => SignerKind::SecretKey { secret_key: data.to_string(), }, }; diff --git a/cmd/soroban-cli/src/commands/config/locator.rs b/cmd/soroban-cli/src/commands/config/locator.rs index d32ff5e1d..f18bc3aca 100644 --- a/cmd/soroban-cli/src/commands/config/locator.rs +++ b/cmd/soroban-cli/src/commands/config/locator.rs @@ -13,7 +13,7 @@ use stellar_strkey::DecodeError; use crate::{utils::find_config_dir, Pwd}; -use super::{alias, network::Network, secret::Secret}; +use super::{alias, network::Network, secret::SignerKind}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -65,6 +65,10 @@ pub enum Error { CannotAccessConfigDir, #[error("cannot parse contract ID {0}: {1}")] CannotParseContractId(String, DecodeError), + #[error("Incorrect Key name")] + IncorrectKeyName, + #[error("Cannot name a Key ledger")] + LedgerKeyName, } #[derive(Debug, clap::Args, Default, Clone)] @@ -98,6 +102,27 @@ impl Display for Location { } } +pub struct KeyName(String); + +impl std::ops::Deref for KeyName { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl FromStr for KeyName { + type Err = Error; + + fn from_str(s: &str) -> Result { + if s == "ledger" { + return Err(Error::LedgerKeyName); + } + Ok(KeyName(s.to_string())) + } +} + impl AsRef for Location { fn as_ref(&self) -> &Path { match self { @@ -144,7 +169,7 @@ impl Args { ) } - pub fn write_identity(&self, name: &str, secret: &Secret) -> Result<(), Error> { + pub fn write_identity(&self, name: &KeyName, secret: &SignerKind) -> Result<(), Error> { KeyType::Identity.write(name, secret, &self.config_dir()?) } @@ -197,10 +222,19 @@ impl Args { }) .collect::>()) } - pub fn read_identity(&self, name: &str) -> Result { + + pub fn read_identity(&self, name: &str) -> Result { KeyType::Identity.read_with_global(name, &self.local_config()?) } + pub fn account(&self, account_str: &str) -> Result { + if let Ok(secret) = self.read_identity(account_str) { + Ok(secret) + } else { + Ok(account_str.parse::()?) + } + } + pub fn read_network(&self, name: &str) -> Result { let res = KeyType::Network.read_with_global(name, &self.local_config()?); if let Err(Error::ConfigMissing(_, _)) = &res { diff --git a/cmd/soroban-cli/src/commands/config/mod.rs b/cmd/soroban-cli/src/commands/config/mod.rs index bb55103c1..5c54a5c46 100644 --- a/cmd/soroban-cli/src/commands/config/mod.rs +++ b/cmd/soroban-cli/src/commands/config/mod.rs @@ -1,17 +1,15 @@ use std::path::PathBuf; use clap::{arg, command}; +use secret::StellarSigner; use serde::{Deserialize, Serialize}; +use stellar_strkey::ed25519::PublicKey; -use soroban_rpc::Client; +use crate::signer; +use crate::xdr::{Transaction, TransactionEnvelope}; +use crate::{signer::Stellar, Pwd}; -use crate::{ - signer, - xdr::{Transaction, TransactionEnvelope}, - Pwd, -}; - -use self::{network::Network, secret::Secret}; +use self::network::Network; use super::{keys, network}; @@ -52,52 +50,54 @@ pub struct Args { #[command(flatten)] pub locator: locator::Args, + + /// Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes + #[arg(long)] + pub check: bool, } impl Args { + pub fn signer(&self) -> Result { + Ok(self + .locator + .account(&self.source_account)? + .signer(self.hd_path, self.check)?) + } + pub fn key_pair(&self) -> Result { - let key = self.account(&self.source_account)?; + let key = self.locator.account(&self.source_account)?; Ok(key.key_pair(self.hd_path)?) } - pub async fn sign_with_local_key(&self, tx: Transaction) -> Result { - self.sign(tx).await + pub async fn public_key(&self) -> Result { + Ok(self.signer()?.get_public_key().await?) } - #[allow(clippy::unused_async)] pub async fn sign(&self, tx: Transaction) -> Result { - let key = self.key_pair()?; + let signer = self.signer()?; + self.sign_with_signer(&signer, tx).await + } + + pub async fn sign_with_signer( + &self, + signer: &impl Stellar, + tx: Transaction, + ) -> Result { let Network { network_passphrase, .. } = &self.get_network()?; - Ok(signer::sign_tx(&key, &tx, network_passphrase)?) + Ok(signer.sign_txn(tx, network_passphrase).await?) } pub async fn sign_soroban_authorizations( &self, + signer: &impl Stellar, tx: &Transaction, - signers: &[ed25519_dalek::SigningKey], ) -> Result, Error> { let network = self.get_network()?; - let source_key = self.key_pair()?; - let client = Client::new(&network.rpc_url)?; - let latest_ledger = client.get_latest_ledger().await?.sequence; - let seq_num = latest_ledger + 60; // ~ 5 min - Ok(signer::sign_soroban_authorizations( - tx, - &source_key, - signers, - seq_num, - &network.network_passphrase, - )?) - } - - pub fn account(&self, account_str: &str) -> Result { - if let Ok(secret) = self.locator.read_identity(account_str) { - Ok(secret) - } else { - Ok(account_str.parse::()?) - } + Ok(signer + .sign_soroban_authorizations(tx, &network.network_passphrase) + .await?) } pub fn get_network(&self) -> Result { diff --git a/cmd/soroban-cli/src/commands/config/secret.rs b/cmd/soroban-cli/src/commands/config/secret.rs index 17a921c78..b28c1338d 100644 --- a/cmd/soroban-cli/src/commands/config/secret.rs +++ b/cmd/soroban-cli/src/commands/config/secret.rs @@ -3,7 +3,10 @@ use serde::{Deserialize, Serialize}; use std::{io::Write, str::FromStr}; use stellar_strkey::ed25519::{PrivateKey, PublicKey}; -use crate::utils; +use crate::{ + signer::{self, native, Ledger, LocalKey, Stellar}, + utils, +}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -21,6 +24,10 @@ pub enum Error { Ed25519(#[from] ed25519_dalek::SignatureError), #[error("Invalid address {0}")] InvalidAddress(String), + #[error("Ledger does not reveal secret key")] + LedgerDoesNotRevealSecretKey, + #[error(transparent)] + Stellar(#[from] signer::Error), } #[derive(Debug, clap::Args, Clone)] @@ -36,16 +43,16 @@ pub struct Args { } impl Args { - pub fn read_secret(&self) -> Result { + pub fn kind(&self) -> Result { if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") { - Ok(Secret::SecretKey { secret_key }) + Ok(SignerKind::SecretKey { secret_key }) } else if self.secret_key { println!("Type a secret key: "); let secret_key = read_password()?; let secret_key = PrivateKey::from_string(&secret_key) .map_err(|_| Error::InvalidSecretKey)? .to_string(); - Ok(Secret::SecretKey { secret_key }) + Ok(SignerKind::SecretKey { secret_key }) } else if self.seed_phrase { println!("Type a 12 word seed phrase: "); let seed_phrase = read_password()?; @@ -54,7 +61,7 @@ impl Args { // let len = seed_phrase.len(); // return Err(Error::InvalidSeedPhrase { len }); // } - Ok(Secret::SeedPhrase { + Ok(SignerKind::SeedPhrase { seed_phrase: seed_phrase .into_iter() .map(ToString::to_string) @@ -69,55 +76,72 @@ impl Args { #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] -pub enum Secret { +pub enum SignerKind { SecretKey { secret_key: String }, SeedPhrase { seed_phrase: String }, + Ledger, } -impl FromStr for Secret { +impl FromStr for SignerKind { type Err = Error; fn from_str(s: &str) -> Result { if PrivateKey::from_string(s).is_ok() { - Ok(Secret::SecretKey { + Ok(SignerKind::SecretKey { secret_key: s.to_string(), }) } else if sep5::SeedPhrase::from_str(s).is_ok() { - Ok(Secret::SeedPhrase { + Ok(SignerKind::SeedPhrase { seed_phrase: s.to_string(), }) + } else if s == "ledger" { + Ok(SignerKind::Ledger) } else { Err(Error::InvalidAddress(s.to_string())) } } } -impl From for Secret { +impl From for SignerKind { fn from(value: PrivateKey) -> Self { - Secret::SecretKey { + SignerKind::SecretKey { secret_key: value.to_string(), } } } -impl Secret { +impl SignerKind { pub fn private_key(&self, index: Option) -> Result { Ok(match self { - Secret::SecretKey { secret_key } => PrivateKey::from_string(secret_key)?, - Secret::SeedPhrase { seed_phrase } => PrivateKey::from_payload( + SignerKind::SecretKey { secret_key } => PrivateKey::from_string(secret_key)?, + SignerKind::SeedPhrase { seed_phrase } => PrivateKey::from_payload( &sep5::SeedPhrase::from_str(seed_phrase)? .from_path_index(index.unwrap_or_default(), None)? .private() .0, )?, + SignerKind::Ledger => panic!("Ledger does not reveal secret key"), }) } - pub fn public_key(&self, index: Option) -> Result { - let key = self.key_pair(index)?; - Ok(stellar_strkey::ed25519::PublicKey::from_payload( - key.verifying_key().as_bytes(), - )?) + pub async fn public_key(&self, index: Option) -> Result { + let key = self.signer(index, true)?; + Ok(key.get_public_key().await?) + } + + pub fn signer(&self, index: Option, prompt: bool) -> Result { + match self { + SignerKind::SecretKey { .. } | SignerKind::SeedPhrase { .. } => Ok(StellarSigner::Local( + LocalKey::new(self.key_pair(index)?, prompt), + )), + SignerKind::Ledger => { + let hd_path: u32 = index + .unwrap_or_default() + .try_into() + .expect("uszie bigger than u32"); + Ok(StellarSigner::Ledger(native(hd_path)?)) + } + } } pub fn key_pair(&self, index: Option) -> Result { @@ -132,7 +156,7 @@ impl Secret { }? .seed_phrase .into_phrase(); - Ok(Secret::SeedPhrase { seed_phrase }) + Ok(SignerKind::SeedPhrase { seed_phrase }) } pub fn test_seed_phrase() -> Result { @@ -140,6 +164,27 @@ impl Secret { } } +pub enum StellarSigner { + Local(LocalKey), + Ledger(Ledger), +} + +impl Stellar for StellarSigner { + async fn get_public_key(&self) -> Result { + match self { + StellarSigner::Local(signer) => signer.get_public_key().await, + StellarSigner::Ledger(signer) => signer.get_public_key().await, + } + } + + async fn sign_blob(&self, blob: &[u8]) -> Result, signer::Error> { + match self { + StellarSigner::Local(signer) => signer.sign_blob(blob).await, + StellarSigner::Ledger(signer) => signer.sign_blob(blob).await, + } + } +} + fn read_password() -> Result { std::io::stdout().flush().map_err(|_| Error::PasswordRead)?; rpassword::read_password().map_err(|_| Error::PasswordRead) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index e912d2b98..ab46a911d 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -124,7 +124,7 @@ impl NetworkRunnable for Cmd { return Ok(TxnResult::Txn(txn)); } let get_txn_resp = client - .send_transaction_polling(&self.config.sign_with_local_key(txn).await?) + .send_transaction_polling(&self.config.sign(txn).await?) .await? .try_into()?; if args.map_or(true, |a| !a.no_cache) { diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 7f51862cf..2c661670c 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -226,7 +226,7 @@ impl NetworkRunnable for Cmd { return Ok(TxnResult::Txn(txn)); } let get_txn_resp = client - .send_transaction_polling(&config.sign_with_local_key(txn).await?) + .send_transaction_polling(&config.sign(txn).await?) .await? .try_into()?; if global_args.map_or(true, |a| !a.no_cache) { diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index a6a0929eb..6be0c9910 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -177,7 +177,7 @@ impl NetworkRunnable for Cmd { .transaction() .clone(); let res = client - .send_transaction_polling(&config.sign_with_local_key(tx).await?) + .send_transaction_polling(&config.sign(tx).await?) .await?; if args.map_or(true, |a| !a.no_cache) { data::write(res.clone().try_into()?, &network.rpc_uri()?)?; diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index d7ebef16d..04d159e36 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -171,7 +171,7 @@ impl NetworkRunnable for Cmd { return Ok(TxnResult::Txn(txn)); } let txn_resp = client - .send_transaction_polling(&self.config.sign_with_local_key(txn).await?) + .send_transaction_polling(&self.config.sign(txn).await?) .await?; if args.map_or(true, |a| !a.no_cache) { data::write(txn_resp.clone().try_into().unwrap(), &network.rpc_uri()?)?; diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 537bcf9eb..4901e410c 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -7,7 +7,7 @@ use std::str::FromStr; use std::{fmt::Debug, fs, io}; use clap::{arg, command, value_parser, Parser}; -use ed25519_dalek::SigningKey; + use heck::ToKebabCase; use soroban_env_host::{ @@ -21,15 +21,18 @@ use soroban_env_host::{ HostError, }; +use soroban_sdk::xdr::ScSpecFunctionInputV0; use soroban_spec::read::FromWasmError; use super::super::{ config::{self, locator}, events, }; +use crate::commands::config::secret::StellarSigner; use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult}; use crate::commands::NetworkRunnable; use crate::get_spec::{self, get_remote_contract_spec}; +use crate::signer::{self, Stellar}; use crate::{ commands::{config::data, global, network}, rpc, Pwd, @@ -146,6 +149,8 @@ pub enum Error { Network(#[from] network::Error), #[error(transparent)] GetSpecError(#[from] get_spec::Error), + #[error(transparent)] + Signer(#[from] signer::Error), } impl From for Error { @@ -163,12 +168,12 @@ impl Cmd { std::env::var("SYSTEM_TEST_VERBOSE_OUTPUT").as_deref() == Ok("true") } - fn build_host_function_parameters( + async fn build_host_function_parameters( &self, contract_id: [u8; 32], spec_entries: &[ScSpecEntry], config: &config::Args, - ) -> Result<(String, Spec, InvokeContractArgs, Vec), Error> { + ) -> Result<(String, Spec, InvokeContractArgs, Vec), Error> { let spec = Spec(Some(spec_entries.to_vec())); let mut cmd = clap::Command::new(self.contract_id.clone()) .no_binary_name(true) @@ -188,59 +193,15 @@ impl Cmd { let func = spec.find_function(function)?; // create parsed_args in same order as the inputs to func - let mut signers: Vec = vec![]; - let parsed_args = func - .inputs - .iter() - .map(|i| { - let name = i.name.to_utf8_string()?; - if let Some(mut val) = matches_.get_raw(&name) { - let mut s = val.next().unwrap().to_string_lossy().to_string(); - if matches!(i.type_, ScSpecTypeDef::Address) { - let cmd = crate::commands::keys::address::Cmd { - name: s.clone(), - hd_path: Some(0), - locator: config.locator.clone(), - }; - if let Ok(address) = cmd.public_key() { - s = address.to_string(); - } - if let Ok(key) = cmd.private_key() { - signers.push(key); - } - } - spec.from_string(&s, &i.type_) - .map_err(|error| Error::CannotParseArg { arg: name, error }) - } else if matches!(i.type_, ScSpecTypeDef::Option(_)) { - Ok(ScVal::Void) - } else if let Some(arg_path) = - matches_.get_one::(&fmt_arg_file_name(&name)) - { - if matches!(i.type_, ScSpecTypeDef::Bytes | ScSpecTypeDef::BytesN(_)) { - Ok(ScVal::try_from( - &std::fs::read(arg_path) - .map_err(|_| Error::MissingFileArg(arg_path.clone()))?, - ) - .map_err(|()| Error::CannotParseArg { - arg: name.clone(), - error: soroban_spec_tools::Error::Unknown, - })?) - } else { - let file_contents = std::fs::read_to_string(arg_path) - .map_err(|_| Error::MissingFileArg(arg_path.clone()))?; - tracing::debug!( - "file {arg_path:?}, has contents:\n{file_contents}\nAnd type {:#?}\n{}", - i.type_, - file_contents.len() - ); - spec.from_string(&file_contents, &i.type_) - .map_err(|error| Error::CannotParseArg { arg: name, error }) - } - } else { - Err(Error::MissingArgument(name)) - } - }) - .collect::, Error>>()?; + let mut signers: Vec = vec![]; + let mut parsed_args: Vec = Vec::new(); + for i in func.inputs.iter() { + let (val, signer) = self.parse_arg(i, matches_, config, &spec).await?; + parsed_args.push(val); + if let Some(signer) = signer { + signers.push(signer); + } + } let contract_address_arg = ScAddress::Contract(Hash(contract_id)); let function_symbol_arg = function @@ -265,6 +226,58 @@ impl Cmd { Ok((function.clone(), spec, invoke_args, signers)) } + pub async fn parse_arg( + &self, + input: &ScSpecFunctionInputV0, + matches_: &clap::ArgMatches, + config: &config::Args, + spec: &Spec, + ) -> Result<(ScVal, Option), Error> { + let mut signer: Option = None; + let name = input.name.to_utf8_string()?; + let sc_val = if let Some(mut val) = matches_.get_raw(&name) { + let mut s = val.next().unwrap().to_string_lossy().to_string(); + if matches!(input.type_, ScSpecTypeDef::Address) { + if let Ok(signer_) = config + .locator + .read_identity(&s) + .and_then(|signer| Ok(signer.signer(config.hd_path, config.check)?)) + { + s = signer_.get_public_key().await?.to_string(); + signer = Some(signer_); + } + } + spec.from_string(&s, &input.type_) + .map_err(|error| Error::CannotParseArg { arg: name, error })? + } else if matches!(input.type_, ScSpecTypeDef::Option(_)) { + ScVal::Void + } else if let Some(arg_path) = matches_.get_one::(&fmt_arg_file_name(&name)) { + if matches!(input.type_, ScSpecTypeDef::Bytes | ScSpecTypeDef::BytesN(_)) { + ScVal::try_from( + &std::fs::read(arg_path) + .map_err(|_| Error::MissingFileArg(arg_path.clone()))?, + ) + .map_err(|()| Error::CannotParseArg { + arg: name.clone(), + error: soroban_spec_tools::Error::Unknown, + })? + } else { + let file_contents = std::fs::read_to_string(arg_path) + .map_err(|_| Error::MissingFileArg(arg_path.clone()))?; + tracing::debug!( + "file {arg_path:?}, has contents:\n{file_contents}\nAnd type {:#?}\n{}", + input.type_, + file_contents.len() + ); + spec.from_string(&file_contents, &input.type_) + .map_err(|error| Error::CannotParseArg { arg: name, error })? + } + } else { + return Err(Error::MissingArgument(name)); + }; + Ok((sc_val, signer)) + } + pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { let res = self.invoke(global_args).await?.to_envelope(); match res { @@ -317,7 +330,9 @@ impl NetworkRunnable for Cmd { let spec_entries = self.spec_entries()?; if let Some(spec_entries) = &spec_entries { // For testing wasm arg parsing - let _ = self.build_host_function_parameters(contract_id, spec_entries, config)?; + let _ = self + .build_host_function_parameters(contract_id, spec_entries, config) + .await?; } let client = rpc::Client::new(&network.rpc_url)?; let account_details = if self.is_view { @@ -326,12 +341,10 @@ impl NetworkRunnable for Cmd { client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; - let key = config.key_pair()?; + let key = config.public_key().await?; // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - client.get_account(&public_strkey).await? + client.get_account(&key.to_string()).await? }; let sequence: i64 = account_details.seq_num.into(); let AccountId(PublicKey::PublicKeyTypeEd25519(account_id)) = account_details.account_id; @@ -347,8 +360,9 @@ impl NetworkRunnable for Cmd { .map_err(Error::from)?; // Get the ledger footprint - let (function, spec, host_function_params, signers) = - self.build_host_function_parameters(contract_id, &spec_entries, config)?; + let (function, spec, host_function_params, signers) = self + .build_host_function_parameters(contract_id, &spec_entries, config) + .await?; let tx = build_invoke_contract_tx( host_function_params.clone(), sequence + 1, @@ -376,13 +390,14 @@ impl NetworkRunnable for Cmd { let mut txn = txn.transaction().clone(); // let auth = auth_entries(&txn); // crate::log::auth(&[auth]); - - if let Some(tx) = config.sign_soroban_authorizations(&txn, &signers).await? { - txn = tx; + for signer in &signers { + if let Some(tx) = config.sign_soroban_authorizations(signer, &txn).await? { + txn = tx; + } } // log_auth_cost_and_footprint(resources(&txn)); let res = client - .send_transaction_polling(&config.sign_with_local_key(txn).await?) + .send_transaction_polling(&config.sign(txn).await?) .await?; if !no_cache { data::write(res.clone().try_into()?, &network.rpc_uri()?)?; diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs index e38a45b31..6994deb89 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -174,7 +174,7 @@ impl NetworkRunnable for Cmd { return Ok(TxnResult::Txn(tx)); } let res = client - .send_transaction_polling(&config.sign_with_local_key(tx).await?) + .send_transaction_polling(&config.sign(tx).await?) .await?; if args.map_or(true, |a| !a.no_cache) { data::write(res.clone().try_into()?, &network.rpc_uri()?)?; diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs index 2868c7371..37f13dfcb 100644 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -28,6 +28,6 @@ impl Cmd { pub fn run(&self) -> Result<(), Error> { Ok(self .config_locator - .write_identity(&self.name, &self.secrets.read_secret()?)?) + .write_identity(&self.name.parse()?, &self.secrets.kind()?)?) } } diff --git a/cmd/soroban-cli/src/commands/keys/address.rs b/cmd/soroban-cli/src/commands/keys/address.rs index d13381b49..766e948eb 100644 --- a/cmd/soroban-cli/src/commands/keys/address.rs +++ b/cmd/soroban-cli/src/commands/keys/address.rs @@ -30,25 +30,16 @@ pub struct Cmd { } impl Cmd { - pub fn run(&self) -> Result<(), Error> { - println!("{}", self.public_key()?); + pub async fn run(&self) -> Result<(), Error> { + println!("{}", self.public_key().await?); Ok(()) } - pub fn private_key(&self) -> Result { + pub async fn public_key(&self) -> Result { Ok(self .locator - .read_identity(&self.name)? - .key_pair(self.hd_path)?) - } - - pub fn public_key(&self) -> Result { - if let Ok(key) = stellar_strkey::ed25519::PublicKey::from_string(&self.name) { - Ok(key) - } else { - Ok(stellar_strkey::ed25519::PublicKey::from_payload( - self.private_key()?.verifying_key().as_bytes(), - )?) - } + .account(&self.name)? + .public_key(self.hd_path) + .await?) } } diff --git a/cmd/soroban-cli/src/commands/keys/fund.rs b/cmd/soroban-cli/src/commands/keys/fund.rs index b6c088f13..a312064fc 100644 --- a/cmd/soroban-cli/src/commands/keys/fund.rs +++ b/cmd/soroban-cli/src/commands/keys/fund.rs @@ -24,7 +24,7 @@ pub struct Cmd { impl Cmd { pub async fn run(&self) -> Result<(), Error> { - let addr = self.address.public_key()?; + let addr = self.address.public_key().await?; self.network .get(&self.address.locator)? .fund_address(&addr) diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs index 159191dc3..6db46d730 100644 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ b/cmd/soroban-cli/src/commands/keys/generate.rs @@ -4,7 +4,7 @@ use crate::commands::network; use super::super::config::{ locator, - secret::{self, Secret}, + secret::{self, SignerKind}, }; #[derive(thiserror::Error, Debug)] @@ -53,18 +53,19 @@ pub struct Cmd { impl Cmd { pub async fn run(&self) -> Result<(), Error> { let seed_phrase = if self.default_seed { - Secret::test_seed_phrase() + SignerKind::test_seed_phrase() } else { - Secret::from_seed(self.seed.as_deref()) + SignerKind::from_seed(self.seed.as_deref()) }?; let secret = if self.as_secret { seed_phrase.private_key(self.hd_path)?.into() } else { seed_phrase }; - self.config_locator.write_identity(&self.name, &secret)?; + self.config_locator + .write_identity(&self.name.parse()?, &secret)?; if !self.no_fund { - let addr = secret.public_key(self.hd_path)?; + let addr = secret.public_key(self.hd_path).await?; let network = self.network.get(&self.config_locator)?; network .fund_address(&addr) diff --git a/cmd/soroban-cli/src/commands/keys/mod.rs b/cmd/soroban-cli/src/commands/keys/mod.rs index 42814092f..7ddb2c34d 100644 --- a/cmd/soroban-cli/src/commands/keys/mod.rs +++ b/cmd/soroban-cli/src/commands/keys/mod.rs @@ -51,7 +51,7 @@ impl Cmd { pub async fn run(&self) -> Result<(), Error> { match self { Cmd::Add(cmd) => cmd.run()?, - Cmd::Address(cmd) => cmd.run()?, + Cmd::Address(cmd) => cmd.run().await?, Cmd::Fund(cmd) => cmd.run().await?, Cmd::Generate(cmd) => cmd.run().await?, Cmd::Ls(cmd) => cmd.run()?, From d8f6b6f410f7aae14d5d2cc0477227e5bed7dfd0 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 25 Jun 2024 13:43:26 -0400 Subject: [PATCH 04/50] feat: use KeyName as a field to make clap parse it --- cmd/soroban-cli/src/commands/config/locator.rs | 11 ++++++----- cmd/soroban-cli/src/commands/contract/invoke.rs | 2 +- cmd/soroban-cli/src/commands/keys/add.rs | 6 ++++-- cmd/soroban-cli/src/commands/keys/address.rs | 7 +++---- cmd/soroban-cli/src/commands/keys/generate.rs | 6 +++--- cmd/soroban-cli/src/commands/keys/rm.rs | 4 ++-- cmd/soroban-cli/src/commands/keys/show.rs | 4 +++- 7 files changed, 22 insertions(+), 18 deletions(-) diff --git a/cmd/soroban-cli/src/commands/config/locator.rs b/cmd/soroban-cli/src/commands/config/locator.rs index f18bc3aca..33d0e3d47 100644 --- a/cmd/soroban-cli/src/commands/config/locator.rs +++ b/cmd/soroban-cli/src/commands/config/locator.rs @@ -102,6 +102,7 @@ impl Display for Location { } } +#[derive(Clone, Debug)] pub struct KeyName(String); impl std::ops::Deref for KeyName { @@ -223,15 +224,15 @@ impl Args { .collect::>()) } - pub fn read_identity(&self, name: &str) -> Result { + pub fn read_identity(&self, name: &KeyName) -> Result { KeyType::Identity.read_with_global(name, &self.local_config()?) } pub fn account(&self, account_str: &str) -> Result { - if let Ok(secret) = self.read_identity(account_str) { - Ok(secret) + if let Ok(signer) = account_str.parse::() { + Ok(signer) } else { - Ok(account_str.parse::()?) + self.read_identity(&account_str.parse()?) } } @@ -247,7 +248,7 @@ impl Args { res } - pub fn remove_identity(&self, name: &str) -> Result<(), Error> { + pub fn remove_identity(&self, name: &KeyName) -> Result<(), Error> { KeyType::Identity.remove(name, &self.config_dir()?) } diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 4901e410c..e1ee84386 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -240,7 +240,7 @@ impl Cmd { if matches!(input.type_, ScSpecTypeDef::Address) { if let Ok(signer_) = config .locator - .read_identity(&s) + .account(&s) .and_then(|signer| Ok(signer.signer(config.hd_path, config.check)?)) { s = signer_.get_public_key().await?.to_string(); diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs index 37f13dfcb..ab8de6cf6 100644 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -1,5 +1,7 @@ use clap::command; +use crate::commands::config::locator::KeyName; + use super::super::config::{locator, secret}; #[derive(thiserror::Error, Debug)] @@ -15,7 +17,7 @@ pub enum Error { #[group(skip)] pub struct Cmd { /// Name of identity - pub name: String, + pub name: KeyName, #[command(flatten)] pub secrets: secret::Args, @@ -28,6 +30,6 @@ impl Cmd { pub fn run(&self) -> Result<(), Error> { Ok(self .config_locator - .write_identity(&self.name.parse()?, &self.secrets.kind()?)?) + .write_identity(&self.name, &self.secrets.kind()?)?) } } diff --git a/cmd/soroban-cli/src/commands/keys/address.rs b/cmd/soroban-cli/src/commands/keys/address.rs index 766e948eb..2d4260dfc 100644 --- a/cmd/soroban-cli/src/commands/keys/address.rs +++ b/cmd/soroban-cli/src/commands/keys/address.rs @@ -1,8 +1,7 @@ -use crate::commands::config::secret; - -use super::super::config::locator; use clap::arg; +use super::super::config::{secret, locator::{self, KeyName}}; + #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] @@ -19,7 +18,7 @@ pub enum Error { #[group(skip)] pub struct Cmd { /// Name of identity to lookup, default test identity used if not provided - pub name: String, + pub name: KeyName, /// If identity is a seed phrase use this hd path, default is 0 #[arg(long)] diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs index 6db46d730..4b6b246e0 100644 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ b/cmd/soroban-cli/src/commands/keys/generate.rs @@ -1,6 +1,6 @@ use clap::{arg, command}; -use crate::commands::network; +use crate::commands::{config::locator::KeyName, network}; use super::super::config::{ locator, @@ -21,7 +21,7 @@ pub enum Error { #[group(skip)] pub struct Cmd { /// Name of identity - pub name: String, + pub name: KeyName, /// Do not fund address #[arg(long)] pub no_fund: bool, @@ -63,7 +63,7 @@ impl Cmd { seed_phrase }; self.config_locator - .write_identity(&self.name.parse()?, &secret)?; + .write_identity(&self.name, &secret)?; if !self.no_fund { let addr = secret.public_key(self.hd_path).await?; let network = self.network.get(&self.config_locator)?; diff --git a/cmd/soroban-cli/src/commands/keys/rm.rs b/cmd/soroban-cli/src/commands/keys/rm.rs index df48108d3..9d9c1be51 100644 --- a/cmd/soroban-cli/src/commands/keys/rm.rs +++ b/cmd/soroban-cli/src/commands/keys/rm.rs @@ -1,6 +1,6 @@ use clap::command; -use super::super::config::locator; +use super::super::config::locator::{self, KeyName}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -12,7 +12,7 @@ pub enum Error { #[group(skip)] pub struct Cmd { /// Identity to remove - pub name: String, + pub name: KeyName, #[command(flatten)] pub config: locator::Args, diff --git a/cmd/soroban-cli/src/commands/keys/show.rs b/cmd/soroban-cli/src/commands/keys/show.rs index b99478cbc..493d02a2f 100644 --- a/cmd/soroban-cli/src/commands/keys/show.rs +++ b/cmd/soroban-cli/src/commands/keys/show.rs @@ -1,5 +1,7 @@ use clap::arg; +use crate::commands::config::locator::KeyName; + use super::super::config::{locator, secret}; #[derive(thiserror::Error, Debug)] @@ -18,7 +20,7 @@ pub enum Error { #[group(skip)] pub struct Cmd { /// Name of identity to lookup, default is test identity - pub name: String, + pub name: KeyName, /// If identity is a seed phrase use this hd path, default is 0 #[arg(long)] From b8c61578d25aec90d924a2f2553206ee95ae761a Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 25 Jun 2024 13:43:40 -0400 Subject: [PATCH 05/50] fix: fmt --- cmd/soroban-cli/src/commands/config/secret.rs | 6 +++--- cmd/soroban-cli/src/commands/keys/address.rs | 5 ++++- cmd/soroban-cli/src/commands/keys/generate.rs | 3 +-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cmd/soroban-cli/src/commands/config/secret.rs b/cmd/soroban-cli/src/commands/config/secret.rs index b28c1338d..36a49608f 100644 --- a/cmd/soroban-cli/src/commands/config/secret.rs +++ b/cmd/soroban-cli/src/commands/config/secret.rs @@ -131,9 +131,9 @@ impl SignerKind { pub fn signer(&self, index: Option, prompt: bool) -> Result { match self { - SignerKind::SecretKey { .. } | SignerKind::SeedPhrase { .. } => Ok(StellarSigner::Local( - LocalKey::new(self.key_pair(index)?, prompt), - )), + SignerKind::SecretKey { .. } | SignerKind::SeedPhrase { .. } => Ok( + StellarSigner::Local(LocalKey::new(self.key_pair(index)?, prompt)), + ), SignerKind::Ledger => { let hd_path: u32 = index .unwrap_or_default() diff --git a/cmd/soroban-cli/src/commands/keys/address.rs b/cmd/soroban-cli/src/commands/keys/address.rs index 2d4260dfc..3a12fc35d 100644 --- a/cmd/soroban-cli/src/commands/keys/address.rs +++ b/cmd/soroban-cli/src/commands/keys/address.rs @@ -1,6 +1,9 @@ use clap::arg; -use super::super::config::{secret, locator::{self, KeyName}}; +use super::super::config::{ + locator::{self, KeyName}, + secret, +}; #[derive(thiserror::Error, Debug)] pub enum Error { diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs index 4b6b246e0..28b01882e 100644 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ b/cmd/soroban-cli/src/commands/keys/generate.rs @@ -62,8 +62,7 @@ impl Cmd { } else { seed_phrase }; - self.config_locator - .write_identity(&self.name, &secret)?; + self.config_locator.write_identity(&self.name, &secret)?; if !self.no_fund { let addr = secret.public_key(self.hd_path).await?; let network = self.network.get(&self.config_locator)?; From b82468ee6ef54379ebca3d23489c444fdfe6b64c Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 25 Jun 2024 17:04:58 -0400 Subject: [PATCH 06/50] fix: tests and docs --- FULL_HELP_DOCS.md | 6 +++--- .../soroban-test/tests/it/integration/hello_world.rs | 8 ++++---- cmd/crates/soroban-test/tests/it/integration/tx.rs | 4 +--- cmd/crates/soroban-test/tests/it/main.rs | 2 +- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index ad1aa3435..b9634ce5a 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -1494,7 +1494,7 @@ Simulate a transaction envelope from stdin * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` +* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config @@ -1519,7 +1519,7 @@ Sign a transaction with a ledger or local key * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` +* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config @@ -1544,7 +1544,7 @@ Send a transaction envelope to the network * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` +* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config 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 fc0c56bd8..795efb322 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -73,8 +73,8 @@ async fn invoke() { .stdout_as_str(); let dir = sandbox.dir(); let seed_phrase = std::fs::read_to_string(dir.join(".soroban/identity/test.toml")).unwrap(); - let s = toml::from_str::(&seed_phrase).unwrap(); - let secret::Signer::SeedPhrase { seed_phrase } = s else { + let s = toml::from_str::(&seed_phrase).unwrap(); + let secret::SignerKind::SeedPhrase { seed_phrase } = s else { panic!("Expected seed phrase") }; let id = &deploy_hello(sandbox).await; @@ -105,8 +105,8 @@ async fn invoke() { }; config_locator .write_identity( - "testone", - &secret::Signer::SecretKey { + &"testone".parse().unwrap(), + &secret::SignerKind::SecretKey { secret_key: secret_key_1.clone(), }, ) diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 450c459b4..9c0800bc3 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -1,6 +1,4 @@ -use soroban_sdk::xdr::{ - Limits, ReadXdr, TransactionEnvelope, TransactionV1Envelope, VecM, WriteXdr, -}; +use soroban_sdk::xdr::{Limits, ReadXdr, TransactionEnvelope, WriteXdr}; use soroban_test::{AssertExt, TestEnv}; use crate::integration::util::{deploy_contract, DeployKind, HELLO_WORLD}; diff --git a/cmd/crates/soroban-test/tests/it/main.rs b/cmd/crates/soroban-test/tests/it/main.rs index 10aea449c..2d9218f32 100644 --- a/cmd/crates/soroban-test/tests/it/main.rs +++ b/cmd/crates/soroban-test/tests/it/main.rs @@ -1,7 +1,7 @@ mod arg_parsing; mod config; mod help; -#[cfg(feature = "it")] +// #[cfg(feature = "it")] mod integration; mod plugin; mod util; From b711e8a123a80972ce47f32e38aa51fd111b2b35 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Wed, 26 Jun 2024 13:13:43 -0400 Subject: [PATCH 07/50] feat: remove stellar-ledger --- Cargo.lock | 1 - cmd/crates/stellar-ledger/src/hd_path.rs | 21 +++--- cmd/crates/stellar-ledger/src/lib.rs | 69 ++++++++----------- cmd/soroban-cli/Cargo.toml | 1 - cmd/soroban-cli/src/commands/config/secret.rs | 16 +---- cmd/soroban-cli/src/signer.rs | 29 -------- 6 files changed, 42 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 59cace36e..c66829f6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4735,7 +4735,6 @@ dependencies = [ "soroban-spec-rust", "soroban-spec-tools", "soroban-spec-typescript", - "stellar-ledger", "stellar-rpc-client", "stellar-strkey", "stellar-xdr", diff --git a/cmd/crates/stellar-ledger/src/hd_path.rs b/cmd/crates/stellar-ledger/src/hd_path.rs index 4a207d644..07ed133f1 100644 --- a/cmd/crates/stellar-ledger/src/hd_path.rs +++ b/cmd/crates/stellar-ledger/src/hd_path.rs @@ -1,8 +1,9 @@ +use crate::Error; + #[derive(Clone, Copy)] pub struct HdPath(pub u32); impl HdPath { - #[must_use] pub fn depth(&self) -> u8 { let path: slip10::BIP32Path = self.into(); path.depth() @@ -22,8 +23,7 @@ impl From<&u32> for HdPath { } impl HdPath { - #[must_use] - pub fn to_vec(&self) -> Vec { + pub fn to_vec(&self) -> Result, Error> { hd_path_to_bytes(&self.into()) } } @@ -35,11 +35,16 @@ impl From<&HdPath> for slip10::BIP32Path { } } -fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { +fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Result, Error> { let hd_path_indices = 0..hd_path.depth(); - // Unsafe unwrap is safe because the depth is the length of interneal vector - hd_path_indices + let result = hd_path_indices .into_iter() - .flat_map(|index| unsafe { hd_path.index(index).unwrap_unchecked().to_be_bytes() }) - .collect() + .map(|index| { + Ok(hd_path + .index(index) + .ok_or_else(|| Error::Bip32PathError(format!("{hd_path}")))? + .to_be_bytes()) + }) + .collect::, Error>>()?; + Ok(result.into_iter().flatten().collect()) } diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index 69789b735..c74365af2 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -1,9 +1,8 @@ use hd_path::HdPath; -use ledger_transport::APDUCommand; -pub use ledger_transport_hid::TransportNativeHID; +use ledger_transport::{APDUCommand, Exchange}; use ledger_transport_hid::{ hidapi::{HidApi, HidError}, - LedgerHIDError, + LedgerHIDError, TransportNativeHID, }; use soroban_env_host::xdr::{Hash, Transaction}; @@ -16,8 +15,6 @@ use stellar_xdr::curr::{ pub use crate::signer::Blob; pub mod hd_path; -pub use ledger_transport::Exchange; - mod signer; // this is from https://github.com/LedgerHQ/ledger-live/blob/36cfbf3fa3300fd99bcee2ab72e1fd8f280e6280/libs/ledgerjs/packages/hw-app-str/src/Str.ts#L181 @@ -83,11 +80,6 @@ pub struct LedgerSigner { unsafe impl Send for LedgerSigner where T: Exchange {} unsafe impl Sync for LedgerSigner where T: Exchange {} -/// Returns a new `LedgerSigner` with a native HID transport, e.i. the transport is connected to the Ledger device -/// -/// # Errors -/// -/// Returns an error if there is an issue with connecting with the device pub fn native() -> Result, Error> { Ok(LedgerSigner { transport: get_transport()?, @@ -101,7 +93,11 @@ where pub fn new(transport: T) -> Self { Self { transport } } - + pub fn native() -> Result, Error> { + Ok(LedgerSigner { + transport: get_transport()?, + }) + } /// Get the device app's configuration /// # Errors /// Returns an error if there is an issue with connecting with the device or getting the config from the device @@ -145,7 +141,7 @@ where }; let mut signature_payload_as_bytes = signature_payload.to_xdr(Limits::none())?; - let mut hd_path_to_bytes = hd_path.into().to_vec(); + let mut hd_path_to_bytes = hd_path.into().to_vec()?; let capacity = 1 + hd_path_to_bytes.len() + signature_payload_as_bytes.len(); let mut data: Vec = Vec::with_capacity(capacity); @@ -186,9 +182,7 @@ where } /// The `display_and_confirm` bool determines if the Ledger will display the public key on its screen and requires user approval to share - /// # Errors - /// Returns an error if there is an issue with connecting with the device or getting the public key from the device - pub async fn get_public_key_with_display_flag( + async fn get_public_key_with_display_flag( &self, hd_path: impl Into, display_and_confirm: bool, @@ -197,7 +191,7 @@ where // the first element of the data should be the number of elements in the path let hd_path = hd_path.into(); let hd_path_elements_count = hd_path.depth(); - let mut hd_path_to_bytes = hd_path.to_vec(); + let mut hd_path_to_bytes = hd_path.to_vec()?; hd_path_to_bytes.insert(0, hd_path_elements_count); let p2 = if display_and_confirm { @@ -247,30 +241,6 @@ where )), } } - - /// Sign a blob of data with the account on the Ledger device - /// # Errors - /// Returns an error if there is an issue with connecting with the device or signing the given tx on the device - pub async fn sign_data(&self, index: &HdPath, blob: &[u8]) -> Result, Error> { - let mut hd_path_to_bytes = index.to_vec(); - - let capacity = 1 + hd_path_to_bytes.len() + blob.len(); - let mut data: Vec = Vec::with_capacity(capacity); - - data.insert(0, HD_PATH_ELEMENTS_COUNT); - data.append(&mut hd_path_to_bytes); - data.extend_from_slice(blob); - - let command = APDUCommand { - cla: CLA, - ins: SIGN_TX_HASH, - p1: P1_SIGN_TX_HASH, - p2: P2_SIGN_TX_HASH, - data, - }; - - self.send_command_to_ledger(command).await - } } #[async_trait::async_trait] @@ -295,7 +265,24 @@ where /// # Errors /// Returns an error if there is an issue with connecting with the device or signing the given tx on the device. Or, if the device has not enabled hash signing async fn sign_blob(&self, index: &Self::Key, blob: &[u8]) -> Result, Error> { - self.sign_data(index, blob).await + let mut hd_path_to_bytes = index.to_vec()?; + + let capacity = 1 + hd_path_to_bytes.len() + blob.len(); + let mut data: Vec = Vec::with_capacity(capacity); + + data.insert(0, HD_PATH_ELEMENTS_COUNT); + data.append(&mut hd_path_to_bytes); + data.extend_from_slice(blob); + + let command = APDUCommand { + cla: CLA, + ins: SIGN_TX_HASH, + p1: P1_SIGN_TX_HASH, + p2: P2_SIGN_TX_HASH, + data, + }; + + self.send_command_to_ledger(command).await } } diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index d8eb044de..aa293d3fd 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -50,7 +50,6 @@ soroban-ledger-snapshot = { workspace = true } stellar-strkey = { workspace = true } soroban-sdk = { workspace = true } soroban-rpc = { workspace = true } -stellar-ledger = { workspace = true } clap = { workspace = true, features = [ "derive", diff --git a/cmd/soroban-cli/src/commands/config/secret.rs b/cmd/soroban-cli/src/commands/config/secret.rs index 36a49608f..dfc4784ce 100644 --- a/cmd/soroban-cli/src/commands/config/secret.rs +++ b/cmd/soroban-cli/src/commands/config/secret.rs @@ -4,7 +4,7 @@ use std::{io::Write, str::FromStr}; use stellar_strkey::ed25519::{PrivateKey, PublicKey}; use crate::{ - signer::{self, native, Ledger, LocalKey, Stellar}, + signer::{self, LocalKey, Stellar}, utils, }; @@ -79,7 +79,6 @@ impl Args { pub enum SignerKind { SecretKey { secret_key: String }, SeedPhrase { seed_phrase: String }, - Ledger, } impl FromStr for SignerKind { @@ -94,8 +93,6 @@ impl FromStr for SignerKind { Ok(SignerKind::SeedPhrase { seed_phrase: s.to_string(), }) - } else if s == "ledger" { - Ok(SignerKind::Ledger) } else { Err(Error::InvalidAddress(s.to_string())) } @@ -120,7 +117,6 @@ impl SignerKind { .private() .0, )?, - SignerKind::Ledger => panic!("Ledger does not reveal secret key"), }) } @@ -134,13 +130,6 @@ impl SignerKind { SignerKind::SecretKey { .. } | SignerKind::SeedPhrase { .. } => Ok( StellarSigner::Local(LocalKey::new(self.key_pair(index)?, prompt)), ), - SignerKind::Ledger => { - let hd_path: u32 = index - .unwrap_or_default() - .try_into() - .expect("uszie bigger than u32"); - Ok(StellarSigner::Ledger(native(hd_path)?)) - } } } @@ -166,21 +155,18 @@ impl SignerKind { pub enum StellarSigner { Local(LocalKey), - Ledger(Ledger), } impl Stellar for StellarSigner { async fn get_public_key(&self) -> Result { match self { StellarSigner::Local(signer) => signer.get_public_key().await, - StellarSigner::Ledger(signer) => signer.get_public_key().await, } } async fn sign_blob(&self, blob: &[u8]) -> Result, signer::Error> { match self { StellarSigner::Local(signer) => signer.sign_blob(blob).await, - StellarSigner::Ledger(signer) => signer.sign_blob(blob).await, } } } diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index af865b110..acc92ca77 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -10,7 +10,6 @@ use crate::xdr::{ TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, Uint256, WriteXdr, }; -use stellar_ledger::{Exchange, LedgerSigner}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -21,8 +20,6 @@ pub enum Error { #[error("Missing signing key for account {address}")] MissingSignerForAddress { address: String }, #[error(transparent)] - Ledger(#[from] stellar_ledger::Error), - #[error(transparent)] Xdr(#[from] xdr::Error), #[error("User cancelled signing, perhaps need to remove --check")] UserCancelledSigning, @@ -248,32 +245,6 @@ impl Stellar for LocalKey { } } -pub struct Ledger { - index: u32, - signer: LedgerSigner, -} - -pub fn native(index: u32) -> Result, Error> { - let signer = stellar_ledger::native()?; - Ok(Ledger { index, signer }) -} - -impl Stellar for Ledger -where - T: Exchange, -{ - async fn get_public_key(&self) -> Result { - Ok(self - .signer - .get_public_key_with_display_flag(self.index, false) - .await?) - } - - async fn sign_blob(&self, blob: &[u8]) -> Result, Error> { - Ok(self.signer.sign_data(&self.index.into(), blob).await?) - } -} - pub fn read_key() -> char { let tty = get_tty().unwrap(); if let Some(key) = tty.keys().next() { From 0ba2de93e8b97d0de807469049649cf03785bbfc Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Wed, 26 Jun 2024 14:48:45 -0400 Subject: [PATCH 08/50] fix: switch to crossterm for cross platform compatibility --- Cargo.lock | 86 ++++++++++++++++++++--------------- cmd/soroban-cli/Cargo.toml | 4 +- cmd/soroban-cli/src/signer.rs | 16 +++---- 3 files changed, 59 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c66829f6c..983104b55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -858,6 +858,31 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.4.2", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -3193,17 +3218,6 @@ dependencies = [ "redox_syscall", ] -[[package]] -name = "libredox" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" -dependencies = [ - "bitflags 2.4.2", - "libc", - "redox_syscall", -] - [[package]] name = "link-cplusplus" version = "1.0.9" @@ -3301,6 +3315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.48.0", ] @@ -3417,12 +3432,6 @@ dependencies = [ "libc", ] -[[package]] -name = "numtoa" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" - [[package]] name = "object" version = "0.32.2" @@ -3898,12 +3907,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "redox_termios" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" - [[package]] name = "redox_users" version = "0.4.4" @@ -3911,7 +3914,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", - "libredox 0.0.1", + "libredox", "thiserror", ] @@ -4564,6 +4567,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -4696,6 +4720,7 @@ dependencies = [ "clap-markdown", "clap_complete", "crate-git-revision 0.0.4", + "crossterm", "csv", "directories", "dirs", @@ -4744,7 +4769,6 @@ dependencies = [ "tempfile", "termcolor", "termcolor_output", - "termion", "thiserror", "tokio", "toml 0.5.11", @@ -5321,18 +5345,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f34dde0bb841eb3762b42bdff8db11bbdbc0a3bd7b32012955f5ce1d081f86c1" -[[package]] -name = "termion" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ccce68e518d1173e80876edd54760b60b792750d0cab6444a79101c6ea03848" -dependencies = [ - "libc", - "libredox 0.0.2", - "numtoa", - "redox_termios", -] - [[package]] name = "termtree" version = "0.4.1" diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index aa293d3fd..05c385740 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -112,10 +112,10 @@ ureq = { version = "2.9.1", features = ["json"] } tempfile = "3.8.1" toml_edit = "0.21.0" rust-embed = { version = "8.2.0", features = ["debug-embed"] } -bollard = { workspace=true } +bollard = { workspace = true } futures-util = "0.3.30" home = "0.5.9" -termion = "4.0.0" +crossterm = "0.27.0" # For hyper-tls [target.'cfg(unix)'.dependencies] openssl = { version = "=0.10.55", features = ["vendored"] } diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index acc92ca77..fcc23fe29 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -1,6 +1,6 @@ +use crossterm::event::{read, Event, KeyCode}; use ed25519_dalek::ed25519::signature::Signer; use sha2::{Digest, Sha256}; -use termion::{event::Key, get_tty, input::TermRead}; use crate::xdr::{ self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, @@ -246,13 +246,13 @@ impl Stellar for LocalKey { } pub fn read_key() -> char { - let tty = get_tty().unwrap(); - if let Some(key) = tty.keys().next() { - match key.unwrap() { - Key::Char(c) => c, - _ => '_', + loop { + if let Event::Key(key) = read().unwrap() { + match key.code { + KeyCode::Char(c) => return c, + KeyCode::Esc => return '\x1b', // escape key + _ => (), + } } - } else { - ' ' } } From c9c8e0225de1473b51f838d875ebd3c386393ce6 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Wed, 26 Jun 2024 15:10:17 -0400 Subject: [PATCH 09/50] fix: use async_trait to allow older versions of rust --- cmd/soroban-cli/src/commands/config/mod.rs | 4 ++-- cmd/soroban-cli/src/commands/config/secret.rs | 1 + cmd/soroban-cli/src/signer.rs | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/soroban-cli/src/commands/config/mod.rs b/cmd/soroban-cli/src/commands/config/mod.rs index 5c54a5c46..858eaa16f 100644 --- a/cmd/soroban-cli/src/commands/config/mod.rs +++ b/cmd/soroban-cli/src/commands/config/mod.rs @@ -80,7 +80,7 @@ impl Args { pub async fn sign_with_signer( &self, - signer: &impl Stellar, + signer: &(impl Stellar + std::marker::Sync), tx: Transaction, ) -> Result { let Network { @@ -91,7 +91,7 @@ impl Args { pub async fn sign_soroban_authorizations( &self, - signer: &impl Stellar, + signer: &(impl Stellar + std::marker::Sync), tx: &Transaction, ) -> Result, Error> { let network = self.get_network()?; diff --git a/cmd/soroban-cli/src/commands/config/secret.rs b/cmd/soroban-cli/src/commands/config/secret.rs index dfc4784ce..a415e631c 100644 --- a/cmd/soroban-cli/src/commands/config/secret.rs +++ b/cmd/soroban-cli/src/commands/config/secret.rs @@ -157,6 +157,7 @@ pub enum StellarSigner { Local(LocalKey), } +#[async_trait::async_trait] impl Stellar for StellarSigner { async fn get_public_key(&self) -> Result { match self { diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index fcc23fe29..9cef41ce3 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -41,7 +41,7 @@ fn requires_auth(txn: &Transaction) -> Option { } /// A trait for signing Stellar transactions and Soroban authorization entries -#[allow(async_fn_in_trait)] +#[async_trait::async_trait] pub trait Stellar { async fn get_public_key(&self) -> Result; @@ -223,6 +223,7 @@ impl LocalKey { } } +#[async_trait::async_trait] impl Stellar for LocalKey { async fn sign_blob(&self, data: &[u8]) -> Result, Error> { if self.prompt { From 46f7272e469a432c9b657d8163c2043f38aa0858 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Wed, 26 Jun 2024 16:04:15 -0400 Subject: [PATCH 10/50] fix: tx test clippy error --- cmd/crates/soroban-test/tests/it/integration/tx.rs | 4 ++-- cmd/crates/soroban-test/tests/it/main.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 9c0800bc3..a28057b56 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -44,7 +44,7 @@ async fn txn_send() { let xdr_base64 = deploy_contract(sandbox, HELLO_WORLD, DeployKind::SimOnly).await; println!("{xdr_base64}"); let tx_env = TransactionEnvelope::from_xdr_base64(&xdr_base64, Limits::none()).unwrap(); - let tx_env = sign(sandbox, tx_env); + let tx_env = sign(sandbox, &tx_env); println!( "Transaction to send:\n{}", @@ -62,7 +62,7 @@ async fn txn_send() { println!("Transaction sent: {assembled_str}"); } -fn sign(sandbox: &TestEnv, tx_env: TransactionEnvelope) -> TransactionEnvelope { +fn sign(sandbox: &TestEnv, tx_env: &TransactionEnvelope) -> TransactionEnvelope { TransactionEnvelope::from_xdr_base64( sandbox .new_assert_cmd("tx") diff --git a/cmd/crates/soroban-test/tests/it/main.rs b/cmd/crates/soroban-test/tests/it/main.rs index 2d9218f32..10aea449c 100644 --- a/cmd/crates/soroban-test/tests/it/main.rs +++ b/cmd/crates/soroban-test/tests/it/main.rs @@ -1,7 +1,7 @@ mod arg_parsing; mod config; mod help; -// #[cfg(feature = "it")] +#[cfg(feature = "it")] mod integration; mod plugin; mod util; From ab62ff0f10bfb4b84591924465424afec271f2bf Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 27 Jun 2024 08:44:04 -0400 Subject: [PATCH 11/50] fix: remove ledger tests --- cmd/crates/soroban-test/tests/it/config.rs | 30 ---------------------- 1 file changed, 30 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/config.rs b/cmd/crates/soroban-test/tests/it/config.rs index 598f65ec0..dd33713aa 100644 --- a/cmd/crates/soroban-test/tests/it/config.rs +++ b/cmd/crates/soroban-test/tests/it/config.rs @@ -153,36 +153,6 @@ fn read_key() { .stdout(predicates::str::contains("test_id\n")); } -#[test] -fn cannot_generate_ledger_key() { - let sandbox = TestEnv::default(); - sandbox - .new_assert_cmd("keys") - .arg("generate") - .arg("ledger") - .assert() - .stdout("") - .stderr("error: Cannot name a Key ledger\n") - .failure(); -} - -#[test] -fn cannot_add_ledger_key() { - let sandbox = TestEnv::default(); - sandbox - .new_assert_cmd("keys") - .env( - "SOROBAN_SECRET_KEY", - "SDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCQYFD", - ) - .arg("add") - .arg("ledger") - .assert() - .stdout("") - .stderr("error: Cannot name a Key ledger\n") - .failure(); -} - #[test] fn generate_key() { let sandbox = TestEnv::default(); From 934b36bc296940622269dc533e0918ac6892ef0d Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 27 Jun 2024 12:41:34 -0400 Subject: [PATCH 12/50] fix: don't mention ledger --- FULL_HELP_DOCS.md | 4 ++-- cmd/soroban-cli/src/commands/tx/mod.rs | 2 +- cmd/soroban-cli/src/commands/tx/sign.rs | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index b9634ce5a..12d50317a 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -1478,7 +1478,7 @@ Sign, Simulate, and Send transactions ###### **Subcommands:** * `simulate` — Simulate a transaction envelope from stdin -* `sign` — Sign a transaction with a ledger or local key +* `sign` — Sign a transaction * `send` — Send a transaction envelope to the network @@ -1510,7 +1510,7 @@ Simulate a transaction envelope from stdin ## `stellar tx sign` -Sign a transaction with a ledger or local key +Sign a transaction **Usage:** `stellar tx sign [OPTIONS] --source-account ` diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index cedfaa322..8de6c8c19 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -11,7 +11,7 @@ pub mod xdr; pub enum Cmd { /// Simulate a transaction envelope from stdin Simulate(simulate::Cmd), - /// Sign a transaction with a ledger or local key + /// Sign a transaction Sign(sign::Cmd), /// Send a transaction envelope to the network Send(send::Cmd), diff --git a/cmd/soroban-cli/src/commands/tx/sign.rs b/cmd/soroban-cli/src/commands/tx/sign.rs index d4a201244..99ce78121 100644 --- a/cmd/soroban-cli/src/commands/tx/sign.rs +++ b/cmd/soroban-cli/src/commands/tx/sign.rs @@ -1,11 +1,13 @@ use crate::xdr::{self, Limits, Transaction, TransactionEnvelope, WriteXdr}; +use super::super::config; + #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] XdrArgs(#[from] super::xdr::Error), #[error(transparent)] - Config(#[from] super::super::config::Error), + Config(#[from] config::Error), #[error(transparent)] Xdr(#[from] xdr::Error), } @@ -14,7 +16,7 @@ pub enum Error { #[group(skip)] pub struct Cmd { #[clap(flatten)] - pub config: super::super::config::Args, + pub config: config::Args, } impl Cmd { From c678b7ad93a6eaa1e20c05a942c26fa8025ca206 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 27 Jun 2024 12:43:00 -0400 Subject: [PATCH 13/50] fix: no longer assume key used is source key with signing auth entries Before we treated the source key special from the collection of keys. Now each key can sign an auth entry in isolation --- cmd/soroban-cli/src/signer.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index 9cef41ce3..3c43bbbeb 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -43,8 +43,10 @@ fn requires_auth(txn: &Transaction) -> Option { /// A trait for signing Stellar transactions and Soroban authorization entries #[async_trait::async_trait] pub trait Stellar { + /// Currently only supports ed25519 keys async fn get_public_key(&self) -> Result; + /// Sign an abritatry byte array async fn sign_blob(&self, blob: &[u8]) -> Result, Error>; /// Sign a transaction hash with the given source account @@ -118,6 +120,8 @@ pub trait Stellar { /// Sign a Stellar transaction with the given source account /// This is a default implementation that signs the transaction hash and returns a decorated signature + /// + /// Todo: support signing the transaction directly. /// # Errors /// Returns an error if the source account is not found async fn sign_txn( @@ -178,7 +182,7 @@ pub trait Stellar { network_passphrase: &str, ) -> Result { if let SorobanAuthorizationEntry { - credentials: SorobanCredentials::Address(SorobanAddressCredentials { ref address, .. }), + credentials: SorobanCredentials::Address(SorobanAddressCredentials { address, .. }), .. } = unsigned_entry { @@ -198,13 +202,12 @@ pub trait Stellar { } }; if needle == self.get_public_key().await? { - return Ok(unsigned_entry.clone()); + return self + .sign_soroban_authorization_entry(unsigned_entry, network_passphrase) + .await; } - self.sign_soroban_authorization_entry(unsigned_entry, network_passphrase) - .await - } else { - Ok(unsigned_entry.clone()) } + Ok(unsigned_entry.clone()) } } From d6f427efbfa2cc19dfe67be064ee769ed46e8d8a Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 2 Jul 2024 16:03:51 -0400 Subject: [PATCH 14/50] fix: add Network to Stellar to allow for getting latest ledger Auth entries need an expiration ledger and so we need to get the current one and add 60 to then set it before signing. --- cmd/soroban-cli/src/commands/config/mod.rs | 15 +- .../src/commands/contract/invoke.rs | 2 +- cmd/soroban-cli/src/commands/tx/sign.rs | 13 +- cmd/soroban-cli/src/signer.rs | 151 ++++++++++-------- 4 files changed, 105 insertions(+), 76 deletions(-) diff --git a/cmd/soroban-cli/src/commands/config/mod.rs b/cmd/soroban-cli/src/commands/config/mod.rs index 858eaa16f..8c80fd7dc 100644 --- a/cmd/soroban-cli/src/commands/config/mod.rs +++ b/cmd/soroban-cli/src/commands/config/mod.rs @@ -83,20 +83,25 @@ impl Args { signer: &(impl Stellar + std::marker::Sync), tx: Transaction, ) -> Result { - let Network { - network_passphrase, .. - } = &self.get_network()?; - Ok(signer.sign_txn(tx, network_passphrase).await?) + let network = self.get_network()?; + Ok(signer.sign_txn(tx, &network).await?) } pub async fn sign_soroban_authorizations( + &self, + tx: &Transaction, + ) -> Result, Error> { + self.sign_soroban_authorizations_with_signer(&self.signer()?, tx) + .await + } + pub async fn sign_soroban_authorizations_with_signer( &self, signer: &(impl Stellar + std::marker::Sync), tx: &Transaction, ) -> Result, Error> { let network = self.get_network()?; Ok(signer - .sign_soroban_authorizations(tx, &network.network_passphrase) + .sign_soroban_authorizations(tx, &network) .await?) } diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 46c50fa45..c82dec7f6 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -392,7 +392,7 @@ impl NetworkRunnable for Cmd { // let auth = auth_entries(&txn); // crate::log::auth(&[auth]); for signer in &signers { - if let Some(tx) = config.sign_soroban_authorizations(signer, &txn).await? { + if let Some(tx) = config.sign_soroban_authorizations_with_signer(signer, &txn).await? { txn = tx; } } diff --git a/cmd/soroban-cli/src/commands/tx/sign.rs b/cmd/soroban-cli/src/commands/tx/sign.rs index 99ce78121..8714470ac 100644 --- a/cmd/soroban-cli/src/commands/tx/sign.rs +++ b/cmd/soroban-cli/src/commands/tx/sign.rs @@ -17,6 +17,8 @@ pub enum Error { pub struct Cmd { #[clap(flatten)] pub config: config::Args, + #[arg(long, visible_alias = "auth", short = 'a')] + pub auth_only: bool, } impl Cmd { @@ -29,7 +31,16 @@ impl Cmd { } pub async fn sign(&self, tx: Transaction) -> Result { - Ok(self.config.sign(tx).await?) + if self.auth_only { + Ok(self + .config + .sign_soroban_authorizations(&tx) + .await? + .unwrap_or(tx) + .into()) + } else { + Ok(self.config.sign(tx).await?) + } } pub async fn sign_env( diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index 3c43bbbeb..f78ac707c 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -2,13 +2,16 @@ use crossterm::event::{read, Event, KeyCode}; use ed25519_dalek::ed25519::signature::Signer; use sha2::{Digest, Sha256}; -use crate::xdr::{ - self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, - InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, - ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, - SorobanAuthorizedFunction, SorobanCredentials, Transaction, TransactionEnvelope, - TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, - TransactionV1Envelope, Uint256, WriteXdr, +use crate::{ + commands::network::Network, + xdr::{ + self, AccountId, DecoratedSignature, Hash, HashIdPreimage, + HashIdPreimageSorobanAuthorization, InvokeHostFunctionOp, Limits, Operation, OperationBody, + PublicKey, ScAddress, ScMap, ScSymbol, ScVal, Signature, SignatureHint, + SorobanAddressCredentials, SorobanAuthorizationEntry, SorobanAuthorizedFunction, + SorobanCredentials, Transaction, TransactionEnvelope, TransactionSignaturePayload, + TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, Uint256, WriteXdr, + }, }; #[derive(thiserror::Error, Debug)] @@ -21,6 +24,8 @@ pub enum Error { MissingSignerForAddress { address: String }, #[error(transparent)] Xdr(#[from] xdr::Error), + #[error(transparent)] + Rpc(#[from] crate::rpc::Error), #[error("User cancelled signing, perhaps need to remove --check")] UserCancelledSigning, } @@ -66,68 +71,18 @@ pub trait Stellar { signature: Signature(tx_signature.try_into()?), }) } - - /// Sign a Soroban authorization entry with the given address - /// # Errors - /// Returns an error if the address is not found - async fn sign_soroban_authorization_entry( - &self, - unsigned_entry: &SorobanAuthorizationEntry, - network_passphrase: &str, - ) -> Result { - let address = self.get_public_key().await?; - let mut auth = unsigned_entry.clone(); - let SorobanAuthorizationEntry { - credentials: SorobanCredentials::Address(ref mut credentials), - .. - } = auth - else { - // Doesn't need special signing - return Ok(auth); - }; - let SorobanAddressCredentials { - nonce, - signature_expiration_ledger, - .. - } = credentials; - - let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { - network_id: hash(network_passphrase), - invocation: auth.root_invocation.clone(), - nonce: *nonce, - signature_expiration_ledger: *signature_expiration_ledger, - }) - .to_xdr(Limits::none())?; - - let payload = Sha256::digest(preimage); - let signature = self.sign_blob(&payload).await?; - - let map = ScMap::sorted_from(vec![ - ( - ScVal::Symbol(ScSymbol("public_key".try_into()?)), - ScVal::Bytes(address.0.to_vec().try_into()?), - ), - ( - ScVal::Symbol(ScSymbol("signature".try_into()?)), - ScVal::Bytes(signature.try_into()?), - ), - ])?; - credentials.signature = ScVal::Vec(Some(vec![ScVal::Map(Some(map))].try_into()?)); - auth.credentials = SorobanCredentials::Address(credentials.clone()); - - Ok(auth) - } - /// Sign a Stellar transaction with the given source account /// This is a default implementation that signs the transaction hash and returns a decorated signature - /// + /// /// Todo: support signing the transaction directly. /// # Errors /// Returns an error if the source account is not found async fn sign_txn( &self, txn: Transaction, - network_passphrase: &str, + Network { + network_passphrase, .. + }: &Network, ) -> Result { let signature_payload = TransactionSignaturePayload { network_id: hash(network_passphrase), @@ -147,7 +102,7 @@ pub trait Stellar { async fn sign_soroban_authorizations( &self, raw: &Transaction, - network_passphrase: &str, + network: &Network, ) -> Result, Error> { let mut tx = raw.clone(); let Some(mut op) = requires_auth(&tx) else { @@ -161,15 +116,16 @@ pub trait Stellar { else { return Ok(None); }; - + let client = crate::rpc::Client::new(&network.rpc_url)?; let mut auths = body.auth.to_vec(); + let current_ledger = client.get_latest_ledger().await?.sequence; for auth in &mut auths { *auth = self - .maybe_sign_soroban_authorization_entry(auth, network_passphrase) + .maybe_sign_soroban_authorization_entry(auth, network, current_ledger) .await?; } body.auth = auths.try_into()?; - tx.operations = vec![op].try_into()?; + tx.operations = [op].try_into()?; Ok(Some(tx)) } @@ -179,7 +135,8 @@ pub trait Stellar { async fn maybe_sign_soroban_authorization_entry( &self, unsigned_entry: &SorobanAuthorizationEntry, - network_passphrase: &str, + network: &Network, + current_ledger: u32, ) -> Result { if let SorobanAuthorizationEntry { credentials: SorobanCredentials::Address(SorobanAddressCredentials { address, .. }), @@ -188,7 +145,7 @@ pub trait Stellar { { // See if we have a signer for this authorizationEntry // If not, then we Error - let needle = match address { + let key = match address { ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(a)))) => { stellar_strkey::ed25519::PublicKey(*a) } @@ -201,14 +158,70 @@ pub trait Stellar { }); } }; - if needle == self.get_public_key().await? { + if key == self.get_public_key().await? { return self - .sign_soroban_authorization_entry(unsigned_entry, network_passphrase) + .sign_soroban_authorization_entry(unsigned_entry, network, current_ledger) .await; } } Ok(unsigned_entry.clone()) } + + /// Sign a Soroban authorization entry with the given address + /// # Errors + /// Returns an error if the address is not found + async fn sign_soroban_authorization_entry( + &self, + unsigned_entry: &SorobanAuthorizationEntry, + Network { + network_passphrase, .. + }: &Network, + current_ledger: u32, + ) -> Result { + let address = self.get_public_key().await?; + let mut auth = unsigned_entry.clone(); + let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(ref mut credentials), + .. + } = auth + else { + // Doesn't need special signing + return Ok(auth); + }; + let SorobanAddressCredentials { + nonce, + signature_expiration_ledger, + .. + } = credentials; + + *signature_expiration_ledger = current_ledger + 60; + + let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { + network_id: hash(network_passphrase), + invocation: auth.root_invocation.clone(), + nonce: *nonce, + signature_expiration_ledger: *signature_expiration_ledger, + }) + .to_xdr(Limits::none())?; + + let payload = Sha256::digest(preimage); + let signature = self.sign_blob(&payload).await?; + + let map = ScMap::sorted_from(vec![ + ( + ScVal::Symbol(ScSymbol("public_key".try_into()?)), + ScVal::Bytes(address.0.to_vec().try_into()?), + ), + ( + ScVal::Symbol(ScSymbol("signature".try_into()?)), + ScVal::Bytes(signature.try_into()?), + ), + ])?; + credentials.signature = ScVal::Vec(Some(vec![ScVal::Map(Some(map))].try_into()?)); + auth.credentials = SorobanCredentials::Address(credentials.clone()); + + Ok(auth) + } } fn hash(network_passphrase: &str) -> xdr::Hash { From b8c7ccfdfa6537460ae37dc9ee50f9637748b985 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 2 Jul 2024 16:05:04 -0400 Subject: [PATCH 15/50] fix: fmt --- cmd/soroban-cli/src/commands/config/mod.rs | 4 +--- cmd/soroban-cli/src/commands/contract/invoke.rs | 5 ++++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/soroban-cli/src/commands/config/mod.rs b/cmd/soroban-cli/src/commands/config/mod.rs index 8c80fd7dc..630d0a343 100644 --- a/cmd/soroban-cli/src/commands/config/mod.rs +++ b/cmd/soroban-cli/src/commands/config/mod.rs @@ -100,9 +100,7 @@ impl Args { tx: &Transaction, ) -> Result, Error> { let network = self.get_network()?; - Ok(signer - .sign_soroban_authorizations(tx, &network) - .await?) + Ok(signer.sign_soroban_authorizations(tx, &network).await?) } pub fn get_network(&self) -> Result { diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index c82dec7f6..de9c94aed 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -392,7 +392,10 @@ impl NetworkRunnable for Cmd { // let auth = auth_entries(&txn); // crate::log::auth(&[auth]); for signer in &signers { - if let Some(tx) = config.sign_soroban_authorizations_with_signer(signer, &txn).await? { + if let Some(tx) = config + .sign_soroban_authorizations_with_signer(signer, &txn) + .await? + { txn = tx; } } From 6c69fa402472902fd6f8afffc601c9e7c2c236b4 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 2 Jul 2024 16:56:36 -0400 Subject: [PATCH 16/50] fix: docs --- FULL_HELP_DOCS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 619765c82..682489a6f 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -1530,6 +1530,10 @@ Sign a transaction Possible values: `true`, `false` +* `-a`, `--auth-only` + + Possible values: `true`, `false` + From aacff2c8cd68abb41bbdd61fa5631ccb719670cd Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 9 Jul 2024 14:05:13 -0400 Subject: [PATCH 17/50] fix: send does not require a source account --- FULL_HELP_DOCS.md | 5 +---- cmd/soroban-cli/src/commands/tx/send.rs | 26 +++++++++++++++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index e032a38cf..fe3c48155 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -1322,18 +1322,15 @@ Sign a transaction Send a transaction envelope to the network -**Usage:** `stellar tx send [OPTIONS] --source-account ` +**Usage:** `stellar tx send [OPTIONS]` ###### **Options:** * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes diff --git a/cmd/soroban-cli/src/commands/tx/send.rs b/cmd/soroban-cli/src/commands/tx/send.rs index 08c32c2da..e64997483 100644 --- a/cmd/soroban-cli/src/commands/tx/send.rs +++ b/cmd/soroban-cli/src/commands/tx/send.rs @@ -1,14 +1,21 @@ use async_trait::async_trait; use soroban_rpc::GetTransactionResponse; -use crate::commands::{config, global, NetworkRunnable}; +use crate::commands::{ + config::{self, locator}, + global, network, NetworkRunnable, +}; #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] XdrArgs(#[from] super::xdr::Error), #[error(transparent)] - Config(#[from] super::super::config::Error), + Network(#[from] network::Error), + #[error(transparent)] + Locator(#[from] locator::Error), + #[error(transparent)] + Config(#[from] config::Error), #[error(transparent)] Rpc(#[from] crate::rpc::Error), #[error(transparent)] @@ -21,14 +28,14 @@ pub enum Error { /// e.g. `cat file.txt | soroban tx send` pub struct Cmd { #[clap(flatten)] - pub config: super::super::config::Args, + pub network: network::Args, + #[clap(flatten)] + pub locator: locator::Args, } impl Cmd { pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { - let response = self - .run_against_rpc_server(Some(global_args), Some(&self.config)) - .await?; + let response = self.run_against_rpc_server(Some(global_args), None).await?; println!("{}", serde_json::to_string_pretty(&response)?); Ok(()) } @@ -44,8 +51,11 @@ impl NetworkRunnable for Cmd { _: Option<&global::Args>, config: Option<&config::Args>, ) -> Result { - let config = config.unwrap_or(&self.config); - let network = config.get_network()?; + let network = if let Some(config) = config { + config.get_network()? + } else { + self.network.get(&self.locator)? + }; let client = crate::rpc::Client::new(&network.rpc_url)?; let tx_env = super::xdr::tx_envelope_from_stdin()?; Ok(client.send_transaction_polling(&tx_env).await?) From 71dc74b9f592ec7bc3e559008115b3976b742f8a Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 9 Jul 2024 14:06:44 -0400 Subject: [PATCH 18/50] chore: initial sign and send test --- .../soroban-test/tests/it/integration/tx.rs | 67 +++++++++++++++++-- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index a28057b56..8bceccc81 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -1,10 +1,10 @@ use soroban_sdk::xdr::{Limits, ReadXdr, TransactionEnvelope, WriteXdr}; use soroban_test::{AssertExt, TestEnv}; -use crate::integration::util::{deploy_contract, DeployKind, HELLO_WORLD}; +use crate::integration::util::{deploy_contract, deploy_hello, DeployKind, HELLO_WORLD}; #[tokio::test] -async fn txn_simulate() { +async fn simulate() { let sandbox = &TestEnv::new(); let xdr_base64_build_only = deploy_contract(sandbox, HELLO_WORLD, DeployKind::BuildOnly).await; let xdr_base64_sim_only = deploy_contract(sandbox, HELLO_WORLD, DeployKind::SimOnly).await; @@ -32,7 +32,7 @@ async fn txn_simulate() { } #[tokio::test] -async fn txn_send() { +async fn send() { let sandbox = &TestEnv::new(); sandbox .new_assert_cmd("contract") @@ -44,7 +44,7 @@ async fn txn_send() { let xdr_base64 = deploy_contract(sandbox, HELLO_WORLD, DeployKind::SimOnly).await; println!("{xdr_base64}"); let tx_env = TransactionEnvelope::from_xdr_base64(&xdr_base64, Limits::none()).unwrap(); - let tx_env = sign(sandbox, &tx_env); + let tx_env = sign_manually(sandbox, &tx_env); println!( "Transaction to send:\n{}", @@ -54,7 +54,6 @@ async fn txn_send() { let assembled_str = sandbox .new_assert_cmd("tx") .arg("send") - .arg("--source=test") .write_stdin(tx_env.to_xdr_base64(Limits::none()).unwrap()) .assert() .success() @@ -62,7 +61,7 @@ async fn txn_send() { println!("Transaction sent: {assembled_str}"); } -fn sign(sandbox: &TestEnv, tx_env: &TransactionEnvelope) -> TransactionEnvelope { +fn sign_manually(sandbox: &TestEnv, tx_env: &TransactionEnvelope) -> TransactionEnvelope { TransactionEnvelope::from_xdr_base64( sandbox .new_assert_cmd("tx") @@ -76,3 +75,59 @@ fn sign(sandbox: &TestEnv, tx_env: &TransactionEnvelope) -> TransactionEnvelope ) .unwrap() } + +#[tokio::test] +async fn sign() { + let sandbox = &TestEnv::new(); + let id = &deploy_hello(sandbox).await; + // Create new test_other account + sandbox + .new_assert_cmd("keys") + .arg("fund") + .arg("test_other") + .assert() + .stderr(predicates::str::contains("Account already exists")); + + // Get Xdr for transaction where auth is required for test_other + let xdr_base64 = sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--id") + .arg(id) + .arg("--sim-only") + .arg("--") + .arg("auth") + .arg("--world=world") + .arg("--addr=test_other") + .assert() + .success() + .stdout_as_str(); + // Sign the transaction's auth entry with test_other + let xdr_base64 = sandbox + .new_assert_cmd("tx") + .arg("sign") + .arg("--auth") + .arg("--source=test_other") + .write_stdin(xdr_base64.as_bytes()) + .assert() + .success() + .stdout_as_str(); + // Sign the transaction with test as source account + let xdr_base64 = sandbox + .new_assert_cmd("tx") + .arg("sign") + .write_stdin(xdr_base64.as_bytes()) + .assert() + .success() + .stdout_as_str(); + // Send transaction + let res = sandbox + .new_assert_cmd("tx") + .arg("send") + .write_stdin(xdr_base64.as_bytes()) + .assert() + .success() + .stdout_as_str(); + println!("{res}"); + assert!(res == "hello world"); +} From c0d3b61074319de8c00ca2fc9d7a6d40f7a4c223 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 9 Jul 2024 14:14:46 -0400 Subject: [PATCH 19/50] fix: test --- cmd/crates/soroban-test/tests/it/integration/tx.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 8bceccc81..1f1ee8bc4 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -83,10 +83,9 @@ async fn sign() { // Create new test_other account sandbox .new_assert_cmd("keys") - .arg("fund") + .arg("generate") .arg("test_other") - .assert() - .stderr(predicates::str::contains("Account already exists")); + .assert(); // Get Xdr for transaction where auth is required for test_other let xdr_base64 = sandbox From cd9803869361b40579b006c9d3205eed907118db Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 9 Jul 2024 14:23:38 -0400 Subject: [PATCH 20/50] fix: send output --- cmd/crates/soroban-test/tests/it/integration/tx.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 1f1ee8bc4..a6c9a2f4a 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -126,7 +126,5 @@ async fn sign() { .write_stdin(xdr_base64.as_bytes()) .assert() .success() - .stdout_as_str(); - println!("{res}"); - assert!(res == "hello world"); + .stdout(predicate::str::contains(r#""status": "SUCCESS""#)); } From d4ce6e23be83e53b7815428cca5a548f1b3b219f Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 9 Jul 2024 14:44:06 -0400 Subject: [PATCH 21/50] fix: crate name --- cmd/crates/soroban-test/tests/it/integration/tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index a6c9a2f4a..9350d3393 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -126,5 +126,5 @@ async fn sign() { .write_stdin(xdr_base64.as_bytes()) .assert() .success() - .stdout(predicate::str::contains(r#""status": "SUCCESS""#)); + .stdout(predicates::str::contains(r#""status": "SUCCESS""#)); } From e26599a5ed44a013548be9acad758ee4bbeb2273 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 9 Jul 2024 15:03:05 -0400 Subject: [PATCH 22/50] fix: remove ledger stuff and add comment to tx sign --- .github/workflows/binaries.yml | 3 --- .github/workflows/e2e.yml | 3 --- .github/workflows/full-help-docs.yml | 3 --- .github/workflows/rpc-tests.yml | 3 --- Cargo.toml | 4 ---- FULL_HELP_DOCS.md | 2 +- cmd/soroban-cli/src/commands/tx/sign.rs | 1 + 7 files changed, 2 insertions(+), 17 deletions(-) diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml index 4d8440cd5..97304b7bd 100644 --- a/.github/workflows/binaries.yml +++ b/.github/workflows/binaries.yml @@ -39,9 +39,6 @@ jobs: - run: rustup target add ${{ matrix.sys.target }} - if: matrix.sys.target == 'aarch64-unknown-linux-gnu' run: sudo apt-get update && sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu - - if: matrix.sys.target == 'aarch64-unknown-linux-gnu' - run: | - sudo apt install -y libudev-dev - name: Setup vars run: | version="$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[] | select(.name == "stellar-cli") | .version')" diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 2b02b4f5c..0704d4a55 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -84,9 +84,6 @@ jobs: path: system-test/js-stellar-sdk - uses: stellar/actions/rust-cache@main - - name: install libudev-dev - run: | - sudo apt install -y libudev-dev - name: Build system test with component versions run: | cd $GITHUB_WORKSPACE/system-test diff --git a/.github/workflows/full-help-docs.yml b/.github/workflows/full-help-docs.yml index f8f61521c..4893a88fd 100644 --- a/.github/workflows/full-help-docs.yml +++ b/.github/workflows/full-help-docs.yml @@ -14,9 +14,6 @@ jobs: - uses: actions/checkout@v3 - uses: stellar/actions/rust-cache@main - run: rustup update - - name: install libudev-dev - run: | - sudo apt install -y libudev-dev - name: Generate help doc # this looks goofy to get GITHUB_OUTPUT to work with multi-line return values; # see https://stackoverflow.com/a/74266196/249801 diff --git a/.github/workflows/rpc-tests.yml b/.github/workflows/rpc-tests.yml index b9279ab95..ad999830a 100644 --- a/.github/workflows/rpc-tests.yml +++ b/.github/workflows/rpc-tests.yml @@ -35,9 +35,6 @@ jobs: target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - run: rustup update - - name: install libudev-dev - run: | - sudo apt install -y libudev-dev - run: cargo build - run: rustup target add wasm32-unknown-unknown - run: make build-test-wasms diff --git a/Cargo.toml b/Cargo.toml index 384bb996c..0f22a48db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,10 +51,6 @@ version = "=21.1.1" version = "=21.1.0" path = "cmd/soroban-cli" -[workspace.dependencies.stellar-ledger] -version = "=21.0.0" -path = "cmd/crates/stellar-ledger" - [workspace.dependencies.soroban-rpc] package = "stellar-rpc-client" version = "21.3.1" diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index fe3c48155..f84a9074e 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -1314,7 +1314,7 @@ Sign a transaction * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." * `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes -* `-a`, `--auth-only` +* `-a`, `--auth-only` — Only sign the Authorization Entries required by the provided source account diff --git a/cmd/soroban-cli/src/commands/tx/sign.rs b/cmd/soroban-cli/src/commands/tx/sign.rs index 8714470ac..5fb48d2b5 100644 --- a/cmd/soroban-cli/src/commands/tx/sign.rs +++ b/cmd/soroban-cli/src/commands/tx/sign.rs @@ -17,6 +17,7 @@ pub enum Error { pub struct Cmd { #[clap(flatten)] pub config: config::Args, + /// Only sign the Authorization Entries required by the provided source account #[arg(long, visible_alias = "auth", short = 'a')] pub auth_only: bool, } From 1767197538e9ce9ab4f356978bfda66aff30f87f Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Wed, 10 Jul 2024 15:14:19 -0400 Subject: [PATCH 23/50] fix: remove KeyName will be added by ledger PR --- .../src/commands/config/locator.rs | 34 +++---------------- cmd/soroban-cli/src/commands/keys/add.rs | 4 +-- cmd/soroban-cli/src/commands/keys/address.rs | 10 +++--- cmd/soroban-cli/src/commands/keys/generate.rs | 4 +-- cmd/soroban-cli/src/commands/keys/rm.rs | 4 +-- cmd/soroban-cli/src/commands/keys/show.rs | 4 +-- 6 files changed, 14 insertions(+), 46 deletions(-) diff --git a/cmd/soroban-cli/src/commands/config/locator.rs b/cmd/soroban-cli/src/commands/config/locator.rs index b82646d63..9054b7cc2 100644 --- a/cmd/soroban-cli/src/commands/config/locator.rs +++ b/cmd/soroban-cli/src/commands/config/locator.rs @@ -70,10 +70,6 @@ pub enum Error { CannotAccessConfigDir, #[error("cannot parse contract ID {0}: {1}")] CannotParseContractId(String, DecodeError), - #[error("Incorrect Key name")] - IncorrectKeyName, - #[error("Cannot name a Key ledger")] - LedgerKeyName, } #[derive(Debug, clap::Args, Default, Clone)] @@ -107,28 +103,6 @@ impl Display for Location { } } -#[derive(Clone, Debug)] -pub struct KeyName(String); - -impl std::ops::Deref for KeyName { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl FromStr for KeyName { - type Err = Error; - - fn from_str(s: &str) -> Result { - if s == "ledger" { - return Err(Error::LedgerKeyName); - } - Ok(KeyName(s.to_string())) - } -} - impl AsRef for Location { fn as_ref(&self) -> &Path { match self { @@ -175,7 +149,7 @@ impl Args { ) } - pub fn write_identity(&self, name: &KeyName, secret: &SignerKind) -> Result<(), Error> { + pub fn write_identity(&self, name: &str, secret: &SignerKind) -> Result<(), Error> { KeyType::Identity.write(name, secret, &self.config_dir()?) } @@ -233,7 +207,7 @@ impl Args { Ok(saved_networks.chain(default_networks).collect()) } - pub fn read_identity(&self, name: &KeyName) -> Result { + pub fn read_identity(&self, name: &str) -> Result { KeyType::Identity.read_with_global(name, &self.local_config()?) } @@ -241,7 +215,7 @@ impl Args { if let Ok(signer) = account_str.parse::() { Ok(signer) } else { - self.read_identity(&account_str.parse()?) + self.read_identity(account_str) } } @@ -256,7 +230,7 @@ impl Args { res } - pub fn remove_identity(&self, name: &KeyName) -> Result<(), Error> { + pub fn remove_identity(&self, name: &str) -> Result<(), Error> { KeyType::Identity.remove(name, &self.config_dir()?) } diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs index ab8de6cf6..bf9ea2293 100644 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -1,7 +1,5 @@ use clap::command; -use crate::commands::config::locator::KeyName; - use super::super::config::{locator, secret}; #[derive(thiserror::Error, Debug)] @@ -17,7 +15,7 @@ pub enum Error { #[group(skip)] pub struct Cmd { /// Name of identity - pub name: KeyName, + pub name: String, #[command(flatten)] pub secrets: secret::Args, diff --git a/cmd/soroban-cli/src/commands/keys/address.rs b/cmd/soroban-cli/src/commands/keys/address.rs index 3a12fc35d..766e948eb 100644 --- a/cmd/soroban-cli/src/commands/keys/address.rs +++ b/cmd/soroban-cli/src/commands/keys/address.rs @@ -1,9 +1,7 @@ -use clap::arg; +use crate::commands::config::secret; -use super::super::config::{ - locator::{self, KeyName}, - secret, -}; +use super::super::config::locator; +use clap::arg; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -21,7 +19,7 @@ pub enum Error { #[group(skip)] pub struct Cmd { /// Name of identity to lookup, default test identity used if not provided - pub name: KeyName, + pub name: String, /// If identity is a seed phrase use this hd path, default is 0 #[arg(long)] diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs index 28b01882e..29dbab5cc 100644 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ b/cmd/soroban-cli/src/commands/keys/generate.rs @@ -1,6 +1,6 @@ use clap::{arg, command}; -use crate::commands::{config::locator::KeyName, network}; +use crate::commands::network; use super::super::config::{ locator, @@ -21,7 +21,7 @@ pub enum Error { #[group(skip)] pub struct Cmd { /// Name of identity - pub name: KeyName, + pub name: String, /// Do not fund address #[arg(long)] pub no_fund: bool, diff --git a/cmd/soroban-cli/src/commands/keys/rm.rs b/cmd/soroban-cli/src/commands/keys/rm.rs index 9d9c1be51..df48108d3 100644 --- a/cmd/soroban-cli/src/commands/keys/rm.rs +++ b/cmd/soroban-cli/src/commands/keys/rm.rs @@ -1,6 +1,6 @@ use clap::command; -use super::super::config::locator::{self, KeyName}; +use super::super::config::locator; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -12,7 +12,7 @@ pub enum Error { #[group(skip)] pub struct Cmd { /// Identity to remove - pub name: KeyName, + pub name: String, #[command(flatten)] pub config: locator::Args, diff --git a/cmd/soroban-cli/src/commands/keys/show.rs b/cmd/soroban-cli/src/commands/keys/show.rs index 493d02a2f..b99478cbc 100644 --- a/cmd/soroban-cli/src/commands/keys/show.rs +++ b/cmd/soroban-cli/src/commands/keys/show.rs @@ -1,7 +1,5 @@ use clap::arg; -use crate::commands::config::locator::KeyName; - use super::super::config::{locator, secret}; #[derive(thiserror::Error, Debug)] @@ -20,7 +18,7 @@ pub enum Error { #[group(skip)] pub struct Cmd { /// Name of identity to lookup, default is test identity - pub name: KeyName, + pub name: String, /// If identity is a seed phrase use this hd path, default is 0 #[arg(long)] From 9df886f7f2ff8711f858e8a160c6b6e36288ff70 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 11 Jul 2024 10:55:35 -0400 Subject: [PATCH 24/50] fix: rpc tests --- cmd/crates/soroban-test/tests/it/integration/hello_world.rs | 2 +- cmd/crates/soroban-test/tests/it/integration/tx.rs | 2 +- 2 files changed, 2 insertions(+), 2 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 795efb322..c8aaaea42 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -105,7 +105,7 @@ async fn invoke() { }; config_locator .write_identity( - &"testone".parse().unwrap(), + "testone", &secret::SignerKind::SecretKey { secret_key: secret_key_1.clone(), }, diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 9350d3393..ce48cc8ae 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -120,7 +120,7 @@ async fn sign() { .success() .stdout_as_str(); // Send transaction - let res = sandbox + sandbox .new_assert_cmd("tx") .arg("send") .write_stdin(xdr_base64.as_bytes()) From ee193cce65c18e4dd0d51afef1aef42c490b0bb4 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 11 Jul 2024 12:27:20 -0400 Subject: [PATCH 25/50] feat: add `ledgers-from-now` arg to invoke and auth This allows users to specify how long a signed auth entry is valid. --- FULL_HELP_DOCS.md | 6 ++ .../soroban-test/tests/it/integration/tx.rs | 55 +++++++++++++++++++ cmd/soroban-cli/src/commands/config/mod.rs | 10 +++- .../src/commands/contract/invoke.rs | 4 +- cmd/soroban-cli/src/commands/tx/auth.rs | 19 +++++++ cmd/soroban-cli/src/commands/tx/mod.rs | 1 + cmd/soroban-cli/src/commands/tx/sign.rs | 4 +- cmd/soroban-cli/src/signer.rs | 13 ++--- 8 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 cmd/soroban-cli/src/commands/tx/auth.rs diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 9c566a967..d6281c433 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -522,6 +522,9 @@ stellar contract invoke ... -- --help * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout * `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout +* `--ledgers-from-now ` — Number of ledgers from current ledger before the signed auth entry expires. Default 60 ~ 5 minutes + + Default value: `60` @@ -1256,6 +1259,9 @@ Sign a transaction * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." * `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes +* `--ledgers-from-now ` — Number of ledgers from current ledger before the signed auth entry expires. Default 60 ~ 5 minutes + + Default value: `60` * `-a`, `--auth-only` — Only sign the Authorization Entries required by the provided source account diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index ce48cc8ae..65d59ebed 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -128,3 +128,58 @@ async fn sign() { .success() .stdout(predicates::str::contains(r#""status": "SUCCESS""#)); } + +#[tokio::test] +async fn expired_auth_entry() { + let sandbox = &TestEnv::new(); + let id = &deploy_hello(sandbox).await; + // Create new test_other account + sandbox + .new_assert_cmd("keys") + .arg("generate") + .arg("test_other") + .assert(); + + // Get Xdr for transaction where auth is required for test_other + let xdr_base64 = sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--id") + .arg(id) + .arg("--sim-only") + .arg("--") + .arg("auth") + .arg("--world=world") + .arg("--addr=test_other") + .assert() + .success() + .stdout_as_str(); + // Sign the transaction's auth entry with test_other + let xdr_base64 = sandbox + .new_assert_cmd("tx") + .arg("sign") + .arg("--auth") + .arg("--source=test_other") + .arg("--expiration-ledger=1") + .write_stdin(xdr_base64.as_bytes()) + .assert() + .success() + .stdout_as_str(); + tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; + // Sign the transaction with test as source account + let xdr_base64 = sandbox + .new_assert_cmd("tx") + .arg("sign") + .write_stdin(xdr_base64.as_bytes()) + .assert() + .success() + .stdout_as_str(); + // Send transaction + sandbox + .new_assert_cmd("tx") + .arg("send") + .write_stdin(xdr_base64.as_bytes()) + .assert() + .stdout(predicates::str::contains(r#""status": "FAILED""#)) + .stdout(predicates::str::contains("signature has expired")); +} diff --git a/cmd/soroban-cli/src/commands/config/mod.rs b/cmd/soroban-cli/src/commands/config/mod.rs index 630d0a343..377385d37 100644 --- a/cmd/soroban-cli/src/commands/config/mod.rs +++ b/cmd/soroban-cli/src/commands/config/mod.rs @@ -90,17 +90,23 @@ impl Args { pub async fn sign_soroban_authorizations( &self, tx: &Transaction, + ledgers_from_current: u32, ) -> Result, Error> { - self.sign_soroban_authorizations_with_signer(&self.signer()?, tx) + self.sign_soroban_authorizations_with_signer(&self.signer()?, tx, ledgers_from_current) .await } pub async fn sign_soroban_authorizations_with_signer( &self, signer: &(impl Stellar + std::marker::Sync), tx: &Transaction, + ledgers_from_current: u32, ) -> Result, Error> { let network = self.get_network()?; - Ok(signer.sign_soroban_authorizations(tx, &network).await?) + let client = crate::rpc::Client::new(&network.rpc_url)?; + let expiration_ledger = client.get_latest_ledger().await?.sequence + ledgers_from_current; + Ok(signer + .sign_soroban_authorizations(tx, &network, expiration_ledger) + .await?) } pub fn get_network(&self) -> Result { diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index de9c94aed..7bfe84b7a 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -59,6 +59,8 @@ pub struct Cmd { pub config: config::Args, #[command(flatten)] pub fee: crate::fee::Args, + #[command(flatten)] + pub ledgers: crate::commands::tx::auth::Args, } impl FromStr for Cmd { @@ -393,7 +395,7 @@ impl NetworkRunnable for Cmd { // crate::log::auth(&[auth]); for signer in &signers { if let Some(tx) = config - .sign_soroban_authorizations_with_signer(signer, &txn) + .sign_soroban_authorizations_with_signer(signer, &txn, self.ledgers.from_now) .await? { txn = tx; diff --git a/cmd/soroban-cli/src/commands/tx/auth.rs b/cmd/soroban-cli/src/commands/tx/auth.rs new file mode 100644 index 000000000..cf91c2991 --- /dev/null +++ b/cmd/soroban-cli/src/commands/tx/auth.rs @@ -0,0 +1,19 @@ +use clap::arg; + +#[derive(Debug, clap::Args, Clone)] +#[group(skip)] +pub struct Args { + /// Number of ledgers from current ledger before the signed auth entry expires. Default 60 ~ 5 minutes. + #[arg( + long = "ledgers-from-now", + visible_alias = "ledgers", + default_value = "60" + )] + pub from_now: u32, +} + +impl Default for Args { + fn default() -> Self { + Self { from_now: 60 } + } +} diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index 8de6c8c19..2ab513b6b 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -2,6 +2,7 @@ use clap::Parser; use super::global; +pub mod auth; pub mod send; pub mod sign; pub mod simulate; diff --git a/cmd/soroban-cli/src/commands/tx/sign.rs b/cmd/soroban-cli/src/commands/tx/sign.rs index 5fb48d2b5..e4e66e4f2 100644 --- a/cmd/soroban-cli/src/commands/tx/sign.rs +++ b/cmd/soroban-cli/src/commands/tx/sign.rs @@ -17,6 +17,8 @@ pub enum Error { pub struct Cmd { #[clap(flatten)] pub config: config::Args, + #[clap(flatten)] + pub ledgers: super::auth::Args, /// Only sign the Authorization Entries required by the provided source account #[arg(long, visible_alias = "auth", short = 'a')] pub auth_only: bool, @@ -35,7 +37,7 @@ impl Cmd { if self.auth_only { Ok(self .config - .sign_soroban_authorizations(&tx) + .sign_soroban_authorizations(&tx, self.ledgers.from_now) .await? .unwrap_or(tx) .into()) diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index f78ac707c..7213de3af 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -103,6 +103,7 @@ pub trait Stellar { &self, raw: &Transaction, network: &Network, + expiration_ledger: u32, ) -> Result, Error> { let mut tx = raw.clone(); let Some(mut op) = requires_auth(&tx) else { @@ -116,12 +117,10 @@ pub trait Stellar { else { return Ok(None); }; - let client = crate::rpc::Client::new(&network.rpc_url)?; let mut auths = body.auth.to_vec(); - let current_ledger = client.get_latest_ledger().await?.sequence; for auth in &mut auths { *auth = self - .maybe_sign_soroban_authorization_entry(auth, network, current_ledger) + .maybe_sign_soroban_authorization_entry(auth, network, expiration_ledger) .await?; } body.auth = auths.try_into()?; @@ -136,7 +135,7 @@ pub trait Stellar { &self, unsigned_entry: &SorobanAuthorizationEntry, network: &Network, - current_ledger: u32, + expiration_ledger: u32, ) -> Result { if let SorobanAuthorizationEntry { credentials: SorobanCredentials::Address(SorobanAddressCredentials { address, .. }), @@ -160,7 +159,7 @@ pub trait Stellar { }; if key == self.get_public_key().await? { return self - .sign_soroban_authorization_entry(unsigned_entry, network, current_ledger) + .sign_soroban_authorization_entry(unsigned_entry, network, expiration_ledger) .await; } } @@ -176,7 +175,7 @@ pub trait Stellar { Network { network_passphrase, .. }: &Network, - current_ledger: u32, + expiration_ledger: u32, ) -> Result { let address = self.get_public_key().await?; let mut auth = unsigned_entry.clone(); @@ -194,7 +193,7 @@ pub trait Stellar { .. } = credentials; - *signature_expiration_ledger = current_ledger + 60; + *signature_expiration_ledger = expiration_ledger; let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { network_id: hash(network_passphrase), From df18c0a68bc03b57c47835d59312080f4053bdbb Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 11 Jul 2024 12:51:07 -0400 Subject: [PATCH 26/50] fix: use correct arg --- cmd/crates/soroban-test/tests/it/integration/tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 65d59ebed..26c28d6d5 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -160,7 +160,7 @@ async fn expired_auth_entry() { .arg("sign") .arg("--auth") .arg("--source=test_other") - .arg("--expiration-ledger=1") + .arg("--ledgers=1") .write_stdin(xdr_base64.as_bytes()) .assert() .success() From 62931d58f001b52314c13388a51e1dd7ea433484 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 11 Jul 2024 12:58:51 -0400 Subject: [PATCH 27/50] fix: use stderr --- cmd/crates/soroban-test/tests/it/integration/tx.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 26c28d6d5..9edac03c6 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -180,6 +180,6 @@ async fn expired_auth_entry() { .arg("send") .write_stdin(xdr_base64.as_bytes()) .assert() - .stdout(predicates::str::contains(r#""status": "FAILED""#)) - .stdout(predicates::str::contains("signature has expired")); + .stderr(predicates::str::contains(r#""status": "FAILED""#)) + .stderr(predicates::str::contains("signature has expired")); } From a9f95a76e693898aa2721a0833af7596613a4a48 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 11 Jul 2024 14:02:35 -0400 Subject: [PATCH 28/50] fix: proper matching of stderr --- cmd/crates/soroban-test/tests/it/integration/tx.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 9edac03c6..d32474b1b 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -180,6 +180,7 @@ async fn expired_auth_entry() { .arg("send") .write_stdin(xdr_base64.as_bytes()) .assert() - .stderr(predicates::str::contains(r#""status": "FAILED""#)) + .failure() + .stderr(predicates::str::contains(r#""FAILED""#)) .stderr(predicates::str::contains("signature has expired")); } From 1e118c1026d887d583d85b3549f2cdfca66c0423 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 12 Jul 2024 16:15:51 -0400 Subject: [PATCH 29/50] fix: remove unused code and make transaction hash public --- cmd/soroban-cli/src/signer.rs | 16 +++++++++----- cmd/soroban-cli/src/utils.rs | 40 ++--------------------------------- 2 files changed, 13 insertions(+), 43 deletions(-) diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index 7213de3af..ded3941f5 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -45,6 +45,16 @@ fn requires_auth(txn: &Transaction) -> Option { .then(move || op.clone()) } +/// Calculate the hash of a Transaction +pub fn transaction_hash(txn: &Transaction, network_passphrase: &str) -> Result<[u8; 32], Error> { + let signature_payload = TransactionSignaturePayload { + network_id: hash(network_passphrase), + tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(txn.clone()), + }; + let hash = Sha256::digest(signature_payload.to_xdr(Limits::none())?).into(); + Ok(hash) +} + /// A trait for signing Stellar transactions and Soroban authorization entries #[async_trait::async_trait] pub trait Stellar { @@ -84,11 +94,7 @@ pub trait Stellar { network_passphrase, .. }: &Network, ) -> Result { - let signature_payload = TransactionSignaturePayload { - network_id: hash(network_passphrase), - tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(txn.clone()), - }; - let hash = Sha256::digest(signature_payload.to_xdr(Limits::none())?).into(); + let hash = transaction_hash(&txn, network_passphrase)?; let decorated_signature = self.sign_txn_hash(hash).await?; Ok(TransactionEnvelope::Tx(TransactionV1Envelope { tx: txn, diff --git a/cmd/soroban-cli/src/utils.rs b/cmd/soroban-cli/src/utils.rs index e74222c2c..1adafa6e1 100644 --- a/cmd/soroban-cli/src/utils.rs +++ b/cmd/soroban-cli/src/utils.rs @@ -1,12 +1,9 @@ -use ed25519_dalek::Signer; use sha2::{Digest, Sha256}; use stellar_strkey::ed25519::PrivateKey; use soroban_env_host::xdr::{ - Asset, ContractIdPreimage, DecoratedSignature, Error as XdrError, Hash, HashIdPreimage, - HashIdPreimageContractId, Limits, Signature, SignatureHint, Transaction, TransactionEnvelope, - TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, - TransactionV1Envelope, WriteXdr, + Asset, ContractIdPreimage, Error as XdrError, Hash, HashIdPreimage, HashIdPreimageContractId, + Limits, WriteXdr, }; pub use soroban_spec_tools::contract as contract_spec; @@ -18,39 +15,6 @@ pub fn contract_hash(contract: &[u8]) -> Result { Ok(Hash(Sha256::digest(contract).into())) } -/// # Errors -/// -/// Might return an error -pub fn transaction_hash(tx: &Transaction, network_passphrase: &str) -> Result<[u8; 32], XdrError> { - let signature_payload = TransactionSignaturePayload { - network_id: Hash(Sha256::digest(network_passphrase).into()), - tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(tx.clone()), - }; - Ok(Sha256::digest(signature_payload.to_xdr(Limits::none())?).into()) -} - -/// # Errors -/// -/// Might return an error -pub fn sign_transaction( - key: &ed25519_dalek::SigningKey, - tx: &Transaction, - network_passphrase: &str, -) -> Result { - let tx_hash = transaction_hash(tx, network_passphrase)?; - let tx_signature = key.sign(&tx_hash); - - let decorated_signature = DecoratedSignature { - hint: SignatureHint(key.verifying_key().to_bytes()[28..].try_into()?), - signature: Signature(tx_signature.to_bytes().try_into()?), - }; - - Ok(TransactionEnvelope::Tx(TransactionV1Envelope { - tx: tx.clone(), - signatures: vec![decorated_signature].try_into()?, - })) -} - /// # Errors /// /// Might return an error From 1369614ef26b4f636d013ac1a3ed2dc5b1b43bd9 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Wed, 24 Jul 2024 12:34:53 -0400 Subject: [PATCH 30/50] Update cmd/soroban-cli/src/commands/tx/sign.rs Co-authored-by: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> --- cmd/soroban-cli/src/commands/tx/sign.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/commands/tx/sign.rs b/cmd/soroban-cli/src/commands/tx/sign.rs index e4e66e4f2..9e882a29b 100644 --- a/cmd/soroban-cli/src/commands/tx/sign.rs +++ b/cmd/soroban-cli/src/commands/tx/sign.rs @@ -20,7 +20,7 @@ pub struct Cmd { #[clap(flatten)] pub ledgers: super::auth::Args, /// Only sign the Authorization Entries required by the provided source account - #[arg(long, visible_alias = "auth", short = 'a')] + #[arg(long)] pub auth_only: bool, } From 344bf95c3641fbd67b3d6810d8c1cf5030c8346d Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 26 Jul 2024 16:29:01 -0400 Subject: [PATCH 31/50] feat: add `sign_with::Args` to handle non-source account signing This also allows now requiring `--yes` since it won't be a breaking change. --- FULL_HELP_DOCS.md | 72 ++++++++++++------- cmd/crates/soroban-test/src/lib.rs | 8 ++- .../src/commands/contract/invoke.rs | 2 +- cmd/soroban-cli/src/config/mod.rs | 20 +++--- cmd/soroban-cli/src/config/sign_with.rs | 32 +++++++++ 5 files changed, 96 insertions(+), 38 deletions(-) create mode 100644 cmd/soroban-cli/src/config/sign_with.rs diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 7cc31ba1a..db791f9e1 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -117,10 +117,12 @@ Get Id of builtin Soroban Asset Contract. Deprecated, use `stellar contract id a * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes +* `--sign-with-key ` — Sign with secret key +* `--sign-with-laboratory` — Sign with labratory +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -137,10 +139,12 @@ Deploy builtin Soroban Asset Contract * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes +* `--sign-with-key ` — Sign with secret key +* `--sign-with-laboratory` — Sign with labratory +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -273,10 +277,12 @@ If no keys are specified the contract itself is extended. * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes +* `--sign-with-key ` — Sign with secret key +* `--sign-with-laboratory` — Sign with labratory +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -302,10 +308,12 @@ Deploy a wasm contract * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes +* `--sign-with-key ` — Sign with secret key +* `--sign-with-laboratory` — Sign with labratory +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -364,10 +372,12 @@ Deploy builtin Soroban Asset Contract * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes +* `--sign-with-key ` — Sign with secret key +* `--sign-with-laboratory` — Sign with labratory +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -384,10 +394,12 @@ Deploy normal Wasm Contract * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes +* `--sign-with-key ` — Sign with secret key +* `--sign-with-laboratory` — Sign with labratory +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -452,10 +464,12 @@ Install a WASM file to the ledger without creating a contract instance * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes +* `--sign-with-key ` — Sign with secret key +* `--sign-with-laboratory` — Sign with labratory +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -492,10 +506,12 @@ stellar contract invoke ... -- --help * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes +* `--sign-with-key ` — Sign with secret key +* `--sign-with-laboratory` — Sign with labratory +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -561,10 +577,12 @@ Print the current value of a contract-data ledger entry * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes +* `--sign-with-key ` — Sign with secret key +* `--sign-with-laboratory` — Sign with labratory +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -599,10 +617,12 @@ If no keys are specificed the contract itself is restored. * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes +* `--sign-with-key ` — Sign with secret key +* `--sign-with-laboratory` — Sign with labratory +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -1038,10 +1058,12 @@ Simulate a transaction envelope from stdin * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes +* `--sign-with-key ` — Sign with secret key +* `--sign-with-laboratory` — Sign with labratory +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -1071,10 +1093,12 @@ Sign a transaction * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--check` — Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes +* `--sign-with-key ` — Sign with secret key +* `--sign-with-laboratory` — Sign with labratory +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted * `--ledgers-from-now ` — Number of ledgers from current ledger before the signed auth entry expires. Default 60 ~ 5 minutes Default value: `60` diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index fe4772a3c..d528e565b 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -31,7 +31,7 @@ use fs_extra::dir::CopyOptions; use soroban_cli::{ commands::{contract::invoke, global, keys, NetworkRunnable}, - config::{self, network}, + config::{self, network, sign_with}, CommandParser, }; @@ -232,8 +232,10 @@ impl TestEnv { global: false, config_dir, }, - hd_path: None, - check: false, + sign_with: sign_with::Args { + yes: true, + ..Default::default() + }, } } diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index d765391cc..6da6b2956 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -238,7 +238,7 @@ impl Cmd { if let Ok(signer_) = config .locator .account(&s) - .and_then(|signer| Ok(signer.signer(config.hd_path, config.check)?)) + .and_then(|signer| Ok(signer.signer(config.sign_with.hd_path, false)?)) { s = signer_.get_public_key().await?.to_string(); signer = Some(signer_); diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index 1bb9a9a89..da4a16bfd 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -16,6 +16,7 @@ pub mod data; pub mod locator; pub mod network; pub mod secret; +pub mod sign_with; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -41,29 +42,28 @@ pub struct Args { /// Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). pub source_account: String, - #[arg(long)] - /// If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - pub hd_path: Option, - #[command(flatten)] pub locator: locator::Args, - /// Check with user before signature. Eventually this will be replaced with `--yes`, which does the opposite and will force a check without --yes - #[arg(long)] - pub check: bool, + #[command(flatten)] + pub sign_with: sign_with::Args, } impl Args { pub fn signer(&self) -> Result { + let (account, prompt) = self.sign_with.sign_with_key.as_ref().map_or_else( + || (&self.source_account, false), + |s| (s, !self.sign_with.yes), + ); Ok(self .locator - .account(&self.source_account)? - .signer(self.hd_path, self.check)?) + .account(account)? + .signer(self.sign_with.hd_path, prompt)?) } pub fn key_pair(&self) -> Result { let key = self.locator.account(&self.source_account)?; - Ok(key.key_pair(self.hd_path)?) + Ok(key.key_pair(self.sign_with.hd_path)?) } pub async fn public_key(&self) -> Result { diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs new file mode 100644 index 000000000..2e50a2bad --- /dev/null +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -0,0 +1,32 @@ +use clap::arg; + +#[derive(thiserror::Error, Debug)] +pub enum Error {} + +#[derive(Debug, clap::Args, Clone, Default)] +#[group(skip)] +pub struct Args { + /// Sign with secret key + #[arg( + long, + conflicts_with = "sign_with_laboratory", + env = "STELLAR_SIGN_WITH_SECRET" + )] + pub sign_with_key: Option, + /// Sign with labratory + #[arg( + long, + visible_alias = "sign-with-lab", + conflicts_with = "sign_with_key", + env = "STELLAR_SIGN_WITH_LABRATORY" + )] + pub sign_with_laboratory: bool, + + #[arg(long, conflicts_with = "sign_with_laboratory")] + /// If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` + pub hd_path: Option, + + /// If `--sign-with-*` is used this will remove requirement of being prompted + #[arg(long)] + pub yes: bool, +} From 87a85dd9918ef0d87cbed162f76a421327398b6c Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 29 Jul 2024 09:20:12 -0400 Subject: [PATCH 32/50] feat: simplify `tx sign` to remove source account --- FULL_HELP_DOCS.md | 125 +++++++++--------- cmd/crates/soroban-test/src/lib.rs | 20 +-- .../src/commands/contract/deploy/wasm.rs | 13 +- .../src/commands/contract/extend.rs | 5 +- .../src/commands/contract/invoke.rs | 11 +- cmd/soroban-cli/src/commands/contract/read.rs | 5 +- .../src/commands/contract/restore.rs | 5 +- cmd/soroban-cli/src/commands/tx/sign.rs | 42 ++---- cmd/soroban-cli/src/config/mod.rs | 82 ++++++------ cmd/soroban-cli/src/config/sign_with.rs | 95 ++++++++++++- 10 files changed, 228 insertions(+), 175 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index db791f9e1..e79a68135 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -113,16 +113,16 @@ Get Id of builtin Soroban Asset Contract. Deprecated, use `stellar contract id a ###### **Options:** * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" -* `--rpc-url ` — RPC server endpoint -* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server -* `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--global` — Use global config -* `--config-dir ` — Location of config directory, default is "." * `--sign-with-key ` — Sign with secret key * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." @@ -135,16 +135,16 @@ Deploy builtin Soroban Asset Contract ###### **Options:** * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" -* `--rpc-url ` — RPC server endpoint -* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server -* `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--global` — Use global config -* `--config-dir ` — Location of config directory, default is "." * `--sign-with-key ` — Sign with secret key * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -273,16 +273,16 @@ If no keys are specified the contract itself is extended. - `temporary`: Temporary -* `--rpc-url ` — RPC server endpoint -* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server -* `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--global` — Use global config -* `--config-dir ` — Location of config directory, default is "." * `--sign-with-key ` — Sign with secret key * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -304,16 +304,16 @@ Deploy a wasm contract * `--wasm ` — WASM file to deploy * `--wasm-hash ` — Hash of the already installed/deployed WASM file * `--salt ` — Custom salt 32-byte salt for the token id -* `--rpc-url ` — RPC server endpoint -* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server -* `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--global` — Use global config -* `--config-dir ` — Location of config directory, default is "." * `--sign-with-key ` — Sign with secret key * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -368,16 +368,16 @@ Deploy builtin Soroban Asset Contract ###### **Options:** * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" -* `--rpc-url ` — RPC server endpoint -* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server -* `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--global` — Use global config -* `--config-dir ` — Location of config directory, default is "." * `--sign-with-key ` — Sign with secret key * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." @@ -390,16 +390,16 @@ Deploy normal Wasm Contract ###### **Options:** * `--salt ` — ID of the Soroban contract -* `--rpc-url ` — RPC server endpoint -* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server -* `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--global` — Use global config -* `--config-dir ` — Location of config directory, default is "." * `--sign-with-key ` — Sign with secret key * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." @@ -460,16 +460,16 @@ Install a WASM file to the ledger without creating a contract instance ###### **Options:** -* `--rpc-url ` — RPC server endpoint -* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server -* `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--global` — Use global config -* `--config-dir ` — Location of config directory, default is "." * `--sign-with-key ` — Sign with secret key * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -502,16 +502,16 @@ stellar contract invoke ... -- --help * `--id ` — Contract ID to invoke * `--is-view` — View the result simulating and do not sign and submit transaction -* `--rpc-url ` — RPC server endpoint -* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server -* `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--global` — Use global config -* `--config-dir ` — Location of config directory, default is "." * `--sign-with-key ` — Sign with secret key * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -573,16 +573,16 @@ Print the current value of a contract-data ledger entry - `temporary`: Temporary -* `--rpc-url ` — RPC server endpoint -* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server -* `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--global` — Use global config -* `--config-dir ` — Location of config directory, default is "." * `--sign-with-key ` — Sign with secret key * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." @@ -613,16 +613,16 @@ If no keys are specificed the contract itself is restored. * `--ledgers-to-extend ` — Number of ledgers to extend the entry * `--ttl-ledger-only` — Only print the new Time To Live ledger -* `--rpc-url ` — RPC server endpoint -* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server -* `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--global` — Use global config -* `--config-dir ` — Location of config directory, default is "." * `--sign-with-key ` — Sign with secret key * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -1054,16 +1054,16 @@ Simulate a transaction envelope from stdin ###### **Options:** -* `--rpc-url ` — RPC server endpoint -* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server -* `--network ` — Name of network to use from config * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--global` — Use global config -* `--config-dir ` — Location of config directory, default is "." * `--sign-with-key ` — Sign with secret key * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." @@ -1085,24 +1085,19 @@ Calculate the hash of a transaction envelope from stdin Sign a transaction -**Usage:** `stellar tx sign [OPTIONS] --source-account ` +**Usage:** `stellar tx sign [OPTIONS]` ###### **Options:** +* `--sign-with-key ` — Sign with secret key +* `--sign-with-laboratory` — Sign with labratory +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--sign-with-key ` — Sign with secret key -* `--sign-with-laboratory` — Sign with labratory -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted -* `--ledgers-from-now ` — Number of ledgers from current ledger before the signed auth entry expires. Default 60 ~ 5 minutes - - Default value: `60` -* `--auth-only` — Only sign the Authorization Entries required by the provided source account diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index d528e565b..6d301bb34 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -222,17 +222,17 @@ impl TestEnv { pub fn clone_config(&self, account: &str) -> config::Args { let config_dir = Some(self.dir().to_path_buf()); config::Args { - network: network::Args { - rpc_url: Some(self.rpc_url.clone()), - network_passphrase: Some(LOCAL_NETWORK_PASSPHRASE.to_string()), - network: None, - }, source_account: account.to_string(), - locator: config::locator::Args { - global: false, - config_dir, - }, sign_with: sign_with::Args { + network: network::Args { + rpc_url: Some(self.rpc_url.clone()), + network_passphrase: Some(LOCAL_NETWORK_PASSPHRASE.to_string()), + network: None, + }, + locator: config::locator::Args { + global: false, + config_dir, + }, yes: true, ..Default::default() }, @@ -248,7 +248,7 @@ impl TestEnv { let config = self.clone_config(account); cmd.run_against_rpc_server( Some(&global::Args { - locator: config.locator.clone(), + locator: config.sign_with.locator.clone(), filter_logs: Vec::default(), quiet: false, verbose: false, diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 9c86336b8..615a06af3 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -117,19 +117,12 @@ pub enum Error { impl Cmd { pub async fn run(&self) -> Result<(), Error> { let res = self.run_against_rpc_server(None, None).await?.to_envelope(); - match res { + match &res { TxnEnvelopeResult::TxnEnvelope(tx) => println!("{}", tx.to_xdr_base64(Limits::none())?), TxnEnvelopeResult::Res(contract) => { - let network = self.config.get_network()?; - - if let Some(alias) = self.alias.clone() { - self.config.locator.save_contract_id( - &network.network_passphrase, - &contract, - &alias, - )?; + if let Some(alias) = self.alias.as_deref() { + self.config.save_contract_id(contract, alias)?; } - println!("{contract}"); } } diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index 5a244b428..f5002e061 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -130,10 +130,7 @@ impl NetworkRunnable for Cmd { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); - let contract = config.locator.resolve_contract_id( - self.key.contract_id.as_ref().unwrap(), - &network.network_passphrase, - )?; + let contract = config.resolve_contract_id(self.key.contract_id.as_ref().unwrap())?; let keys = self.key.parse_keys(contract)?; let network = &config.get_network()?; let client = Client::new(&network.rpc_url)?; diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 6da6b2956..ce9437028 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -236,6 +236,7 @@ impl Cmd { let mut s = val.next().unwrap().to_string_lossy().to_string(); if matches!(input.type_, ScSpecTypeDef::Address) { if let Ok(signer_) = config + .sign_with .locator .account(&s) .and_then(|signer| Ok(signer.signer(config.sign_with.hd_path, false)?)) @@ -320,11 +321,7 @@ impl NetworkRunnable for Cmd { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); - let contract_id = self - .config - .locator - .resolve_contract_id(&self.contract_id, &network.network_passphrase)? - .0; + let contract_id = self.config.resolve_contract_id(&self.contract_id)?.0; let spec_entries = self.spec_entries()?; if let Some(spec_entries) = &spec_entries { // For testing wasm arg parsing @@ -349,8 +346,8 @@ impl NetworkRunnable for Cmd { let spec_entries = get_remote_contract_spec( &contract_id, - &config.locator, - &config.network, + &config.sign_with.locator, + &config.sign_with.network, global_args, Some(config), ) diff --git a/cmd/soroban-cli/src/commands/contract/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs index bef3f3737..b688a28b8 100644 --- a/cmd/soroban-cli/src/commands/contract/read.rs +++ b/cmd/soroban-cli/src/commands/contract/read.rs @@ -186,10 +186,7 @@ impl NetworkRunnable for Cmd { let network = config.get_network()?; tracing::trace!(?network); let client = Client::new(&network.rpc_url)?; - let contract = config.locator.resolve_contract_id( - self.key.contract_id.as_ref().unwrap(), - &network.network_passphrase, - )?; + let contract = config.resolve_contract_id(self.key.contract_id.as_ref().unwrap())?; let keys = self.key.parse_keys(contract)?; Ok(client.get_full_ledger_entries(&keys).await?) } diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs index 7e850e691..378ce791c 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -133,10 +133,7 @@ impl NetworkRunnable for Cmd { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); - let contract = config.locator.resolve_contract_id( - self.key.contract_id.as_ref().unwrap(), - &network.network_passphrase, - )?; + let contract = config.resolve_contract_id(self.key.contract_id.as_ref().unwrap())?; let entry_keys = self.key.parse_keys(contract)?; let client = Client::new(&network.rpc_url)?; let key = config.key_pair()?; diff --git a/cmd/soroban-cli/src/commands/tx/sign.rs b/cmd/soroban-cli/src/commands/tx/sign.rs index 9e882a29b..094d58483 100644 --- a/cmd/soroban-cli/src/commands/tx/sign.rs +++ b/cmd/soroban-cli/src/commands/tx/sign.rs @@ -1,13 +1,14 @@ -use crate::xdr::{self, Limits, Transaction, TransactionEnvelope, WriteXdr}; - -use super::super::config; +use crate::{ + config::sign_with, + xdr::{self, Limits, Transaction, TransactionEnvelope, WriteXdr}, +}; #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] XdrArgs(#[from] super::xdr::Error), #[error(transparent)] - Config(#[from] config::Error), + SignWith(#[from] sign_with::Error), #[error(transparent)] Xdr(#[from] xdr::Error), } @@ -15,41 +16,22 @@ pub enum Error { #[derive(Debug, clap::Parser, Clone)] #[group(skip)] pub struct Cmd { - #[clap(flatten)] - pub config: config::Args, - #[clap(flatten)] - pub ledgers: super::auth::Args, - /// Only sign the Authorization Entries required by the provided source account - #[arg(long)] - pub auth_only: bool, + #[command(flatten)] + pub sign_with: sign_with::Args, } impl Cmd { #[allow(clippy::unused_async)] pub async fn run(&self) -> Result<(), Error> { let txn_env = super::xdr::tx_envelope_from_stdin()?; - let envelope = self.sign_env(txn_env).await?; + let envelope = self + .sign_tx(super::xdr::unwrap_envelope_v1(txn_env)?) + .await?; println!("{}", envelope.to_xdr_base64(Limits::none())?.trim()); Ok(()) } - pub async fn sign(&self, tx: Transaction) -> Result { - if self.auth_only { - Ok(self - .config - .sign_soroban_authorizations(&tx, self.ledgers.from_now) - .await? - .unwrap_or(tx) - .into()) - } else { - Ok(self.config.sign(tx).await?) - } - } - - pub async fn sign_env( - &self, - tx_env: TransactionEnvelope, - ) -> Result { - self.sign(super::xdr::unwrap_envelope_v1(tx_env)?).await + pub async fn sign_tx(&self, tx: Transaction) -> Result { + Ok(self.sign_with.sign(tx).await?) } } diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index da4a16bfd..48859ed0e 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -3,11 +3,10 @@ use std::path::PathBuf; use clap::{arg, command}; use secret::StellarSigner; use serde::{Deserialize, Serialize}; -use stellar_strkey::ed25519::PublicKey; -use crate::signer; +use crate::signer::Stellar; use crate::xdr::{Transaction, TransactionEnvelope}; -use crate::{signer::Stellar, Pwd}; +use crate::Pwd; use self::network::Network; @@ -21,30 +20,22 @@ pub mod sign_with; #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] - Network(#[from] network::Error), + SignWith(#[from] sign_with::Error), #[error(transparent)] Secret(#[from] secret::Error), #[error(transparent)] - Config(#[from] locator::Error), - #[error(transparent)] - Rpc(#[from] soroban_rpc::Error), + Network(#[from] network::Error), #[error(transparent)] - Signer(#[from] signer::Error), + Locator(#[from] locator::Error), } #[derive(Debug, clap::Args, Clone, Default)] #[group(skip)] pub struct Args { - #[command(flatten)] - pub network: network::Args, - #[arg(long, visible_alias = "source", env = "STELLAR_ACCOUNT")] /// Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). pub source_account: String, - #[command(flatten)] - pub locator: locator::Args, - #[command(flatten)] pub sign_with: sign_with::Args, } @@ -56,32 +47,23 @@ impl Args { |s| (s, !self.sign_with.yes), ); Ok(self + .sign_with .locator .account(account)? .signer(self.sign_with.hd_path, prompt)?) } - pub fn key_pair(&self) -> Result { - let key = self.locator.account(&self.source_account)?; - Ok(key.key_pair(self.sign_with.hd_path)?) + pub async fn public_key(&self) -> Result { + Ok(self.sign_with.public_key().await?) } - pub async fn public_key(&self) -> Result { - Ok(self.signer()?.get_public_key().await?) + pub fn key_pair(&self) -> Result { + let key = self.sign_with.locator.account(&self.source_account)?; + Ok(key.key_pair(self.sign_with.hd_path)?) } pub async fn sign(&self, tx: Transaction) -> Result { - let signer = self.signer()?; - self.sign_with_signer(&signer, tx).await - } - - pub async fn sign_with_signer( - &self, - signer: &(impl Stellar + std::marker::Sync), - tx: Transaction, - ) -> Result { - let network = self.get_network()?; - Ok(signer.sign_txn(tx, &network).await?) + Ok(self.sign_with.sign(tx).await?) } pub async fn sign_soroban_authorizations( @@ -89,35 +71,55 @@ impl Args { tx: &Transaction, ledgers_from_current: u32, ) -> Result, Error> { - self.sign_soroban_authorizations_with_signer(&self.signer()?, tx, ledgers_from_current) - .await + Ok(self + .sign_with + .sign_soroban_authorizations(tx, ledgers_from_current) + .await?) } + pub async fn sign_soroban_authorizations_with_signer( &self, signer: &(impl Stellar + std::marker::Sync), tx: &Transaction, ledgers_from_current: u32, ) -> Result, Error> { - let network = self.get_network()?; - let client = crate::rpc::Client::new(&network.rpc_url)?; - let expiration_ledger = client.get_latest_ledger().await?.sequence + ledgers_from_current; - Ok(signer - .sign_soroban_authorizations(tx, &network, expiration_ledger) + Ok(self + .sign_with + .sign_soroban_authorizations_with_signer(signer, tx, ledgers_from_current) .await?) } pub fn get_network(&self) -> Result { - Ok(self.network.get(&self.locator)?) + Ok(self.sign_with.get_network()?) } pub fn config_dir(&self) -> Result { - Ok(self.locator.config_dir()?) + Ok(self.sign_with.config_dir()?) + } + + pub fn resolve_contract_id( + &self, + contract_id: &str, + ) -> Result { + Ok(self + .sign_with + .locator + .resolve_contract_id(contract_id, &self.get_network()?.network_passphrase)?) + } + + pub fn save_contract_id(&self, contract_id: &str, alias: &str) -> Result<(), Error> { + self.sign_with.locator.save_contract_id( + &self.get_network()?.network_passphrase, + contract_id, + alias, + )?; + Ok(()) } } impl Pwd for Args { fn set_pwd(&mut self, pwd: &std::path::Path) { - self.locator.set_pwd(pwd); + self.sign_with.locator.set_pwd(pwd); } } diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index 2e50a2bad..54b17f4bc 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -1,7 +1,35 @@ +use std::path::PathBuf; + use clap::arg; +use stellar_strkey::ed25519::PublicKey; + +use super::network::{self, Network}; +use super::{ + locator, + secret::{self, StellarSigner}, +}; +use crate::{ + signer::{self, Stellar}, + xdr::{Transaction, TransactionEnvelope}, +}; #[derive(thiserror::Error, Debug)] -pub enum Error {} +pub enum Error { + #[error(transparent)] + Network(#[from] network::Error), + #[error(transparent)] + Signer(#[from] signer::Error), + #[error(transparent)] + Secret(#[from] secret::Error), + + #[error(transparent)] + Locator(#[from] locator::Error), + #[error(transparent)] + Rpc(#[from] soroban_rpc::Error), + + #[error("No sign with key provided")] + NoSignWithKey, +} #[derive(Debug, clap::Args, Clone, Default)] #[group(skip)] @@ -29,4 +57,69 @@ pub struct Args { /// If `--sign-with-*` is used this will remove requirement of being prompted #[arg(long)] pub yes: bool, + + #[command(flatten)] + pub network: network::Args, + + #[command(flatten)] + pub locator: locator::Args, +} + +impl Args { + pub fn signer(&self) -> Result { + let account = self.sign_with_key.as_deref().ok_or(Error::NoSignWithKey)?; + Ok(self + .locator + .account(account)? + .signer(self.hd_path, !self.yes)?) + } + + pub async fn public_key(&self) -> Result { + Ok(self.signer()?.get_public_key().await?) + } + + pub async fn sign(&self, tx: Transaction) -> Result { + let signer = self.signer()?; + self.sign_with_signer(&signer, tx).await + } + + pub async fn sign_with_signer( + &self, + signer: &(impl Stellar + std::marker::Sync), + tx: Transaction, + ) -> Result { + let network = self.get_network()?; + Ok(signer.sign_txn(tx, &network).await?) + } + + pub async fn sign_soroban_authorizations( + &self, + tx: &Transaction, + ledgers_from_current: u32, + ) -> Result, Error> { + self.sign_soroban_authorizations_with_signer(&self.signer()?, tx, ledgers_from_current) + .await + } + + pub async fn sign_soroban_authorizations_with_signer( + &self, + signer: &(impl Stellar + std::marker::Sync), + tx: &Transaction, + ledgers_from_current: u32, + ) -> Result, Error> { + let network = self.get_network()?; + let client = crate::rpc::Client::new(&network.rpc_url)?; + let expiration_ledger = client.get_latest_ledger().await?.sequence + ledgers_from_current; + Ok(signer + .sign_soroban_authorizations(tx, &network, expiration_ledger) + .await?) + } + + pub fn get_network(&self) -> Result { + Ok(self.network.get(&self.locator)?) + } + + pub fn config_dir(&self) -> Result { + Ok(self.locator.config_dir()?) + } } From bec2474b4222ab0035cc8f7f7b2fc733aed70c00 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 29 Jul 2024 09:38:38 -0400 Subject: [PATCH 33/50] feat: add `sign_txn_env` to Signer trait `tx sign` should append a signature to the provided transaction envelope and not throw away the original ones. --- FULL_HELP_DOCS.md | 4 +-- cmd/soroban-cli/src/commands/tx/mod.rs | 3 +-- cmd/soroban-cli/src/commands/tx/sign.rs | 10 +++----- cmd/soroban-cli/src/config/mod.rs | 2 +- cmd/soroban-cli/src/config/sign_with.rs | 18 +++++++++---- cmd/soroban-cli/src/signer.rs | 34 +++++++++++++++++++------ 6 files changed, 47 insertions(+), 24 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index e79a68135..79daa6da4 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -1041,7 +1041,7 @@ Sign, Simulate, and Send transactions * `simulate` — Simulate a transaction envelope from stdin * `hash` — Calculate the hash of a transaction envelope from stdin -* `sign` — Sign a transaction +* `sign` — Sign a transaction envolope appending the signature to the envelope * `send` — Send a transaction envelope to the network @@ -1083,7 +1083,7 @@ Calculate the hash of a transaction envelope from stdin ## `stellar tx sign` -Sign a transaction +Sign a transaction envolope appending the signature to the envelope **Usage:** `stellar tx sign [OPTIONS]` diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index 95d0ecc78..d5d36ead6 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -15,7 +15,7 @@ pub enum Cmd { Simulate(simulate::Cmd), /// Calculate the hash of a transaction envelope from stdin Hash(hash::Cmd), - /// Sign a transaction + /// Sign a transaction envolope appending the signature to the envelope Sign(sign::Cmd), /// Send a transaction envelope to the network Send(send::Cmd), @@ -25,7 +25,6 @@ pub enum Cmd { pub enum Error { #[error(transparent)] Simulate(#[from] simulate::Error), - /// An error during hash calculation #[error(transparent)] Hash(#[from] hash::Error), #[error(transparent)] diff --git a/cmd/soroban-cli/src/commands/tx/sign.rs b/cmd/soroban-cli/src/commands/tx/sign.rs index 094d58483..3f0b90139 100644 --- a/cmd/soroban-cli/src/commands/tx/sign.rs +++ b/cmd/soroban-cli/src/commands/tx/sign.rs @@ -1,6 +1,6 @@ use crate::{ config::sign_with, - xdr::{self, Limits, Transaction, TransactionEnvelope, WriteXdr}, + xdr::{self, Limits, TransactionEnvelope, WriteXdr}, }; #[derive(thiserror::Error, Debug)] @@ -24,14 +24,12 @@ impl Cmd { #[allow(clippy::unused_async)] pub async fn run(&self) -> Result<(), Error> { let txn_env = super::xdr::tx_envelope_from_stdin()?; - let envelope = self - .sign_tx(super::xdr::unwrap_envelope_v1(txn_env)?) - .await?; + let envelope = self.sign_tx_env(txn_env).await?; println!("{}", envelope.to_xdr_base64(Limits::none())?.trim()); Ok(()) } - pub async fn sign_tx(&self, tx: Transaction) -> Result { - Ok(self.sign_with.sign(tx).await?) + pub async fn sign_tx_env(&self, tx: TransactionEnvelope) -> Result { + Ok(self.sign_with.sign_txn_env(tx).await?) } } diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index 48859ed0e..7eb79c7e0 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -63,7 +63,7 @@ impl Args { } pub async fn sign(&self, tx: Transaction) -> Result { - Ok(self.sign_with.sign(tx).await?) + Ok(self.sign_with.sign_txn(tx).await?) } pub async fn sign_soroban_authorizations( diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index 54b17f4bc..31701ecdc 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -78,18 +78,26 @@ impl Args { Ok(self.signer()?.get_public_key().await?) } - pub async fn sign(&self, tx: Transaction) -> Result { + pub async fn sign_txn(&self, tx: Transaction) -> Result { let signer = self.signer()?; - self.sign_with_signer(&signer, tx).await + self.sign_tx_env_with_signer(&signer, tx.into()).await } - pub async fn sign_with_signer( + pub async fn sign_txn_env( + &self, + tx: TransactionEnvelope, + ) -> Result { + let signer = self.signer()?; + self.sign_tx_env_with_signer(&signer, tx).await + } + + pub async fn sign_tx_env_with_signer( &self, signer: &(impl Stellar + std::marker::Sync), - tx: Transaction, + tx_env: TransactionEnvelope, ) -> Result { let network = self.get_network()?; - Ok(signer.sign_txn(tx, &network).await?) + Ok(signer.sign_txn_env(tx_env, &network).await?) } pub async fn sign_soroban_authorizations( diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index 09000ed99..f2ac358f0 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -28,6 +28,8 @@ pub enum Error { Rpc(#[from] crate::rpc::Error), #[error("User cancelled signing, perhaps need to remove --check")] UserCancelledSigning, + #[error("Only Transaction envelope V1 type is supported")] + UnsupportedTransactionEnvelopeType, } fn requires_auth(txn: &Transaction) -> Option { @@ -81,6 +83,26 @@ pub trait Stellar { signature: Signature(tx_signature.try_into()?), }) } + + async fn sign_txn_env( + &self, + txn_env: TransactionEnvelope, + network: &Network, + ) -> Result { + match txn_env { + TransactionEnvelope::Tx(TransactionV1Envelope { tx, signatures }) => { + let decorated_signature = self.sign_txn(&tx, network).await?; + let mut sigs = signatures.to_vec(); + sigs.push(decorated_signature); + Ok(TransactionEnvelope::Tx(TransactionV1Envelope { + tx, + signatures: sigs.try_into()?, + })) + } + _ => Err(Error::UnsupportedTransactionEnvelopeType), + } + } + /// Sign a Stellar transaction with the given source account /// This is a default implementation that signs the transaction hash and returns a decorated signature /// @@ -89,17 +111,13 @@ pub trait Stellar { /// Returns an error if the source account is not found async fn sign_txn( &self, - txn: Transaction, + txn: &Transaction, Network { network_passphrase, .. }: &Network, - ) -> Result { - let hash = transaction_hash(&txn, network_passphrase)?; - let decorated_signature = self.sign_txn_hash(hash).await?; - Ok(TransactionEnvelope::Tx(TransactionV1Envelope { - tx: txn, - signatures: vec![decorated_signature].try_into()?, - })) + ) -> Result { + let hash = transaction_hash(txn, network_passphrase)?; + self.sign_txn_hash(hash).await } /// Sign a Soroban authorization entries for a given transaction and set the expiration ledger From f099cbe261ef311843cbff22fb49a28171a64f84 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 29 Jul 2024 10:50:49 -0400 Subject: [PATCH 34/50] fix: remove key_pair since not all signers will expose the private key --- .../src/commands/contract/deploy/asset.rs | 13 ++++----- .../src/commands/contract/deploy/wasm.rs | 27 +++++++++---------- .../src/commands/contract/extend.rs | 10 +++---- cmd/soroban-cli/src/commands/contract/id.rs | 4 +-- .../src/commands/contract/id/wasm.rs | 12 +++++---- .../src/commands/contract/install.rs | 26 +++++++++--------- cmd/soroban-cli/src/commands/contract/mod.rs | 2 +- .../src/commands/contract/restore.rs | 8 +++--- cmd/soroban-cli/src/config/mod.rs | 5 ---- 9 files changed, 46 insertions(+), 61 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index 29a443d18..f7502a3d4 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -97,13 +97,10 @@ impl NetworkRunnable for Cmd { client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; - let key = config.key_pair()?; - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); + let public_strkey = config.public_key().await?; // TODO: use symbols for the method names (both here and in serve) - let account_details = client.get_account(&public_strkey).await?; + let account_details = client.get_account(&public_strkey.to_string()).await?; let sequence: i64 = account_details.seq_num.into(); let network_passphrase = &network.network_passphrase; let contract_id = contract_id_hash_from_asset(&asset, network_passphrase)?; @@ -113,7 +110,7 @@ impl NetworkRunnable for Cmd { sequence + 1, self.fee.fee, network_passphrase, - &key, + &public_strkey, )?; if self.fee.build_only { return Ok(TxnResult::Txn(tx)); @@ -141,7 +138,7 @@ fn build_wrap_token_tx( sequence: i64, fee: u32, _network_passphrase: &str, - key: &ed25519_dalek::SigningKey, + key: &stellar_strkey::ed25519::PublicKey, ) -> Result { let contract = ScAddress::Contract(contract_id.clone()); let mut read_write = vec![ @@ -180,7 +177,7 @@ fn build_wrap_token_tx( }; Ok(Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), + source_account: MuxedAccount::Ed25519(Uint256(key.0)), fee, seq_num: SequenceNumber(sequence), cond: Preconditions::None, diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 615a06af3..df4be5685 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -195,13 +195,9 @@ impl NetworkRunnable for Cmd { client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; - let key = config.key_pair()?; - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - - let account_details = client.get_account(&public_strkey).await?; + let public_strkey = config.public_key().await?; + let account_details = client.get_account(&public_strkey.to_string()).await?; let sequence: i64 = account_details.seq_num.into(); let (txn, contract_id) = build_create_contract_tx( wasm_hash, @@ -209,7 +205,7 @@ impl NetworkRunnable for Cmd { self.fee.fee, &network.network_passphrase, salt, - &key, + &public_strkey, )?; if self.fee.build_only { return Ok(TxnResult::Txn(txn)); @@ -239,11 +235,9 @@ fn build_create_contract_tx( fee: u32, network_passphrase: &str, salt: [u8; 32], - key: &ed25519_dalek::SigningKey, + key: &stellar_strkey::ed25519::PublicKey, ) -> Result<(Transaction, Hash), Error> { - let source_account = AccountId(PublicKey::PublicKeyTypeEd25519( - key.verifying_key().to_bytes().into(), - )); + let source_account = AccountId(PublicKey::PublicKeyTypeEd25519(key.0.into())); let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress { address: ScAddress::Account(source_account), @@ -262,7 +256,7 @@ fn build_create_contract_tx( }), }; let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), + source_account: MuxedAccount::Ed25519(Uint256(key.0)), fee, seq_num: SequenceNumber(sequence), cond: Preconditions::None, @@ -276,6 +270,7 @@ fn build_create_contract_tx( #[cfg(test)] mod tests { + use super::*; #[test] @@ -290,8 +285,12 @@ mod tests { 1, "Public Global Stellar Network ; September 2015", [0u8; 32], - &utils::parse_secret_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP") - .unwrap(), + &stellar_strkey::ed25519::PublicKey( + utils::parse_secret_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP") + .unwrap() + .verifying_key() + .to_bytes(), + ), ); assert!(result.is_ok()); diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index f5002e061..a9294495f 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -134,17 +134,13 @@ impl NetworkRunnable for Cmd { let keys = self.key.parse_keys(contract)?; let network = &config.get_network()?; let client = Client::new(&network.rpc_url)?; - let key = config.key_pair()?; + let public_key = config.public_key().await?; let extend_to = self.ledgers_to_extend(); - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - let account_details = client.get_account(&public_strkey).await?; + let account_details = client.get_account(&public_key.to_string()).await?; let sequence: i64 = account_details.seq_num.into(); let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), + source_account: MuxedAccount::Ed25519(Uint256(public_key.0)), fee: self.fee.fee, seq_num: SequenceNumber(sequence + 1), cond: Preconditions::None, diff --git a/cmd/soroban-cli/src/commands/contract/id.rs b/cmd/soroban-cli/src/commands/contract/id.rs index bb8744d51..f07fa8df6 100644 --- a/cmd/soroban-cli/src/commands/contract/id.rs +++ b/cmd/soroban-cli/src/commands/contract/id.rs @@ -18,10 +18,10 @@ pub enum Error { } impl Cmd { - pub fn run(&self) -> Result<(), Error> { + pub async fn run(&self) -> Result<(), Error> { match &self { Cmd::Asset(asset) => asset.run()?, - Cmd::Wasm(wasm) => wasm.run()?, + Cmd::Wasm(wasm) => wasm.run().await?, } Ok(()) } diff --git a/cmd/soroban-cli/src/commands/contract/id/wasm.rs b/cmd/soroban-cli/src/commands/contract/id/wasm.rs index 14824b145..66bf24d55 100644 --- a/cmd/soroban-cli/src/commands/contract/id/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/id/wasm.rs @@ -29,13 +29,12 @@ pub enum Error { CannotParseSalt(String), } impl Cmd { - pub fn run(&self) -> Result<(), Error> { + pub async fn run(&self) -> Result<(), Error> { let salt: [u8; 32] = soroban_spec_tools::utils::padded_hex_from_str(&self.salt, 32) .map_err(|_| Error::CannotParseSalt(self.salt.clone()))? .try_into() .map_err(|_| Error::CannotParseSalt(self.salt.clone()))?; - let contract_id_preimage = - contract_preimage(&self.config.key_pair()?.verifying_key(), salt); + let contract_id_preimage = contract_preimage(&self.config.public_key().await?, salt); let contract_id = get_contract_id( contract_id_preimage.clone(), &self.config.get_network()?.network_passphrase, @@ -46,8 +45,11 @@ impl Cmd { } } -pub fn contract_preimage(key: &ed25519_dalek::VerifyingKey, salt: [u8; 32]) -> ContractIdPreimage { - let source_account = AccountId(PublicKey::PublicKeyTypeEd25519(key.to_bytes().into())); +pub fn contract_preimage( + key: &stellar_strkey::ed25519::PublicKey, + salt: [u8; 32], +) -> ContractIdPreimage { + let source_account = AccountId(PublicKey::PublicKeyTypeEd25519(key.0.into())); ContractIdPreimage::Address(ContractIdPreimageFromAddress { address: ScAddress::Account(source_account), salt: Uint256(salt), diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 8188dab7f..b6ae4a0e2 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -118,16 +118,12 @@ impl NetworkRunnable for Cmd { tracing::warn!("the deployed smart contract {path} was built with Soroban Rust SDK v{rs_sdk_ver}, a release candidate version not intended for use with the Stellar Public Network", path = self.wasm.wasm.display()); } } - let key = config.key_pair()?; - + let public_strkey = config.public_key().await?; // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - let account_details = client.get_account(&public_strkey).await?; + let account_details = client.get_account(&public_strkey.to_string()).await?; let sequence: i64 = account_details.seq_num.into(); - let (tx_without_preflight, hash) = - build_install_contract_code_tx(&contract, sequence + 1, self.fee.fee, &key)?; + build_install_contract_code_tx(&contract, sequence + 1, self.fee.fee, &public_strkey)?; if self.fee.build_only { return Ok(TxnResult::Txn(tx_without_preflight)); @@ -231,14 +227,12 @@ pub(crate) fn build_install_contract_code_tx( source_code: &[u8], sequence: i64, fee: u32, - key: &ed25519_dalek::SigningKey, + key: &stellar_strkey::ed25519::PublicKey, ) -> Result<(Transaction, Hash), XdrError> { let hash = utils::contract_hash(source_code)?; let op = Operation { - source_account: Some(MuxedAccount::Ed25519(Uint256( - key.verifying_key().to_bytes(), - ))), + source_account: Some(MuxedAccount::Ed25519(Uint256(key.0))), body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { host_function: HostFunction::UploadContractWasm(source_code.try_into()?), auth: VecM::default(), @@ -246,7 +240,7 @@ pub(crate) fn build_install_contract_code_tx( }; let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), + source_account: MuxedAccount::Ed25519(Uint256(key.0)), fee, seq_num: SequenceNumber(sequence), cond: Preconditions::None, @@ -268,8 +262,12 @@ mod tests { b"foo", 300, 1, - &utils::parse_secret_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP") - .unwrap(), + &stellar_strkey::ed25519::PublicKey( + utils::parse_secret_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP") + .unwrap() + .verifying_key() + .to_bytes(), + ), ); assert!(result.is_ok()); diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs index f83cad37f..486bc6002 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -125,7 +125,7 @@ impl Cmd { Cmd::Build(build) => build.run()?, Cmd::Extend(extend) => extend.run().await?, Cmd::Deploy(deploy) => deploy.run().await?, - Cmd::Id(id) => id.run()?, + Cmd::Id(id) => id.run().await?, Cmd::Init(init) => init.run()?, Cmd::Inspect(inspect) => inspect.run()?, Cmd::Install(install) => install.run().await?, diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs index 378ce791c..2022eee67 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -136,16 +136,14 @@ impl NetworkRunnable for Cmd { let contract = config.resolve_contract_id(self.key.contract_id.as_ref().unwrap())?; let entry_keys = self.key.parse_keys(contract)?; let client = Client::new(&network.rpc_url)?; - let key = config.key_pair()?; // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - let account_details = client.get_account(&public_strkey).await?; + let public_strkey = config.public_key().await?; + let account_details = client.get_account(&public_strkey.to_string()).await?; let sequence: i64 = account_details.seq_num.into(); let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), + source_account: MuxedAccount::Ed25519(Uint256(public_strkey.0)), fee: self.fee.fee, seq_num: SequenceNumber(sequence + 1), cond: Preconditions::None, diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index 7eb79c7e0..7735c830e 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -57,11 +57,6 @@ impl Args { Ok(self.sign_with.public_key().await?) } - pub fn key_pair(&self) -> Result { - let key = self.sign_with.locator.account(&self.source_account)?; - Ok(key.key_pair(self.sign_with.hd_path)?) - } - pub async fn sign(&self, tx: Transaction) -> Result { Ok(self.sign_with.sign_txn(tx).await?) } From 57ddbe1a8323551120075ef89bca7da02aa1f75d Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 29 Jul 2024 11:51:53 -0400 Subject: [PATCH 35/50] fix: use signer with source account --- cmd/soroban-cli/src/config/mod.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index 7735c830e..54e3b63cd 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -4,7 +4,7 @@ use clap::{arg, command}; use secret::StellarSigner; use serde::{Deserialize, Serialize}; -use crate::signer::Stellar; +use crate::signer::{self, Stellar}; use crate::xdr::{Transaction, TransactionEnvelope}; use crate::Pwd; @@ -27,6 +27,8 @@ pub enum Error { Network(#[from] network::Error), #[error(transparent)] Locator(#[from] locator::Error), + #[error(transparent)] + Signer(#[from] signer::Error), } #[derive(Debug, clap::Args, Clone, Default)] @@ -54,21 +56,13 @@ impl Args { } pub async fn public_key(&self) -> Result { - Ok(self.sign_with.public_key().await?) + Ok(self.signer()?.get_public_key().await?) } pub async fn sign(&self, tx: Transaction) -> Result { - Ok(self.sign_with.sign_txn(tx).await?) - } - - pub async fn sign_soroban_authorizations( - &self, - tx: &Transaction, - ledgers_from_current: u32, - ) -> Result, Error> { Ok(self .sign_with - .sign_soroban_authorizations(tx, ledgers_from_current) + .sign_tx_env_with_signer(&self.signer()?, tx.into()) .await?) } From de926b81449dedac116401dfd19b6adf2926fe18 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 29 Jul 2024 20:31:08 -0400 Subject: [PATCH 36/50] fix: remove auth test and update --sign-with-key description --- FULL_HELP_DOCS.md | 24 ++-- cmd/crates/soroban-test/src/lib.rs | 1 + .../soroban-test/tests/it/integration/tx.rs | 111 +----------------- cmd/soroban-cli/src/config/sign_with.rs | 2 +- 4 files changed, 15 insertions(+), 123 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 79daa6da4..4176c6047 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -114,7 +114,7 @@ Get Id of builtin Soroban Asset Contract. Deprecated, use `stellar contract id a * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-key ` — Sign with secret key +* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -136,7 +136,7 @@ Deploy builtin Soroban Asset Contract * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-key ` — Sign with secret key +* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -274,7 +274,7 @@ If no keys are specified the contract itself is extended. Temporary * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-key ` — Sign with secret key +* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -305,7 +305,7 @@ Deploy a wasm contract * `--wasm-hash ` — Hash of the already installed/deployed WASM file * `--salt ` — Custom salt 32-byte salt for the token id * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-key ` — Sign with secret key +* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -369,7 +369,7 @@ Deploy builtin Soroban Asset Contract * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-key ` — Sign with secret key +* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -391,7 +391,7 @@ Deploy normal Wasm Contract * `--salt ` — ID of the Soroban contract * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-key ` — Sign with secret key +* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -461,7 +461,7 @@ Install a WASM file to the ledger without creating a contract instance ###### **Options:** * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-key ` — Sign with secret key +* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -503,7 +503,7 @@ stellar contract invoke ... -- --help * `--id ` — Contract ID to invoke * `--is-view` — View the result simulating and do not sign and submit transaction * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-key ` — Sign with secret key +* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -574,7 +574,7 @@ Print the current value of a contract-data ledger entry Temporary * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-key ` — Sign with secret key +* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -614,7 +614,7 @@ If no keys are specificed the contract itself is restored. * `--ledgers-to-extend ` — Number of ledgers to extend the entry * `--ttl-ledger-only` — Only print the new Time To Live ledger * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-key ` — Sign with secret key +* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -1055,7 +1055,7 @@ Simulate a transaction envelope from stdin ###### **Options:** * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-key ` — Sign with secret key +* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted @@ -1089,7 +1089,7 @@ Sign a transaction envolope appending the signature to the envelope ###### **Options:** -* `--sign-with-key ` — Sign with secret key +* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index 6d301bb34..6234e5de8 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -246,6 +246,7 @@ impl TestEnv { account: &str, ) -> Result { let config = self.clone_config(account); + println!("{config:#?}"); cmd.run_against_rpc_server( Some(&global::Args { locator: config.sign_with.locator.clone(), diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 2510ba420..de7e5cc6a 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -85,7 +85,7 @@ fn sign_manually(sandbox: &TestEnv, tx_env: &TransactionEnvelope) -> Transaction sandbox .new_assert_cmd("tx") .arg("sign") - .arg("--source=test") + .arg("--sign-with-key=test") .write_stdin(tx_env.to_xdr_base64(Limits::none()).unwrap().as_bytes()) .assert() .success() @@ -94,112 +94,3 @@ fn sign_manually(sandbox: &TestEnv, tx_env: &TransactionEnvelope) -> Transaction ) .unwrap() } - -#[tokio::test] -async fn sign() { - let sandbox = &TestEnv::new(); - let id = &deploy_hello(sandbox).await; - // Create new test_other account - sandbox - .new_assert_cmd("keys") - .arg("generate") - .arg("test_other") - .assert(); - - // Get Xdr for transaction where auth is required for test_other - let xdr_base64 = sandbox - .new_assert_cmd("contract") - .arg("invoke") - .arg("--id") - .arg(id) - .arg("--sim-only") - .arg("--") - .arg("auth") - .arg("--world=world") - .arg("--addr=test_other") - .assert() - .success() - .stdout_as_str(); - // Sign the transaction's auth entry with test_other - let xdr_base64 = sandbox - .new_assert_cmd("tx") - .arg("sign") - .arg("--auth") - .arg("--source=test_other") - .write_stdin(xdr_base64.as_bytes()) - .assert() - .success() - .stdout_as_str(); - // Sign the transaction with test as source account - let xdr_base64 = sandbox - .new_assert_cmd("tx") - .arg("sign") - .write_stdin(xdr_base64.as_bytes()) - .assert() - .success() - .stdout_as_str(); - // Send transaction - sandbox - .new_assert_cmd("tx") - .arg("send") - .write_stdin(xdr_base64.as_bytes()) - .assert() - .success() - .stdout(predicates::str::contains(r#""status": "SUCCESS""#)); -} - -#[tokio::test] -async fn expired_auth_entry() { - let sandbox = &TestEnv::new(); - let id = &deploy_hello(sandbox).await; - // Create new test_other account - sandbox - .new_assert_cmd("keys") - .arg("generate") - .arg("test_other") - .assert(); - - // Get Xdr for transaction where auth is required for test_other - let xdr_base64 = sandbox - .new_assert_cmd("contract") - .arg("invoke") - .arg("--id") - .arg(id) - .arg("--sim-only") - .arg("--") - .arg("auth") - .arg("--world=world") - .arg("--addr=test_other") - .assert() - .success() - .stdout_as_str(); - // Sign the transaction's auth entry with test_other - let xdr_base64 = sandbox - .new_assert_cmd("tx") - .arg("sign") - .arg("--auth") - .arg("--source=test_other") - .arg("--ledgers=1") - .write_stdin(xdr_base64.as_bytes()) - .assert() - .success() - .stdout_as_str(); - tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; - // Sign the transaction with test as source account - let xdr_base64 = sandbox - .new_assert_cmd("tx") - .arg("sign") - .write_stdin(xdr_base64.as_bytes()) - .assert() - .success() - .stdout_as_str(); - // Send transaction - sandbox - .new_assert_cmd("tx") - .arg("send") - .write_stdin(xdr_base64.as_bytes()) - .assert() - .failure() - .stderr(predicates::str::contains(r#""FAILED""#)) - .stderr(predicates::str::contains("signature has expired")); -} diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index 31701ecdc..db7b65fad 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -34,7 +34,7 @@ pub enum Error { #[derive(Debug, clap::Args, Clone, Default)] #[group(skip)] pub struct Args { - /// Sign with secret key + /// Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). #[arg( long, conflicts_with = "sign_with_laboratory", From 8cf9c9218f675b81adf2b85aee8f54220b5ab0d9 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 30 Jul 2024 10:29:11 -0400 Subject: [PATCH 37/50] feat: `auth_expires_in_ledgers` & `auth_expires_at_ledger` in auth::Args --- FULL_HELP_DOCS.md | 3 +- .../src/commands/contract/invoke.rs | 7 ++-- cmd/soroban-cli/src/commands/tx/auth.rs | 28 +++++++++++---- cmd/soroban-cli/src/config/mod.rs | 12 ------- cmd/soroban-cli/src/config/sign_with.rs | 35 ++++++------------- 5 files changed, 38 insertions(+), 47 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 4176c6047..977192715 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -519,9 +519,10 @@ stellar contract invoke ... -- --help * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout * `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout -* `--ledgers-from-now ` — Number of ledgers from current ledger before the signed auth entry expires. Default 60 ~ 5 minutes +* `--auth-expires-in-ledgers ` — Number of ledgers from current ledger before the signed auth entry expires. Default 60 ~ 5 minutes Default value: `60` +* `--auth-expires-at-ledger ` — Ledger number when signed auth entry expires diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index ce9437028..41095b1bf 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -55,7 +55,7 @@ pub struct Cmd { #[command(flatten)] pub fee: crate::fee::Args, #[command(flatten)] - pub ledgers: crate::commands::tx::auth::Args, + pub auth: crate::commands::tx::auth::Args, } impl FromStr for Cmd { @@ -385,9 +385,10 @@ impl NetworkRunnable for Cmd { let mut txn = txn.transaction().clone(); // let auth = auth_entries(&txn); // crate::log::auth(&[auth]); + let expriation_ledger = self.auth.expiration_ledger(&client).await?; for signer in &signers { - if let Some(tx) = config - .sign_soroban_authorizations_with_signer(signer, &txn, self.ledgers.from_now) + if let Some(tx) = signer + .sign_soroban_authorizations(&txn, &network, expriation_ledger) .await? { txn = tx; diff --git a/cmd/soroban-cli/src/commands/tx/auth.rs b/cmd/soroban-cli/src/commands/tx/auth.rs index cf91c2991..7c8989a1c 100644 --- a/cmd/soroban-cli/src/commands/tx/auth.rs +++ b/cmd/soroban-cli/src/commands/tx/auth.rs @@ -1,19 +1,33 @@ use clap::arg; +use crate::rpc::{self, Client}; + #[derive(Debug, clap::Args, Clone)] #[group(skip)] pub struct Args { /// Number of ledgers from current ledger before the signed auth entry expires. Default 60 ~ 5 minutes. - #[arg( - long = "ledgers-from-now", - visible_alias = "ledgers", - default_value = "60" - )] - pub from_now: u32, + #[arg(long, default_value = "60")] + pub auth_expires_in_ledgers: u32, + /// Ledger number when signed auth entry expires. + #[arg(long, conflicts_with = "auth_expires_in_ledgers")] + pub auth_expires_at_ledger: Option, +} + +impl Args { + pub async fn expiration_ledger(&self, client: &Client) -> Result { + if let Some(ledger) = self.auth_expires_at_ledger { + return Ok(ledger); + } + let current_ledger = client.get_latest_ledger().await?.sequence; + Ok(current_ledger + self.auth_expires_in_ledgers) + } } impl Default for Args { fn default() -> Self { - Self { from_now: 60 } + Self { + auth_expires_in_ledgers: 60, + auth_expires_at_ledger: None, + } } } diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index 54e3b63cd..5d9d2c765 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -66,18 +66,6 @@ impl Args { .await?) } - pub async fn sign_soroban_authorizations_with_signer( - &self, - signer: &(impl Stellar + std::marker::Sync), - tx: &Transaction, - ledgers_from_current: u32, - ) -> Result, Error> { - Ok(self - .sign_with - .sign_soroban_authorizations_with_signer(signer, tx, ledgers_from_current) - .await?) - } - pub fn get_network(&self) -> Result { Ok(self.sign_with.get_network()?) } diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index db7b65fad..bd449adbd 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -1,17 +1,16 @@ use std::path::PathBuf; +use crate::{ + signer::{self, Stellar}, + xdr::{Transaction, TransactionEnvelope}, +}; use clap::arg; -use stellar_strkey::ed25519::PublicKey; -use super::network::{self, Network}; use super::{ locator, + network::{self, Network}, secret::{self, StellarSigner}, }; -use crate::{ - signer::{self, Stellar}, - xdr::{Transaction, TransactionEnvelope}, -}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -21,12 +20,10 @@ pub enum Error { Signer(#[from] signer::Error), #[error(transparent)] Secret(#[from] secret::Error), - #[error(transparent)] Locator(#[from] locator::Error), #[error(transparent)] Rpc(#[from] soroban_rpc::Error), - #[error("No sign with key provided")] NoSignWithKey, } @@ -74,15 +71,6 @@ impl Args { .signer(self.hd_path, !self.yes)?) } - pub async fn public_key(&self) -> Result { - Ok(self.signer()?.get_public_key().await?) - } - - pub async fn sign_txn(&self, tx: Transaction) -> Result { - let signer = self.signer()?; - self.sign_tx_env_with_signer(&signer, tx.into()).await - } - pub async fn sign_txn_env( &self, tx: TransactionEnvelope, @@ -105,21 +93,20 @@ impl Args { tx: &Transaction, ledgers_from_current: u32, ) -> Result, Error> { - self.sign_soroban_authorizations_with_signer(&self.signer()?, tx, ledgers_from_current) - .await + Ok(self + .signer()? + .sign_soroban_authorizations(tx, &self.get_network()?, ledgers_from_current) + .await?) } pub async fn sign_soroban_authorizations_with_signer( &self, signer: &(impl Stellar + std::marker::Sync), tx: &Transaction, - ledgers_from_current: u32, + expiration_ledger: u32, ) -> Result, Error> { - let network = self.get_network()?; - let client = crate::rpc::Client::new(&network.rpc_url)?; - let expiration_ledger = client.get_latest_ledger().await?.sequence + ledgers_from_current; Ok(signer - .sign_soroban_authorizations(tx, &network, expiration_ledger) + .sign_soroban_authorizations(tx, &self.get_network()?, expiration_ledger) .await?) } From 2277384c059c94390803769fbbde276bd071ef05 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 30 Jul 2024 10:55:36 -0400 Subject: [PATCH 38/50] fix: --yes in tests and improve its docs --- FULL_HELP_DOCS.md | 24 +++++++++---------- .../soroban-test/tests/it/integration/tx.rs | 1 + cmd/soroban-cli/src/config/sign_with.rs | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 977192715..55c29a5dd 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -117,7 +117,7 @@ Get Id of builtin Soroban Asset Contract. Deprecated, use `stellar contract id a * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -139,7 +139,7 @@ Deploy builtin Soroban Asset Contract * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -277,7 +277,7 @@ If no keys are specified the contract itself is extended. * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -308,7 +308,7 @@ Deploy a wasm contract * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -372,7 +372,7 @@ Deploy builtin Soroban Asset Contract * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -394,7 +394,7 @@ Deploy normal Wasm Contract * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -464,7 +464,7 @@ Install a WASM file to the ledger without creating a contract instance * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -506,7 +506,7 @@ stellar contract invoke ... -- --help * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -578,7 +578,7 @@ Print the current value of a contract-data ledger entry * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -618,7 +618,7 @@ If no keys are specificed the contract itself is restored. * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -1059,7 +1059,7 @@ Simulate a transaction envelope from stdin * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -1093,7 +1093,7 @@ Sign a transaction envolope appending the signature to the envelope * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted +* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index de7e5cc6a..b877536b1 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -86,6 +86,7 @@ fn sign_manually(sandbox: &TestEnv, tx_env: &TransactionEnvelope) -> Transaction .new_assert_cmd("tx") .arg("sign") .arg("--sign-with-key=test") + .arg("--yes") .write_stdin(tx_env.to_xdr_base64(Limits::none()).unwrap().as_bytes()) .assert() .success() diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index bd449adbd..f79499c1e 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -51,7 +51,7 @@ pub struct Args { /// If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` pub hd_path: Option, - /// If `--sign-with-*` is used this will remove requirement of being prompted + /// If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions #[arg(long)] pub yes: bool, From 9c02bcf089dbebab8a8bd7a98517301c2215781e Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 30 Jul 2024 10:57:24 -0400 Subject: [PATCH 39/50] chore: add print for testing --- cmd/crates/soroban-test/src/lib.rs | 1 - cmd/soroban-cli/src/config/network.rs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index 6234e5de8..6d301bb34 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -246,7 +246,6 @@ impl TestEnv { account: &str, ) -> Result { let config = self.clone_config(account); - println!("{config:#?}"); cmd.run_against_rpc_server( Some(&global::Args { locator: config.sign_with.locator.clone(), diff --git a/cmd/soroban-cli/src/config/network.rs b/cmd/soroban-cli/src/config/network.rs index d332fa904..924060db9 100644 --- a/cmd/soroban-cli/src/config/network.rs +++ b/cmd/soroban-cli/src/config/network.rs @@ -68,6 +68,7 @@ pub struct Args { impl Args { pub fn get(&self, locator: &locator::Args) -> Result { + println!("{self:#?}"); if let Some(name) = self.network.as_deref() { if let Ok(network) = locator.read_network(name) { return Ok(network); From 2856eab8080879ddf91d876bd32482dfaaabce35 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 30 Jul 2024 11:22:55 -0400 Subject: [PATCH 40/50] fix: improve arg name and don't use `self.config` --- cmd/soroban-cli/src/commands/contract/invoke.rs | 6 +++++- cmd/soroban-cli/src/config/network.rs | 1 - cmd/soroban-cli/src/config/sign_with.rs | 5 +++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 41095b1bf..c05639f99 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -319,9 +319,13 @@ impl NetworkRunnable for Cmd { config: Option<&config::Args>, ) -> Result, Error> { let config = config.unwrap_or(&self.config); + eprintln!( + "Invoking contract {} on config {config:#?}...", + self.contract_id + ); let network = config.get_network()?; tracing::trace!(?network); - let contract_id = self.config.resolve_contract_id(&self.contract_id)?.0; + let contract_id = config.resolve_contract_id(&self.contract_id)?.0; let spec_entries = self.spec_entries()?; if let Some(spec_entries) = &spec_entries { // For testing wasm arg parsing diff --git a/cmd/soroban-cli/src/config/network.rs b/cmd/soroban-cli/src/config/network.rs index 924060db9..d332fa904 100644 --- a/cmd/soroban-cli/src/config/network.rs +++ b/cmd/soroban-cli/src/config/network.rs @@ -68,7 +68,6 @@ pub struct Args { impl Args { pub fn get(&self, locator: &locator::Args) -> Result { - println!("{self:#?}"); if let Some(name) = self.network.as_deref() { if let Ok(network) = locator.read_network(name) { return Ok(network); diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index f79499c1e..3efb261aa 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -91,11 +91,11 @@ impl Args { pub async fn sign_soroban_authorizations( &self, tx: &Transaction, - ledgers_from_current: u32, + expiration_ledger: u32, ) -> Result, Error> { Ok(self .signer()? - .sign_soroban_authorizations(tx, &self.get_network()?, ledgers_from_current) + .sign_soroban_authorizations(tx, &self.get_network()?, expiration_ledger) .await?) } @@ -111,6 +111,7 @@ impl Args { } pub fn get_network(&self) -> Result { + eprintln!("{self:#?}"); Ok(self.network.get(&self.locator)?) } From c84f8c2ceea8832a70ff353ff0988b589cd14001 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 30 Jul 2024 11:36:53 -0400 Subject: [PATCH 41/50] fix: use better print --- cmd/crates/soroban-test/src/lib.rs | 1 + cmd/soroban-cli/src/commands/contract/invoke.rs | 4 ---- cmd/soroban-cli/src/config/sign_with.rs | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index 6d301bb34..44e14204e 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -246,6 +246,7 @@ impl TestEnv { account: &str, ) -> Result { let config = self.clone_config(account); + eprintln!("Running with config: {config:#?}"); cmd.run_against_rpc_server( Some(&global::Args { locator: config.sign_with.locator.clone(), diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index c05639f99..1a096bf91 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -319,10 +319,6 @@ impl NetworkRunnable for Cmd { config: Option<&config::Args>, ) -> Result, Error> { let config = config.unwrap_or(&self.config); - eprintln!( - "Invoking contract {} on config {config:#?}...", - self.contract_id - ); let network = config.get_network()?; tracing::trace!(?network); let contract_id = config.resolve_contract_id(&self.contract_id)?.0; diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index 3efb261aa..398c92606 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -111,7 +111,6 @@ impl Args { } pub fn get_network(&self) -> Result { - eprintln!("{self:#?}"); Ok(self.network.get(&self.locator)?) } From ea40e248da3199f25ce89a8a021aed0ea9d42f4c Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 30 Jul 2024 11:58:00 -0400 Subject: [PATCH 42/50] fix: revert enum to Secret --- .../tests/it/integration/hello_world.rs | 6 ++-- cmd/crates/soroban-test/tests/it/util.rs | 6 ++-- cmd/soroban-cli/src/commands/keys/generate.rs | 6 ++-- cmd/soroban-cli/src/config/locator.rs | 10 +++--- cmd/soroban-cli/src/config/secret.rs | 34 +++++++++---------- 5 files changed, 31 insertions(+), 31 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 74d5f30ef..3a8826a51 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -69,8 +69,8 @@ async fn invoke() { .stdout_as_str(); let dir = sandbox.dir(); let seed_phrase = std::fs::read_to_string(dir.join(".soroban/identity/test.toml")).unwrap(); - let s = toml::from_str::(&seed_phrase).unwrap(); - let secret::SignerKind::SeedPhrase { seed_phrase } = s else { + let s = toml::from_str::(&seed_phrase).unwrap(); + let secret::Secret::SeedPhrase { seed_phrase } = s else { panic!("Expected seed phrase") }; let id = &deploy_hello(sandbox).await; @@ -102,7 +102,7 @@ async fn invoke() { config_locator .write_identity( "testone", - &secret::SignerKind::SecretKey { + &secret::Secret::SecretKey { secret_key: secret_key_1.clone(), }, ) diff --git a/cmd/crates/soroban-test/tests/it/util.rs b/cmd/crates/soroban-test/tests/it/util.rs index a3af56d80..f424ea1ae 100644 --- a/cmd/crates/soroban-test/tests/it/util.rs +++ b/cmd/crates/soroban-test/tests/it/util.rs @@ -2,7 +2,7 @@ use std::path::Path; use soroban_cli::{ commands::contract, - config::{locator::KeyType, secret::SignerKind}, + config::{locator::KeyType, secret::Secret}, }; use soroban_test::{TestEnv, Wasm, TEST_ACCOUNT}; @@ -17,10 +17,10 @@ pub enum SecretKind { #[allow(clippy::needless_pass_by_value)] pub fn add_key(dir: &Path, name: &str, kind: SecretKind, data: &str) { let secret = match kind { - SecretKind::Seed => SignerKind::SeedPhrase { + SecretKind::Seed => Secret::SeedPhrase { seed_phrase: data.to_string(), }, - SecretKind::Key => SignerKind::SecretKey { + SecretKind::Key => Secret::SecretKey { secret_key: data.to_string(), }, }; diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs index 43456bf9f..cd4071de4 100644 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ b/cmd/soroban-cli/src/commands/keys/generate.rs @@ -2,7 +2,7 @@ use clap::{arg, command}; use super::super::config::{ locator, network, - secret::{self, SignerKind}, + secret::{self, Secret}, }; #[derive(thiserror::Error, Debug)] @@ -51,9 +51,9 @@ pub struct Cmd { impl Cmd { pub async fn run(&self) -> Result<(), Error> { let seed_phrase = if self.default_seed { - SignerKind::test_seed_phrase() + Secret::test_seed_phrase() } else { - SignerKind::from_seed(self.seed.as_deref()) + Secret::from_seed(self.seed.as_deref()) }?; let secret = if self.as_secret { seed_phrase.private_key(self.hd_path)?.into() diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 272fe8953..77e5c9323 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -17,7 +17,7 @@ use crate::{utils::find_config_dir, Pwd}; use super::{ alias, network::{self, Network}, - secret::SignerKind, + secret::Secret, }; #[derive(thiserror::Error, Debug)] @@ -149,7 +149,7 @@ impl Args { ) } - pub fn write_identity(&self, name: &str, secret: &SignerKind) -> Result<(), Error> { + pub fn write_identity(&self, name: &str, secret: &Secret) -> Result<(), Error> { KeyType::Identity.write(name, secret, &self.config_dir()?) } @@ -207,12 +207,12 @@ impl Args { Ok(saved_networks.chain(default_networks).collect()) } - pub fn read_identity(&self, name: &str) -> Result { + pub fn read_identity(&self, name: &str) -> Result { KeyType::Identity.read_with_global(name, &self.local_config()?) } - pub fn account(&self, account_str: &str) -> Result { - if let Ok(signer) = account_str.parse::() { + pub fn account(&self, account_str: &str) -> Result { + if let Ok(signer) = account_str.parse::() { Ok(signer) } else { self.read_identity(account_str) diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index d8c4bfba1..c74a828f2 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -43,16 +43,16 @@ pub struct Args { } impl Args { - pub fn kind(&self) -> Result { + pub fn kind(&self) -> Result { if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") { - Ok(SignerKind::SecretKey { secret_key }) + Ok(Secret::SecretKey { secret_key }) } else if self.secret_key { println!("Type a secret key: "); let secret_key = read_password()?; let secret_key = PrivateKey::from_string(&secret_key) .map_err(|_| Error::InvalidSecretKey)? .to_string(); - Ok(SignerKind::SecretKey { secret_key }) + Ok(Secret::SecretKey { secret_key }) } else if self.seed_phrase { println!("Type a 12 word seed phrase: "); let seed_phrase = read_password()?; @@ -61,7 +61,7 @@ impl Args { // let len = seed_phrase.len(); // return Err(Error::InvalidSeedPhrase { len }); // } - Ok(SignerKind::SeedPhrase { + Ok(Secret::SeedPhrase { seed_phrase: seed_phrase .into_iter() .map(ToString::to_string) @@ -76,21 +76,21 @@ impl Args { #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] -pub enum SignerKind { +pub enum Secret { SecretKey { secret_key: String }, SeedPhrase { seed_phrase: String }, } -impl FromStr for SignerKind { +impl FromStr for Secret { type Err = Error; fn from_str(s: &str) -> Result { if PrivateKey::from_string(s).is_ok() { - Ok(SignerKind::SecretKey { + Ok(Secret::SecretKey { secret_key: s.to_string(), }) } else if sep5::SeedPhrase::from_str(s).is_ok() { - Ok(SignerKind::SeedPhrase { + Ok(Secret::SeedPhrase { seed_phrase: s.to_string(), }) } else { @@ -99,19 +99,19 @@ impl FromStr for SignerKind { } } -impl From for SignerKind { +impl From for Secret { fn from(value: PrivateKey) -> Self { - SignerKind::SecretKey { + Secret::SecretKey { secret_key: value.to_string(), } } } -impl SignerKind { +impl Secret { pub fn private_key(&self, index: Option) -> Result { Ok(match self { - SignerKind::SecretKey { secret_key } => PrivateKey::from_string(secret_key)?, - SignerKind::SeedPhrase { seed_phrase } => PrivateKey::from_payload( + Secret::SecretKey { secret_key } => PrivateKey::from_string(secret_key)?, + Secret::SeedPhrase { seed_phrase } => PrivateKey::from_payload( &sep5::SeedPhrase::from_str(seed_phrase)? .from_path_index(index.unwrap_or_default(), None)? .private() @@ -127,9 +127,9 @@ impl SignerKind { pub fn signer(&self, index: Option, prompt: bool) -> Result { match self { - SignerKind::SecretKey { .. } | SignerKind::SeedPhrase { .. } => Ok( - StellarSigner::Local(LocalKey::new(self.key_pair(index)?, prompt)), - ), + Secret::SecretKey { .. } | Secret::SeedPhrase { .. } => Ok(StellarSigner::Local( + LocalKey::new(self.key_pair(index)?, prompt), + )), } } @@ -145,7 +145,7 @@ impl SignerKind { }? .seed_phrase .into_phrase(); - Ok(SignerKind::SeedPhrase { seed_phrase }) + Ok(Secret::SeedPhrase { seed_phrase }) } pub fn test_seed_phrase() -> Result { From a3b84e4ec367c0395f8e19f486f1028be419e195 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Sat, 3 Aug 2024 12:08:17 -0400 Subject: [PATCH 43/50] Update cmd/soroban-cli/src/config/secret.rs --- cmd/soroban-cli/src/config/secret.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index c74a828f2..0418a8bf5 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -24,8 +24,6 @@ pub enum Error { Ed25519(#[from] ed25519_dalek::SignatureError), #[error("Invalid address {0}")] InvalidAddress(String), - #[error("Ledger does not reveal secret key")] - LedgerDoesNotRevealSecretKey, #[error(transparent)] Stellar(#[from] signer::Error), } From 47ba14b90b93e6cabcb0a62ff35a7f18ab5ed69b Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 5 Aug 2024 18:26:25 -0700 Subject: [PATCH 44/50] fix: hide sign-with-lab --- FULL_HELP_DOCS.md | 12 ------------ cmd/soroban-cli/src/config/sign_with.rs | 10 +++++----- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 28356221b..d34989f05 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -116,7 +116,6 @@ Get Id of builtin Soroban Asset Contract. Deprecated, use `stellar contract id a * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint @@ -138,7 +137,6 @@ Deploy builtin Soroban Asset Contract * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint @@ -276,7 +274,6 @@ If no keys are specified the contract itself is extended. * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint @@ -307,7 +304,6 @@ Deploy a wasm contract * `--salt ` — Custom salt 32-byte salt for the token id * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint @@ -371,7 +367,6 @@ Deploy builtin Soroban Asset Contract * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint @@ -393,7 +388,6 @@ Deploy normal Wasm Contract * `--salt ` — ID of the Soroban contract * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint @@ -463,7 +457,6 @@ Install a WASM file to the ledger without creating a contract instance * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint @@ -505,7 +498,6 @@ stellar contract invoke ... -- --help * `--is-view` — View the result simulating and do not sign and submit transaction * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint @@ -577,7 +569,6 @@ Print the current value of a contract-data ledger entry * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint @@ -617,7 +608,6 @@ If no keys are specificed the contract itself is restored. * `--ttl-ledger-only` — Only print the new Time To Live ledger * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint @@ -1105,7 +1095,6 @@ Simulate a transaction envelope from stdin * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint @@ -1139,7 +1128,6 @@ Sign a transaction envolope appending the signature to the envelope ###### **Options:** * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--sign-with-laboratory` — Sign with labratory * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions * `--rpc-url ` — RPC server endpoint diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index 398c92606..fa878702a 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -34,20 +34,20 @@ pub struct Args { /// Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). #[arg( long, - conflicts_with = "sign_with_laboratory", + conflicts_with = "sign_with_lab", env = "STELLAR_SIGN_WITH_SECRET" )] pub sign_with_key: Option, /// Sign with labratory #[arg( long, - visible_alias = "sign-with-lab", conflicts_with = "sign_with_key", - env = "STELLAR_SIGN_WITH_LABRATORY" + env = "STELLAR_SIGN_WITH_LABRATORY", + hide = true )] - pub sign_with_laboratory: bool, + pub sign_with_lab: bool, - #[arg(long, conflicts_with = "sign_with_laboratory")] + #[arg(long, conflicts_with = "sign_with_lab")] /// If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` pub hd_path: Option, From 8e9fd292a6b05d1ffe422b8edfeb821732936c0f Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 13 Aug 2024 14:41:30 -0400 Subject: [PATCH 45/50] fix: simplify Stellar trait and remove auth methods --- .../src/commands/contract/invoke.rs | 7 +- cmd/soroban-cli/src/config/sign_with.rs | 28 +-- cmd/soroban-cli/src/signer.rs | 174 +++--------------- cmd/soroban-cli/src/signer/auth.rs | 141 ++++++++++++++ 4 files changed, 172 insertions(+), 178 deletions(-) create mode 100644 cmd/soroban-cli/src/signer/auth.rs diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 4c0d8c61a..6a895be99 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -32,6 +32,7 @@ use crate::commands::NetworkRunnable; use crate::config::{self, data, locator, network}; use crate::get_spec::{self, get_remote_contract_spec}; use crate::print; +use crate::signer::auth::sign_soroban_authorizations; use crate::signer::{self, Stellar}; use crate::{commands::global, rpc, Pwd}; use soroban_spec_tools::{contract, Spec}; @@ -398,9 +399,9 @@ impl NetworkRunnable for Cmd { let mut txn = txn.transaction().clone(); let expriation_ledger = self.auth.expiration_ledger(&client).await?; for signer in &signers { - if let Some(tx) = signer - .sign_soroban_authorizations(&txn, &network, expriation_ledger) - .await? + if let Some(tx) = + sign_soroban_authorizations(signer, &txn, &network, expriation_ledger) + .await? { txn = tx; } diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index fa878702a..fccff9a81 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -1,8 +1,8 @@ use std::path::PathBuf; use crate::{ - signer::{self, Stellar}, - xdr::{Transaction, TransactionEnvelope}, + signer::{self, sign_txn_env, Stellar}, + xdr::TransactionEnvelope, }; use clap::arg; @@ -85,29 +85,7 @@ impl Args { tx_env: TransactionEnvelope, ) -> Result { let network = self.get_network()?; - Ok(signer.sign_txn_env(tx_env, &network).await?) - } - - pub async fn sign_soroban_authorizations( - &self, - tx: &Transaction, - expiration_ledger: u32, - ) -> Result, Error> { - Ok(self - .signer()? - .sign_soroban_authorizations(tx, &self.get_network()?, expiration_ledger) - .await?) - } - - pub async fn sign_soroban_authorizations_with_signer( - &self, - signer: &(impl Stellar + std::marker::Sync), - tx: &Transaction, - expiration_ledger: u32, - ) -> Result, Error> { - Ok(signer - .sign_soroban_authorizations(tx, &self.get_network()?, expiration_ledger) - .await?) + Ok(sign_txn_env(signer, tx_env, &network).await?) } pub fn get_network(&self) -> Result { diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index f2ac358f0..4f4a3b885 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -5,15 +5,15 @@ use sha2::{Digest, Sha256}; use crate::{ config::network::Network, xdr::{ - self, AccountId, DecoratedSignature, Hash, HashIdPreimage, - HashIdPreimageSorobanAuthorization, InvokeHostFunctionOp, Limits, Operation, OperationBody, - PublicKey, ScAddress, ScMap, ScSymbol, ScVal, Signature, SignatureHint, - SorobanAddressCredentials, SorobanAuthorizationEntry, SorobanAuthorizedFunction, - SorobanCredentials, Transaction, TransactionEnvelope, TransactionSignaturePayload, - TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, Uint256, WriteXdr, + self, DecoratedSignature, InvokeHostFunctionOp, Limits, Operation, OperationBody, + Signature, SignatureHint, SorobanAuthorizedFunction, Transaction, TransactionEnvelope, + TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + TransactionV1Envelope, WriteXdr, }, }; +pub mod auth; + #[derive(thiserror::Error, Debug)] pub enum Error { #[error("Contract addresses are not supported to sign auth entries {address}")] @@ -32,7 +32,7 @@ pub enum Error { UnsupportedTransactionEnvelopeType, } -fn requires_auth(txn: &Transaction) -> Option { +pub fn extract_auth_operation(txn: &Transaction) -> Option { let [op @ Operation { body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), .. @@ -84,25 +84,6 @@ pub trait Stellar { }) } - async fn sign_txn_env( - &self, - txn_env: TransactionEnvelope, - network: &Network, - ) -> Result { - match txn_env { - TransactionEnvelope::Tx(TransactionV1Envelope { tx, signatures }) => { - let decorated_signature = self.sign_txn(&tx, network).await?; - let mut sigs = signatures.to_vec(); - sigs.push(decorated_signature); - Ok(TransactionEnvelope::Tx(TransactionV1Envelope { - tx, - signatures: sigs.try_into()?, - })) - } - _ => Err(Error::UnsupportedTransactionEnvelopeType), - } - } - /// Sign a Stellar transaction with the given source account /// This is a default implementation that signs the transaction hash and returns a decorated signature /// @@ -119,135 +100,28 @@ pub trait Stellar { let hash = transaction_hash(txn, network_passphrase)?; self.sign_txn_hash(hash).await } +} - /// Sign a Soroban authorization entries for a given transaction and set the expiration ledger - /// # Errors - /// Returns an error if the address is not found - async fn sign_soroban_authorizations( - &self, - raw: &Transaction, - network: &Network, - expiration_ledger: u32, - ) -> Result, Error> { - let mut tx = raw.clone(); - let Some(mut op) = requires_auth(&tx) else { - return Ok(None); - }; - - let xdr::Operation { - body: OperationBody::InvokeHostFunction(ref mut body), - .. - } = op - else { - return Ok(None); - }; - let mut auths = body.auth.to_vec(); - for auth in &mut auths { - *auth = self - .maybe_sign_soroban_authorization_entry(auth, network, expiration_ledger) - .await?; - } - body.auth = auths.try_into()?; - tx.operations = [op].try_into()?; - Ok(Some(tx)) - } - - /// Sign a Soroban authorization entry if the address is public key - /// # Errors - /// Returns an error if the address in entry is a contract - async fn maybe_sign_soroban_authorization_entry( - &self, - unsigned_entry: &SorobanAuthorizationEntry, - network: &Network, - expiration_ledger: u32, - ) -> Result { - if let SorobanAuthorizationEntry { - credentials: SorobanCredentials::Address(SorobanAddressCredentials { address, .. }), - .. - } = unsigned_entry - { - // See if we have a signer for this authorizationEntry - // If not, then we Error - let key = match address { - ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(a)))) => { - stellar_strkey::ed25519::PublicKey(*a) - } - ScAddress::Contract(Hash(c)) => { - // This address is for a contract. This means we're using a custom - // smart-contract account. Currently the CLI doesn't support that yet. - return Err(Error::MissingSignerForAddress { - address: stellar_strkey::Strkey::Contract(stellar_strkey::Contract(*c)) - .to_string(), - }); - } - }; - if key == self.get_public_key().await? { - return self - .sign_soroban_authorization_entry(unsigned_entry, network, expiration_ledger) - .await; - } +pub async fn sign_txn_env( + signer: &(impl Stellar + std::marker::Sync), + txn_env: TransactionEnvelope, + network: &Network, +) -> Result { + match txn_env { + TransactionEnvelope::Tx(TransactionV1Envelope { tx, signatures }) => { + let decorated_signature = signer.sign_txn(&tx, network).await?; + let mut sigs = signatures.to_vec(); + sigs.push(decorated_signature); + Ok(TransactionEnvelope::Tx(TransactionV1Envelope { + tx, + signatures: sigs.try_into()?, + })) } - Ok(unsigned_entry.clone()) - } - - /// Sign a Soroban authorization entry with the given address - /// # Errors - /// Returns an error if the address is not found - async fn sign_soroban_authorization_entry( - &self, - unsigned_entry: &SorobanAuthorizationEntry, - Network { - network_passphrase, .. - }: &Network, - expiration_ledger: u32, - ) -> Result { - let address = self.get_public_key().await?; - let mut auth = unsigned_entry.clone(); - let SorobanAuthorizationEntry { - credentials: SorobanCredentials::Address(ref mut credentials), - .. - } = auth - else { - // Doesn't need special signing - return Ok(auth); - }; - let SorobanAddressCredentials { - nonce, - signature_expiration_ledger, - .. - } = credentials; - - *signature_expiration_ledger = expiration_ledger; - - let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { - network_id: hash(network_passphrase), - invocation: auth.root_invocation.clone(), - nonce: *nonce, - signature_expiration_ledger: *signature_expiration_ledger, - }) - .to_xdr(Limits::none())?; - - let payload = Sha256::digest(preimage); - let signature = self.sign_blob(&payload).await?; - - let map = ScMap::sorted_from(vec![ - ( - ScVal::Symbol(ScSymbol("public_key".try_into()?)), - ScVal::Bytes(address.0.to_vec().try_into()?), - ), - ( - ScVal::Symbol(ScSymbol("signature".try_into()?)), - ScVal::Bytes(signature.try_into()?), - ), - ])?; - credentials.signature = ScVal::Vec(Some(vec![ScVal::Map(Some(map))].try_into()?)); - auth.credentials = SorobanCredentials::Address(credentials.clone()); - - Ok(auth) + _ => Err(Error::UnsupportedTransactionEnvelopeType), } } -fn hash(network_passphrase: &str) -> xdr::Hash { +pub(crate) fn hash(network_passphrase: &str) -> xdr::Hash { xdr::Hash(Sha256::digest(network_passphrase.as_bytes()).into()) } diff --git a/cmd/soroban-cli/src/signer/auth.rs b/cmd/soroban-cli/src/signer/auth.rs new file mode 100644 index 000000000..e404aff8f --- /dev/null +++ b/cmd/soroban-cli/src/signer/auth.rs @@ -0,0 +1,141 @@ +use sha2::{Digest, Sha256}; + +use crate::{ + config::network::Network, + xdr::{ + self, AccountId, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, Limits, + OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, ScVal, SorobanAddressCredentials, + SorobanAuthorizationEntry, SorobanCredentials, Transaction, Uint256, WriteXdr, + }, +}; + +use super::{extract_auth_operation, hash, Error, Stellar}; + +/// Sign a Soroban authorization entries for a given transaction and set the expiration ledger +/// # Errors +/// Returns an error if the address is not found +pub async fn sign_soroban_authorizations( + signer: &impl Stellar, + raw: &Transaction, + network: &Network, + expiration_ledger: u32, +) -> Result, Error> { + let mut tx = raw.clone(); + let Some(mut op) = extract_auth_operation(&tx) else { + return Ok(None); + }; + + let xdr::Operation { + body: OperationBody::InvokeHostFunction(ref mut body), + .. + } = op + else { + return Ok(None); + }; + let mut auths = body.auth.to_vec(); + for auth in &mut auths { + *auth = maybe_sign_soroban_authorization_entry(signer, auth, network, expiration_ledger) + .await?; + } + body.auth = auths.try_into()?; + tx.operations = [op].try_into()?; + Ok(Some(tx)) +} + +/// Sign a Soroban authorization entry if the address is public key +/// # Errors +/// Returns an error if the address in entry is a contract +pub async fn maybe_sign_soroban_authorization_entry( + signer: &impl Stellar, + unsigned_entry: &SorobanAuthorizationEntry, + network: &Network, + expiration_ledger: u32, +) -> Result { + if let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(SorobanAddressCredentials { address, .. }), + .. + } = unsigned_entry + { + // See if we have a signer for this authorizationEntry + // If not, then we Error + let key = match address { + ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(a)))) => { + stellar_strkey::ed25519::PublicKey(*a) + } + ScAddress::Contract(Hash(c)) => { + // This address is for a contract. This means we're using a custom + // smart-contract account. Currently the CLI doesn't support that yet. + return Err(Error::MissingSignerForAddress { + address: stellar_strkey::Strkey::Contract(stellar_strkey::Contract(*c)) + .to_string(), + }); + } + }; + if key == signer.get_public_key().await? { + return sign_soroban_authorization_entry( + signer, + unsigned_entry, + network, + expiration_ledger, + ) + .await; + } + } + Ok(unsigned_entry.clone()) +} + +/// Sign a Soroban authorization entry with the given address +/// # Errors +/// Returns an error if the address is not found +pub async fn sign_soroban_authorization_entry( + signer: &impl Stellar, + unsigned_entry: &SorobanAuthorizationEntry, + Network { + network_passphrase, .. + }: &Network, + expiration_ledger: u32, +) -> Result { + let address = signer.get_public_key().await?; + let mut auth = unsigned_entry.clone(); + let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(ref mut credentials), + .. + } = auth + else { + // Doesn't need special signing + return Ok(auth); + }; + let SorobanAddressCredentials { + nonce, + signature_expiration_ledger, + .. + } = credentials; + + *signature_expiration_ledger = expiration_ledger; + + let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { + network_id: hash(network_passphrase), + invocation: auth.root_invocation.clone(), + nonce: *nonce, + signature_expiration_ledger: *signature_expiration_ledger, + }) + .to_xdr(Limits::none())?; + + let payload = Sha256::digest(preimage); + let signature = signer.sign_blob(&payload).await?; + + let map = ScMap::sorted_from(vec![ + ( + ScVal::Symbol(ScSymbol("public_key".try_into()?)), + ScVal::Bytes(address.0.to_vec().try_into()?), + ), + ( + ScVal::Symbol(ScSymbol("signature".try_into()?)), + ScVal::Bytes(signature.try_into()?), + ), + ])?; + credentials.signature = ScVal::Vec(Some(vec![ScVal::Map(Some(map))].try_into()?)); + auth.credentials = SorobanCredentials::Address(credentials.clone()); + + Ok(auth) +} From ae71c9071a1fb1c0951239a1bde72a148b28e6e9 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 13 Aug 2024 15:08:16 -0400 Subject: [PATCH 46/50] fix: don't assume source account is signer --- cmd/soroban-cli/src/commands/contract/deploy/asset.rs | 2 +- cmd/soroban-cli/src/commands/contract/deploy/wasm.rs | 2 +- cmd/soroban-cli/src/commands/contract/extend.rs | 2 +- cmd/soroban-cli/src/commands/contract/id/wasm.rs | 2 +- cmd/soroban-cli/src/commands/contract/install.rs | 2 +- cmd/soroban-cli/src/commands/contract/invoke.rs | 2 +- cmd/soroban-cli/src/commands/contract/restore.rs | 2 +- cmd/soroban-cli/src/config/mod.rs | 11 ++++++++--- 8 files changed, 15 insertions(+), 10 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index 2567624ac..d0abea606 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -98,7 +98,7 @@ impl NetworkRunnable for Cmd { .verify_network_passphrase(Some(&network.network_passphrase)) .await?; // Get the account sequence number - let public_strkey = config.public_key().await?; + let public_strkey = config.source_account().await?; // TODO: use symbols for the method names (both here and in serve) let account_details = client.get_account(&public_strkey.to_string()).await?; let sequence: i64 = account_details.seq_num.into(); diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index db2fd8a2e..bc0283b6d 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -206,7 +206,7 @@ impl NetworkRunnable for Cmd { .verify_network_passphrase(Some(&network.network_passphrase)) .await?; // Get the account sequence number - let public_strkey = config.public_key().await?; + let public_strkey = config.source_account().await?; let account_details = client.get_account(&public_strkey.to_string()).await?; let sequence: i64 = account_details.seq_num.into(); let (txn, contract_id) = build_create_contract_tx( diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index a9294495f..18d41d172 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -134,7 +134,7 @@ impl NetworkRunnable for Cmd { let keys = self.key.parse_keys(contract)?; let network = &config.get_network()?; let client = Client::new(&network.rpc_url)?; - let public_key = config.public_key().await?; + let public_key = config.source_account().await?; let extend_to = self.ledgers_to_extend(); let account_details = client.get_account(&public_key.to_string()).await?; let sequence: i64 = account_details.seq_num.into(); diff --git a/cmd/soroban-cli/src/commands/contract/id/wasm.rs b/cmd/soroban-cli/src/commands/contract/id/wasm.rs index 66bf24d55..4afe14463 100644 --- a/cmd/soroban-cli/src/commands/contract/id/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/id/wasm.rs @@ -34,7 +34,7 @@ impl Cmd { .map_err(|_| Error::CannotParseSalt(self.salt.clone()))? .try_into() .map_err(|_| Error::CannotParseSalt(self.salt.clone()))?; - let contract_id_preimage = contract_preimage(&self.config.public_key().await?, salt); + let contract_id_preimage = contract_preimage(&self.config.source_account().await?, salt); let contract_id = get_contract_id( contract_id_preimage.clone(), &self.config.get_network()?.network_passphrase, diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 79d0de090..48ff44fc4 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -125,7 +125,7 @@ impl NetworkRunnable for Cmd { tracing::warn!("the deployed smart contract {path} was built with Soroban Rust SDK v{rs_sdk_ver}, a release candidate version not intended for use with the Stellar Public Network", path = self.wasm.wasm.display()); } } - let public_strkey = config.public_key().await?; + let public_strkey = config.source_account().await?; // Get the account sequence number let account_details = client.get_account(&public_strkey.to_string()).await?; let sequence: i64 = account_details.seq_num.into(); diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 6a895be99..6f5ae326b 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -351,7 +351,7 @@ impl NetworkRunnable for Cmd { client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; - let key = config.public_key().await?; + let key = config.source_account().await?; // Get the account sequence number client.get_account(&key.to_string()).await? diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs index 2022eee67..8ca899328 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -138,7 +138,7 @@ impl NetworkRunnable for Cmd { let client = Client::new(&network.rpc_url)?; // Get the account sequence number - let public_strkey = config.public_key().await?; + let public_strkey = config.source_account().await?; let account_details = client.get_account(&public_strkey.to_string()).await?; let sequence: i64 = account_details.seq_num.into(); diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index 5d9d2c765..a3e3c7b0f 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -4,7 +4,7 @@ use clap::{arg, command}; use secret::StellarSigner; use serde::{Deserialize, Serialize}; -use crate::signer::{self, Stellar}; +use crate::signer; use crate::xdr::{Transaction, TransactionEnvelope}; use crate::Pwd; @@ -55,8 +55,13 @@ impl Args { .signer(self.sign_with.hd_path, prompt)?) } - pub async fn public_key(&self) -> Result { - Ok(self.signer()?.get_public_key().await?) + pub async fn source_account(&self) -> Result { + Ok(self + .sign_with + .locator + .account(&self.source_account)? + .public_key(self.sign_with.hd_path) + .await?) } pub async fn sign(&self, tx: Transaction) -> Result { From 80819fe08840378ceb379e17b89b6b6ba46842c0 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 13 Aug 2024 17:01:11 -0400 Subject: [PATCH 47/50] fix: update comments --- FULL_HELP_DOCS.md | 70 ++++++++++++------------- cmd/soroban-cli/src/config/mod.rs | 4 +- cmd/soroban-cli/src/config/sign_with.rs | 4 +- 3 files changed, 40 insertions(+), 38 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 59c03dbc3..1e6d7428e 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -115,10 +115,10 @@ Get Id of builtin Soroban Asset Contract. Deprecated, use `stellar contract id a ###### **Options:** * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions +* `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -136,10 +136,10 @@ Deploy builtin Soroban Asset Contract ###### **Options:** * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions +* `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -273,10 +273,10 @@ If no keys are specified the contract itself is extended. - `temporary`: Temporary -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions +* `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -303,10 +303,10 @@ Deploy a wasm contract * `--wasm ` — WASM file to deploy * `--wasm-hash ` — Hash of the already installed/deployed WASM file * `--salt ` — Custom salt 32-byte salt for the token id -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions +* `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -366,10 +366,10 @@ Deploy builtin Soroban Asset Contract ###### **Options:** * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions +* `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -387,10 +387,10 @@ Deploy normal Wasm Contract ###### **Options:** * `--salt ` — ID of the Soroban contract -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions +* `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -587,10 +587,10 @@ Install a WASM file to the ledger without creating a contract instance ###### **Options:** -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions +* `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -628,10 +628,10 @@ stellar contract invoke ... -- --help * `--id ` — Contract ID to invoke * `--is-view` — View the result simulating and do not sign and submit transaction. Deprecated use `--send=no` -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions +* `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -711,10 +711,10 @@ Print the current value of a contract-data ledger entry - `temporary`: Temporary -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions +* `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -750,10 +750,10 @@ If no keys are specificed the contract itself is restored. * `--ledgers-to-extend ` — Number of ledgers to extend the entry * `--ttl-ledger-only` — Only print the new Time To Live ledger -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions +* `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -1237,10 +1237,10 @@ Simulate a transaction envelope from stdin ###### **Options:** -* `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions +* `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -1272,8 +1272,8 @@ Sign a transaction envolope appending the signature to the envelope ###### **Options:** * `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` -* `--yes` — If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions +* `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` +* `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index a3e3c7b0f..825acd565 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -35,7 +35,9 @@ pub enum Error { #[group(skip)] pub struct Args { #[arg(long, visible_alias = "source", env = "STELLAR_ACCOUNT")] - /// Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). + /// Account where the final transaction originates from. + /// If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. + /// Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) pub source_account: String, #[command(flatten)] diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index fccff9a81..ae22465ce 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -48,10 +48,10 @@ pub struct Args { pub sign_with_lab: bool, #[arg(long, conflicts_with = "sign_with_lab")] - /// If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` + /// If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` pub hd_path: Option, - /// If `--sign-with-*` is used this will remove requirement of being prompted to sign transactions + /// If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction #[arg(long)] pub yes: bool, From 1fdc4de33f600a92faffec5ca9bd1e2a0f1a526a Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 27 Aug 2024 03:18:57 -0400 Subject: [PATCH 48/50] Apply suggestions from code review Co-authored-by: Gleb --- cmd/soroban-cli/src/commands/contract/invoke.rs | 1 + cmd/soroban-cli/src/config/sign_with.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 6f5ae326b..d8f1c4e87 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -233,6 +233,7 @@ impl Cmd { let sc_val = if let Some(mut val) = matches_.get_raw(&name) { let mut s = val.next().unwrap().to_string_lossy().to_string(); if matches!(input.type_, ScSpecTypeDef::Address) { + // Currently we only support local keys, same as input for --sign-with-key`, for signing auth entries. if let Ok(signer_) = config .sign_with .locator diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index ae22465ce..c31580eef 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -31,7 +31,7 @@ pub enum Error { #[derive(Debug, clap::Args, Clone, Default)] #[group(skip)] pub struct Args { - /// Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). + /// Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path. #[arg( long, conflicts_with = "sign_with_lab", From b853484f278a07476e4dcbd12e504469ffec909c Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 27 Aug 2024 13:52:29 -0400 Subject: [PATCH 49/50] fix: docs --- FULL_HELP_DOCS.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 8fe656483..76c60ad3e 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -117,7 +117,7 @@ Get Id of builtin Soroban Asset Contract. Deprecated, use `stellar contract id a * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -138,7 +138,7 @@ Deploy builtin Soroban Asset Contract * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -307,7 +307,7 @@ If no keys are specified the contract itself is extended. Temporary * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -337,7 +337,7 @@ Deploy a wasm contract * `--wasm-hash ` — Hash of the already installed/deployed WASM file * `--salt ` — Custom salt 32-byte salt for the token id * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -400,7 +400,7 @@ Deploy builtin Soroban Asset Contract * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -421,7 +421,7 @@ Deploy normal Wasm Contract * `--salt ` — ID of the Soroban contract * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -621,7 +621,7 @@ Install a WASM file to the ledger without creating a contract instance ###### **Options:** * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -662,7 +662,7 @@ stellar contract invoke ... -- --help * `--id ` — Contract ID to invoke * `--is-view` — View the result simulating and do not sign and submit transaction. Deprecated use `--send=no` * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -745,7 +745,7 @@ Print the current value of a contract-data ledger entry Temporary * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -784,7 +784,7 @@ If no keys are specificed the contract itself is restored. * `--ledgers-to-extend ` — Number of ledgers to extend the entry * `--ttl-ledger-only` — Only print the new Time To Live ledger * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -1271,7 +1271,7 @@ Simulate a transaction envelope from stdin ###### **Options:** * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -1304,7 +1304,7 @@ Sign a transaction envolope appending the signature to the envelope ###### **Options:** -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint From ce1bbe7c228d30a434867949cd044ca0a16852a5 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:05:32 -0400 Subject: [PATCH 50/50] Fix after merging --- .../src/commands/contract/arg_parsing.rs | 127 ++++++++++-------- .../src/commands/contract/extend.rs | 2 +- .../src/commands/contract/invoke.rs | 15 +-- cmd/soroban-cli/src/commands/contract/read.rs | 2 +- .../src/commands/contract/restore.rs | 2 +- 5 files changed, 79 insertions(+), 69 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs index 4a8af47e2..772a3e2f9 100644 --- a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs +++ b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs @@ -5,16 +5,17 @@ use std::fmt::Debug; use std::path::PathBuf; use clap::value_parser; -use ed25519_dalek::SigningKey; use heck::ToKebabCase; +use crate::commands::txn_result::TxnResult; +use crate::config::secret::StellarSigner; +use crate::config::{self}; +use crate::signer::{self, Stellar}; use soroban_env_host::xdr::{ self, Hash, InvokeContractArgs, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, ScVec, }; - -use crate::commands::txn_result::TxnResult; -use crate::config::{self}; +use soroban_sdk::xdr::ScSpecFunctionInputV0; use soroban_spec_tools::Spec; #[derive(thiserror::Error, Debug)] @@ -43,14 +44,16 @@ pub enum Error { MissingArgument(String), #[error("")] MissingFileArg(PathBuf), + #[error(transparent)] + Signer(#[from] signer::Error), } -pub fn build_host_function_parameters( +pub async fn build_host_function_parameters( contract_id: &stellar_strkey::Contract, slop: &[OsString], spec_entries: &[ScSpecEntry], config: &config::Args, -) -> Result<(String, Spec, InvokeContractArgs, Vec), Error> { +) -> Result<(String, Spec, InvokeContractArgs, Vec), Error> { let spec = Spec(Some(spec_entries.to_vec())); let mut cmd = clap::Command::new(contract_id.to_string()) .no_binary_name(true) @@ -70,57 +73,15 @@ pub fn build_host_function_parameters( let func = spec.find_function(function)?; // create parsed_args in same order as the inputs to func - let mut signers: Vec = vec![]; - let parsed_args = func - .inputs - .iter() - .map(|i| { - let name = i.name.to_utf8_string()?; - if let Some(mut val) = matches_.get_raw(&name) { - let mut s = val.next().unwrap().to_string_lossy().to_string(); - if matches!(i.type_, ScSpecTypeDef::Address) { - let cmd = crate::commands::keys::address::Cmd { - name: s.clone(), - hd_path: Some(0), - locator: config.locator.clone(), - }; - if let Ok(address) = cmd.public_key() { - s = address.to_string(); - } - if let Ok(key) = cmd.private_key() { - signers.push(key); - } - } - spec.from_string(&s, &i.type_) - .map_err(|error| Error::CannotParseArg { arg: name, error }) - } else if matches!(i.type_, ScSpecTypeDef::Option(_)) { - Ok(ScVal::Void) - } else if let Some(arg_path) = matches_.get_one::(&fmt_arg_file_name(&name)) { - if matches!(i.type_, ScSpecTypeDef::Bytes | ScSpecTypeDef::BytesN(_)) { - Ok(ScVal::try_from( - &std::fs::read(arg_path) - .map_err(|_| Error::MissingFileArg(arg_path.clone()))?, - ) - .map_err(|()| Error::CannotParseArg { - arg: name.clone(), - error: soroban_spec_tools::Error::Unknown, - })?) - } else { - let file_contents = std::fs::read_to_string(arg_path) - .map_err(|_| Error::MissingFileArg(arg_path.clone()))?; - tracing::debug!( - "file {arg_path:?}, has contents:\n{file_contents}\nAnd type {:#?}\n{}", - i.type_, - file_contents.len() - ); - spec.from_string(&file_contents, &i.type_) - .map_err(|error| Error::CannotParseArg { arg: name, error }) - } - } else { - Err(Error::MissingArgument(name)) - } - }) - .collect::, Error>>()?; + let mut signers: Vec = vec![]; + let mut parsed_args: Vec = Vec::new(); + for i in func.inputs.iter() { + let (val, signer) = parse_arg(i, matches_, config, &spec).await?; + parsed_args.push(val); + if let Some(signer) = signer { + signers.push(signer); + } + } let contract_address_arg = ScAddress::Contract(Hash(contract_id.0)); let function_symbol_arg = function @@ -145,6 +106,58 @@ pub fn build_host_function_parameters( Ok((function.clone(), spec, invoke_args, signers)) } +pub async fn parse_arg( + input: &ScSpecFunctionInputV0, + matches_: &clap::ArgMatches, + config: &config::Args, + spec: &Spec, +) -> Result<(ScVal, Option), Error> { + let mut signer: Option = None; + let name = input.name.to_utf8_string()?; + let sc_val = if let Some(mut val) = matches_.get_raw(&name) { + let mut s = val.next().unwrap().to_string_lossy().to_string(); + if matches!(input.type_, ScSpecTypeDef::Address) { + // Currently we only support local keys, same as input for --sign-with-key`, for signing auth entries. + if let Ok(signer_) = config + .sign_with + .locator + .account(&s) + .and_then(|signer| Ok(signer.signer(config.sign_with.hd_path, false)?)) + { + s = signer_.get_public_key().await?.to_string(); + signer = Some(signer_); + } + } + spec.from_string(&s, &input.type_) + .map_err(|error| Error::CannotParseArg { arg: name, error })? + } else if matches!(input.type_, ScSpecTypeDef::Option(_)) { + ScVal::Void + } else if let Some(arg_path) = matches_.get_one::(&fmt_arg_file_name(&name)) { + if matches!(input.type_, ScSpecTypeDef::Bytes | ScSpecTypeDef::BytesN(_)) { + ScVal::try_from( + &std::fs::read(arg_path).map_err(|_| Error::MissingFileArg(arg_path.clone()))?, + ) + .map_err(|()| Error::CannotParseArg { + arg: name.clone(), + error: soroban_spec_tools::Error::Unknown, + })? + } else { + let file_contents = std::fs::read_to_string(arg_path) + .map_err(|_| Error::MissingFileArg(arg_path.clone()))?; + tracing::debug!( + "file {arg_path:?}, has contents:\n{file_contents}\nAnd type {:#?}\n{}", + input.type_, + file_contents.len() + ); + spec.from_string(&file_contents, &input.type_) + .map_err(|error| Error::CannotParseArg { arg: name, error })? + } + } else { + return Err(Error::MissingArgument(name)); + }; + Ok((sc_val, signer)) +} + fn build_custom_cmd(name: &str, spec: &Spec) -> Result { let func = spec .find_function(name) diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index 199d36810..79d0d3bd9 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -130,7 +130,7 @@ impl NetworkRunnable for Cmd { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); - let keys = self.key.parse_keys(&config.locator, &network)?; + let keys = self.key.parse_keys(&config.sign_with.locator, &network)?; let network = &config.get_network()?; let client = Client::new(&network.rpc_url)?; let public_key = config.source_account().await?; diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 9264d8f44..816431c14 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -19,28 +19,23 @@ use soroban_env_host::{ }; use soroban_rpc::{SimulateHostFunctionResult, SimulateTransactionResponse}; -use soroban_sdk::xdr::ScSpecFunctionInputV0; use soroban_spec::read::FromWasmError; use super::super::events; use super::arg_parsing; -use crate::commands::config::secret::StellarSigner; use crate::commands::contract::arg_parsing::{build_host_function_parameters, output_to_string}; use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult}; use crate::commands::NetworkRunnable; -use crate::config::{self, data, locator, network}; use crate::get_spec::{self, get_remote_contract_spec}; use crate::print; -use crate::signer::auth::sign_soroban_authorizations; -use crate::signer::{self, Stellar}; +use crate::signer::{self, auth::sign_soroban_authorizations}; +// use crate::signer; use crate::{ commands::global, config::{self, data, locator, network}, rpc, Pwd, }; -use crate::{commands::global, rpc, Pwd}; use soroban_spec_tools::contract; -use soroban_spec_tools::{contract, Spec}; #[derive(Parser, Debug, Default, Clone)] #[allow(clippy::struct_excessive_bools)] @@ -212,13 +207,15 @@ impl NetworkRunnable for Cmd { tracing::trace!(?network); let contract_id = self .config + .sign_with .locator .resolve_contract_id(&self.contract_id, &network.network_passphrase)?; let spec_entries = self.spec_entries()?; if let Some(spec_entries) = &spec_entries { // For testing wasm arg parsing - let _ = build_host_function_parameters(&contract_id, &self.slop, spec_entries, config)?; + let _ = build_host_function_parameters(&contract_id, &self.slop, spec_entries, config) + .await?; } let client = rpc::Client::new(&network.rpc_url)?; let account_details = if self.is_view { @@ -247,7 +244,7 @@ impl NetworkRunnable for Cmd { // Get the ledger footprint let (function, spec, host_function_params, signers) = - build_host_function_parameters(&contract_id, &self.slop, &spec_entries, config)?; + build_host_function_parameters(&contract_id, &self.slop, &spec_entries, config).await?; let tx = build_invoke_contract_tx( host_function_params.clone(), sequence + 1, diff --git a/cmd/soroban-cli/src/commands/contract/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs index 3cb253bb1..8398a281b 100644 --- a/cmd/soroban-cli/src/commands/contract/read.rs +++ b/cmd/soroban-cli/src/commands/contract/read.rs @@ -186,7 +186,7 @@ impl NetworkRunnable for Cmd { let network = config.get_network()?; tracing::trace!(?network); let client = Client::new(&network.rpc_url)?; - let keys = self.key.parse_keys(&config.locator, &network)?; + let keys = self.key.parse_keys(&config.sign_with.locator, &network)?; Ok(client.get_full_ledger_entries(&keys).await?) } } diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs index 67791a24f..d3cdad6d0 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -133,7 +133,7 @@ impl NetworkRunnable for Cmd { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); - let entry_keys = self.key.parse_keys(&config.locator, &network)?; + let entry_keys = self.key.parse_keys(&config.sign_with.locator, &network)?; let client = Client::new(&network.rpc_url)?; // Get the account sequence number