Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: tx sign and new signer #1590

Merged
merged 26 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
bf88e36
feat: `tx sign` and new traits for signing
willemneal Sep 10, 2024
df58864
refactor(tx): test
willemneal Sep 10, 2024
58630c3
Merge remote-tracking branch 'origin/main' into feat/tx_sign
willemneal Sep 12, 2024
a0cc592
fix: simplify sign_with
willemneal Sep 12, 2024
c705666
Apply suggestions from code review
willemneal Sep 16, 2024
00aaedc
fix: address PR review
willemneal Sep 17, 2024
8745dc5
Merge remote-tracking branch 'origin/main' into feat/tx_sign
willemneal Sep 17, 2024
ada184f
Merge remote-tracking branch 'origin/main' into feat/tx_sign
willemneal Sep 18, 2024
00d989a
fix: remove unneeded arg
willemneal Sep 18, 2024
c36f788
rename Transaction trait to SignTx and collapse traits
leighmcculloch Sep 23, 2024
1470057
rename account to key
leighmcculloch Sep 23, 2024
70837be
update docs
leighmcculloch Sep 23, 2024
bf7f88d
flatten layer of one liner
leighmcculloch Sep 23, 2024
c2a7cc2
undo move transaction_hash, remove trait
leighmcculloch Sep 23, 2024
df299e8
undo change
leighmcculloch Sep 23, 2024
9beed05
rename Stellar error to Signer
leighmcculloch Sep 23, 2024
d3e22cc
move StellarSigner and eliminate trait
leighmcculloch Sep 23, 2024
ded7eba
handle instead of panic on error
leighmcculloch Sep 23, 2024
015435b
move types into signer and replace existing signing logic
leighmcculloch Sep 23, 2024
baf7036
address clippy
leighmcculloch Sep 23, 2024
f3fbff7
lab.stellar.org
leighmcculloch Sep 23, 2024
d56fa09
clippy
leighmcculloch Sep 23, 2024
25102a0
remove unnecessary trim
leighmcculloch Sep 24, 2024
02245e7
fix e2e test run and tweak
leighmcculloch Sep 24, 2024
376c464
remove deadcode
leighmcculloch Sep 24, 2024
1db4b5a
remove sign with lab
leighmcculloch Sep 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions FULL_HELP_DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,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 envelope appending the signature to the envelope



Expand Down Expand Up @@ -1326,6 +1327,24 @@ Calculate the hash of a transaction envelope from stdin



## `stellar tx sign`

Sign a transaction envelope appending the signature to the envelope

**Usage:** `stellar tx sign [OPTIONS]`

###### **Options:**

* `--sign-with-key <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 <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`
* `--rpc-url <RPC_URL>` — RPC server endpoint
* `--network-passphrase <NETWORK_PASSPHRASE>` — Network passphrase to sign the transaction sent to the rpc server
* `--network <NETWORK>` — Name of network to use from config
* `--global` — Use global config
* `--config-dir <CONFIG_DIR>` — Location of config directory, default is "."



## `stellar xdr`

Decode and encode XDR
Expand Down
49 changes: 47 additions & 2 deletions cmd/crates/soroban-test/tests/it/integration/tx.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use soroban_rpc::GetTransactionResponse;
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;
Expand Down Expand Up @@ -49,3 +50,47 @@ async fn txn_hash() {

assert_eq!(hash.trim(), expected_hash);
}

#[tokio::test]
async fn 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}");
leighmcculloch marked this conversation as resolved.
Show resolved Hide resolved
let tx_env = TransactionEnvelope::from_xdr_base64(&xdr_base64, Limits::none()).unwrap();
let tx_env = sign_manually(sandbox, &tx_env);

println!(
"Transaction to send:\n{}",
tx_env.to_xdr_base64(Limits::none()).unwrap()
);

let rpc_result = send_manually(sandbox, &tx_env).await;
assert_eq!(rpc_result.status, "SUCCESS");
}

async fn send_manually(sandbox: &TestEnv, tx_env: &TransactionEnvelope) -> GetTransactionResponse {
let client = soroban_rpc::Client::new(&sandbox.rpc_url).unwrap();
client.send_transaction_polling(tx_env).await.unwrap()
}

fn sign_manually(sandbox: &TestEnv, tx_env: &TransactionEnvelope) -> TransactionEnvelope {
TransactionEnvelope::from_xdr_base64(
sandbox
.new_assert_cmd("tx")
.arg("sign")
.arg("--sign-with-key=test")
.write_stdin(tx_env.to_xdr_base64(Limits::none()).unwrap().as_bytes())
.assert()
.success()
.stdout_as_str(),
Limits::none(),
)
.unwrap()
}
6 changes: 6 additions & 0 deletions cmd/soroban-cli/src/commands/tx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use clap::Parser;
use super::global;

pub mod hash;
pub mod sign;
pub mod simulate;
pub mod xdr;

Expand All @@ -12,6 +13,8 @@ pub enum Cmd {
Simulate(simulate::Cmd),
/// Calculate the hash of a transaction envelope from stdin
Hash(hash::Cmd),
/// Sign a transaction envelope appending the signature to the envelope
Sign(sign::Cmd),
}

#[derive(thiserror::Error, Debug)]
Expand All @@ -22,13 +25,16 @@ pub enum Error {
/// An error during hash calculation
#[error(transparent)]
Hash(#[from] hash::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::Hash(cmd) => cmd.run(global_args)?,
Cmd::Sign(cmd) => cmd.run(global_args).await?,
};
Ok(())
}
Expand Down
45 changes: 45 additions & 0 deletions cmd/soroban-cli/src/commands/tx/sign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use crate::{
commands::global,
config::{locator, network, sign_with},
xdr::{self, Limits, WriteXdr},
};

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
XdrArgs(#[from] super::xdr::Error),
#[error(transparent)]
Network(#[from] network::Error),
#[error(transparent)]
Locator(#[from] locator::Error),
#[error(transparent)]
SignWith(#[from] sign_with::Error),
#[error(transparent)]
Xdr(#[from] xdr::Error),
}

#[derive(Debug, clap::Parser, Clone)]
#[group(skip)]
pub struct Cmd {
#[command(flatten)]
pub sign_with: sign_with::Args,
#[command(flatten)]
pub network: network::Args,
#[command(flatten)]
pub locator: locator::Args,
}

impl Cmd {
#[allow(clippy::unused_async)]
pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
let tx_env = super::xdr::tx_envelope_from_stdin()?;
let tx_env_signed = self.sign_with.sign_tx_env(
tx_env,
&self.locator,
&self.network.get(&self.locator)?,
global_args.quiet,
)?;
println!("{}", tx_env_signed.to_xdr_base64(Limits::none())?.trim());
Ok(())
}
}
8 changes: 8 additions & 0 deletions cmd/soroban-cli/src/config/locator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ impl Args {
KeyType::Identity.read_with_global(name, &self.local_config()?)
}

pub fn key(&self, key_or_name: &str) -> Result<Secret, Error> {
if let Ok(signer) = key_or_name.parse::<Secret>() {
Ok(signer)
} else {
self.read_identity(key_or_name)
}
}

pub fn read_network(&self, name: &str) -> Result<Network, Error> {
let res = KeyType::Network.read_with_global(name, &self.local_config()?);
if let Err(Error::ConfigMissing(_, _)) = &res {
Expand Down
14 changes: 9 additions & 5 deletions cmd/soroban-cli/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize};
use soroban_rpc::Client;

use crate::{
signer,
print::Print,
signer::{self, LocalKey, Signer, SignerKind},
xdr::{Transaction, TransactionEnvelope},
Pwd,
};
Expand All @@ -18,6 +19,7 @@ pub mod data;
pub mod locator;
pub mod network;
pub mod secret;
pub mod sign_with;
pub mod upgrade_check;

#[derive(thiserror::Error, Debug)]
Expand Down Expand Up @@ -65,10 +67,12 @@ impl Args {
#[allow(clippy::unused_async)]
pub async fn sign(&self, tx: Transaction) -> Result<TransactionEnvelope, Error> {
let key = self.key_pair()?;
let Network {
network_passphrase, ..
} = &self.get_network()?;
Ok(signer::sign_tx(&key, &tx, network_passphrase)?)
let network = &self.get_network()?;
let signer = Signer {
kind: SignerKind::Local(LocalKey::new(key, false)),
printer: Print::new(false),
};
Ok(signer.sign_tx(tx, network)?)
}

pub async fn sign_soroban_authorizations(
Expand Down
20 changes: 19 additions & 1 deletion cmd/soroban-cli/src/config/secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ use serde::{Deserialize, Serialize};
use std::{io::Write, str::FromStr};
use stellar_strkey::ed25519::{PrivateKey, PublicKey};

use crate::utils;
use crate::print::Print;
use crate::{
signer::{self, LocalKey, Signer, SignerKind},
utils,
};

#[derive(thiserror::Error, Debug)]
pub enum Error {
Expand All @@ -21,6 +25,8 @@ pub enum Error {
Ed25519(#[from] ed25519_dalek::SignatureError),
#[error("Invalid address {0}")]
InvalidAddress(String),
#[error(transparent)]
Signer(#[from] signer::Error),
}

#[derive(Debug, clap::Args, Clone)]
Expand Down Expand Up @@ -120,6 +126,18 @@ impl Secret {
)?)
}

pub fn signer(&self, index: Option<usize>, prompt: bool, quiet: bool) -> Result<Signer, Error> {
let kind = match self {
Secret::SecretKey { .. } | Secret::SeedPhrase { .. } => {
SignerKind::Local(LocalKey::new(self.key_pair(index)?, prompt))
}
};
Ok(Signer {
kind,
printer: Print::new(quiet),
})
}

pub fn key_pair(&self, index: Option<usize>) -> Result<ed25519_dalek::SigningKey, Error> {
Ok(utils::into_signing_key(&self.private_key(index)?))
}
Expand Down
61 changes: 61 additions & 0 deletions cmd/soroban-cli/src/config/sign_with.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use crate::{signer, xdr::TransactionEnvelope};
use clap::arg;

use super::{
locator,
network::{self, Network},
secret,
};

#[derive(thiserror::Error, Debug)]
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,
#[error(transparent)]
StrKey(#[from] stellar_strkey::DecodeError),
}

#[derive(Debug, clap::Args, Clone, Default)]
#[group(skip)]
pub struct Args {
/// 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", env = "STELLAR_SIGN_WITH_KEY")]
pub sign_with_key: Option<String>,
leighmcculloch marked this conversation as resolved.
Show resolved Hide resolved
/// Sign with <https://lab.stellar.org>
#[arg(
long,
conflicts_with = "sign_with_key",
env = "STELLAR_SIGN_WITH_LAB",
hide = true
)]
pub sign_with_lab: bool,
leighmcculloch marked this conversation as resolved.
Show resolved Hide resolved

#[arg(long, conflicts_with = "sign_with_lab")]
/// 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<usize>,
}

impl Args {
pub fn sign_tx_env(
&self,
tx: TransactionEnvelope,
locator: &locator::Args,
network: &Network,
quiet: bool,
) -> Result<TransactionEnvelope, Error> {
let key_or_name = self.sign_with_key.as_deref().ok_or(Error::NoSignWithKey)?;
let secret = locator.key(key_or_name)?;
let signer = secret.signer(self.hd_path, false, quiet)?;
Ok(signer.sign_tx_env(tx, network)?)
}
}
3 changes: 1 addition & 2 deletions cmd/soroban-cli/src/print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ use std::{env, fmt::Display};
use soroban_env_host::xdr::{Error as XdrError, Transaction};

use crate::{
config::network::Network,
utils::{explorer_url_for_transaction, transaction_hash},
config::network::Network, utils::explorer_url_for_transaction, utils::transaction_hash,
};

const TERMS: &[&str] = &["Apple_Terminal", "vscode"];
Expand Down
Loading
Loading