Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions cmd/crates/soroban-test/tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod init;
#[cfg(feature = "it")]
mod integration;
mod log;
mod message;
mod plugin;
mod rpc_provider;
mod strkey;
Expand Down
177 changes: 177 additions & 0 deletions cmd/crates/soroban-test/tests/it/message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
use soroban_test::{AssertExt, TestEnv};

#[tokio::test]
async fn sep_53_sign_message_and_verify() {
let sandbox = &TestEnv::new();

let message = "Hello, World!";
let expected_signature =
"fO5dbYhXUhBMhe6kId/cuVq/AfEnHRHEvsP8vXh03M1uLpi5e46yO2Q8rEBzu3feXQewcQE5GArp88u6ePK6BA==";
let wrong_signature =
"CDU265Xs8y3OWbB/56H9jPgUss5G9A0qFuTqH2zs2YDgTm+++dIfmAEceFqB7bhfN3am59lCtDXrCtwH2k1GBA==";
let secret_key = "SAKICEVQLYWGSOJS4WW7HZJWAHZVEEBS527LHK5V4MLJALYKICQCJXMW";
let public_key = "GBXFXNDLV4LSWA4VB7YIL5GBD7BVNR22SGBTDKMO2SBZZHDXSKZYCP7L";
let wrong_public_key = "GAREAZZQWHOCBJS236KIE3AWYBVFLSBK7E5UW3ICI3TCRWQKT5LNLCEZ";

let output = sandbox
.new_assert_cmd("message")
.args(["sign", message, "--sign-with-key", secret_key])
.assert()
.success()
.stdout_as_str();
assert_eq!(output.trim(), expected_signature);

sandbox
.new_assert_cmd("message")
.args([
"verify",
message,
"--signature",
expected_signature,
"--public-key",
public_key,
])
.assert()
.success();

// wrong signature
sandbox
.new_assert_cmd("message")
.args([
"verify",
message,
"--signature",
wrong_signature,
"--public-key",
public_key,
])
.assert()
.failure();

// wrong public key
sandbox
.new_assert_cmd("message")
.args([
"verify",
message,
"--signature",
expected_signature,
"--public-key",
wrong_public_key,
])
.assert()
.failure();
}

#[tokio::test]
async fn sep_53_sign_message_and_verify_stdin() {
let sandbox = &TestEnv::new();

let message = "Hello, World!";
let expected_signature =
"fO5dbYhXUhBMhe6kId/cuVq/AfEnHRHEvsP8vXh03M1uLpi5e46yO2Q8rEBzu3feXQewcQE5GArp88u6ePK6BA==";
let secret_key = "SAKICEVQLYWGSOJS4WW7HZJWAHZVEEBS527LHK5V4MLJALYKICQCJXMW";
let public_key = "GBXFXNDLV4LSWA4VB7YIL5GBD7BVNR22SGBTDKMO2SBZZHDXSKZYCP7L";

// sandbox
// .new_assert_cmd("keys")
// .args(["add", alias_secret, "--secret-key", secret_key])
// .assert()
// .success();
// sandbox
// .new_assert_cmd("keys")
// .args(["add", alias_public, "--public-key", public_key])
// .assert()
// .success();

let output = sandbox
.new_assert_cmd("message")
.write_stdin(message)
.args(["sign", "--sign-with-key", secret_key])
.assert()
.success()
.stdout_as_str();
assert_eq!(output.trim(), expected_signature);

sandbox
.new_assert_cmd("message")
.write_stdin(message)
.args([
"verify",
"--signature",
expected_signature,
"--public-key",
public_key,
])
.assert()
.success();
}

#[tokio::test]
async fn sep_53_sign_message_and_verify_with_alias() {
let sandbox = &TestEnv::new();

let message = "Hello, World!";
let expected_signature =
"fO5dbYhXUhBMhe6kId/cuVq/AfEnHRHEvsP8vXh03M1uLpi5e46yO2Q8rEBzu3feXQewcQE5GArp88u6ePK6BA==";
let public_key = "GBXFXNDLV4LSWA4VB7YIL5GBD7BVNR22SGBTDKMO2SBZZHDXSKZYCP7L";

// generate a new secret "alice" and a public alias "bob" of the example pubkey
sandbox
.new_assert_cmd("keys")
.args(["generate", "alice"])
.assert()
.success();
sandbox
.new_assert_cmd("keys")
.args(["add", "bob", "--public-key", public_key])
.assert()
.success();

// since this is randomly generated, just validate the output matches for alice
let alice_signature = sandbox
.new_assert_cmd("message")
.write_stdin(message)
.args(["sign", "--sign-with-key", "alice"])
.assert()
.success()
.stdout_as_str();
sandbox
.new_assert_cmd("message")
.write_stdin(message)
.args([
"verify",
"--signature",
&alice_signature,
"--public-key",
"alice",
])
.assert()
.success();

// validate a public key alias works for validation
sandbox
.new_assert_cmd("message")
.write_stdin(message)
.args([
"verify",
"--signature",
expected_signature,
"--public-key",
"bob",
])
.assert()
.success();
sandbox
.new_assert_cmd("message")
.write_stdin(message)
.args([
"verify",
"--signature",
&alice_signature,
"--public-key",
"bob",
])
.assert()
.failure();
}
48 changes: 48 additions & 0 deletions cmd/soroban-cli/src/commands/message/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use crate::commands::global;

pub mod sign;
pub mod verify;

/// The prefix used for SEP-53 message signing.
/// See: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md
pub const SEP53_PREFIX: &str = "Stellar Signed Message:\n";

#[derive(Debug, clap::Subcommand)]
pub enum Cmd {
/// Sign an arbitrary message using SEP-53
///
/// Signs a message following the SEP-53 specification for arbitrary message signing.
/// The provided message will get prefixed with "Stellar Signed Message:\n", hashed with SHA-256,
/// and signed with the ed25519 private key.
///
/// Example: stellar message sign "Hello, World!" --sign-with-key alice
Sign(sign::Cmd),

/// Verify a SEP-53 signed message
///
/// Verifies that a signature was produced by the holder of the private key
/// corresponding to the given account public key, following the SEP-53 specification. The
/// provided message will get prefixed with "Stellar Signed Message:\n" before verification.
///
/// Example: stellar message verify "Hello, World!" --signature <BASE64_SIG> --account GABC...
Verify(verify::Cmd),
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Sign(#[from] sign::Error),

#[error(transparent)]
Verify(#[from] verify::Error),
}

impl Cmd {
pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
match self {
Cmd::Sign(cmd) => cmd.run(global_args).await?,
Cmd::Verify(cmd) => cmd.run(global_args)?,
}
Ok(())
}
}
Loading