Skip to content
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...
Comment thread
mootz12 marked this conversation as resolved.
Outdated
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
Loading