Skip to content

Commit

Permalink
feat: add signer::Stellar trait and sign & send
Browse files Browse the repository at this point in the history
Also add Ledger options.
  • Loading branch information
willemneal committed May 31, 2024
1 parent 0552599 commit 3c65b14
Show file tree
Hide file tree
Showing 9 changed files with 827 additions and 563 deletions.
816 changes: 417 additions & 399 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ version = "=21.0.1-preview.1"
version = "=21.0.0-rc.1"
path = "cmd/soroban-cli"

[workspace.dependencies.stellar-ledger]
version = "=21.0.0-rc.1"
path = "cmd/crates/stellar-ledger"

[workspace.dependencies.soroban-rpc]
package = "stellar-rpc-client"
version = "=21.0.1"
Expand Down
50 changes: 49 additions & 1 deletion cmd/crates/soroban-test/tests/it/integration/tx.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use soroban_cli::commands::tx;
use soroban_sdk::xdr::{
Limits, ReadXdr, TransactionEnvelope, TransactionV1Envelope, VecM, WriteXdr,
Limits, ReadXdr, Transaction, TransactionEnvelope, TransactionV1Envelope, VecM, WriteXdr,
};
use soroban_test::{AssertExt, TestEnv};

Expand Down Expand Up @@ -38,3 +38,51 @@ 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("--signer=file")
.arg("--source=test")
.arg("-y")
.write_stdin(tx_env.to_xdr_base64(Limits::none()).unwrap().as_bytes())
.assert()
.success()
.stdout_as_str(),
Limits::none(),
)
.unwrap()
}
48 changes: 27 additions & 21 deletions cmd/crates/stellar-ledger/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use hd_path::HdPath;
use ledger_transport::{APDUCommand, Exchange};
use ledger_transport::APDUCommand;
use ledger_transport_hid::{
hidapi::{HidApi, HidError},
LedgerHIDError, TransportNativeHID,
LedgerHIDError,
};
pub use ledger_transport_hid::TransportNativeHID;

use soroban_env_host::xdr::{Hash, Transaction};
use std::vec;
Expand All @@ -14,6 +15,7 @@ use stellar_xdr::curr::{
};

pub use crate::signer::Blob;
pub use ledger_transport::Exchange;

mod emulator_http_transport;
mod signer;
Expand Down Expand Up @@ -188,7 +190,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
async fn get_public_key_with_display_flag(
pub async fn get_public_key_with_display_flag(
&self,
hd_path: impl Into<HdPath>,
display_and_confirm: bool,
Expand Down Expand Up @@ -247,6 +249,27 @@ where
)),
}
}

pub async fn sign_data(&self, index: &HdPath, blob: &[u8]) -> Result<Vec<u8>, Error> {
let mut hd_path_to_bytes = index.to_vec()?;

let capacity = 1 + hd_path_to_bytes.len() + blob.len();
let mut data: Vec<u8> = 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]
Expand All @@ -271,24 +294,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<Vec<u8>, Error> {
let mut hd_path_to_bytes = index.to_vec()?;

let capacity = 1 + hd_path_to_bytes.len() + blob.len();
let mut data: Vec<u8> = 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
}
}

Expand Down
2 changes: 2 additions & 0 deletions cmd/soroban-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 13 additions & 1 deletion cmd/soroban-cli/src/commands/tx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,38 @@ use clap::Parser;

use super::global;

pub mod send;
pub mod sign;
pub mod simulate;
pub mod xdr;

#[derive(Debug, Parser)]
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(())
}
Expand Down
53 changes: 53 additions & 0 deletions cmd/soroban-cli/src/commands/tx/send.rs
Original file line number Diff line number Diff line change
@@ -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<Self::Result, Self::Error> {
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(&tx_env).await?)
}
}
Loading

0 comments on commit 3c65b14

Please sign in to comment.