Skip to content

Latest commit

 

History

History
209 lines (159 loc) · 7.86 KB

File metadata and controls

209 lines (159 loc) · 7.86 KB

Preamble

SEP: 0053
Title: Sign and Verify Messages
Author: Jun Luo (@overcat), Pamphile Roy (@tupui), OrbitLens (@orbitlens), Piyal Basu (@piyalbasu)
Status: Draft
Created: 2025-02-01
Updated: 2025-02-01
Version: 0.0.1
Discussion: https://github.com/stellar/stellar-protocol/discussions/1641

Simple Summary

This SEP proposes a canonical method for signing and verifying arbitrary messages using Stellar key pairs. It aims to standardize message signing functionality across various Stellar wallets, libraries, and services, preventing ecosystem fragmentation and ensuring interoperability.

Abstract

Stellar uses ed25519 keys for transaction signatures by design, but there is currently no canonical specification for signing arbitrary messages outside the normal transaction flow. This proposal defines:

  • A message format supporting user-supplied data in various encodings
  • SHA-256 as the standard hashing function
  • Standardized procedures for signing and verifying messages off-chain

By adopting this SEP, developers can seamlessly incorporate message signing capabilities for multi-lingual text or arbitrary binary data, enabling proof-of-ownership and authentication in off-chain scenarios such as social platform verification, cross-chain operations, and general data validation.

Motivation

Many blockchain ecosystems provide "Sign Message" capabilities for proving key ownership outside of normal transactions. While Stellar has the fundamental cryptographic primitives, there is no official, widely adopted protocol for signing arbitrary messages. Without standardization of how bytes are composed, hashed, and verified, different implementations risk incompatibility.

This functionality is particularly useful for:

  • Proving control of a Stellar address on social platforms
  • Verifying off-chain agreements or terms
  • Integrating with cross-chain or multi-chain dApps that require address proof

Specification

Prefix and Encodings

To prevent confusion with raw transactions and to mitigate replay attacks, a fixed prefix string is used: "Stellar Signed Message:\n",

The implementation MUST handle the user-provided message in the way it is supplied:

  • If the message is provided as a string, it SHOULD be interpreted as UTF-8 text.
  • If the message is provided as raw bytes, it SHOULD be processed as binary data.

A signed message can be any data, but human-readable text is generally recommended as it is easier for users to verify visually. Wallets and libraries MUST clearly display or otherwise confirm the content being signed, especially for complex or user-supplied data.

Message Format

The canonical signing payload is constructed by concatenating: <prefixBytes><message>.

Where:

  • <prefixBytes>: Fixed UTF-8 encoded string "Stellar Signed Message:\n".
  • <messageBytes>: The byte representation of the message. If the input was a string, it should be UTF-8 encoded. If the input was already bytes, no further conversion is needed.

Why "Stellar Signed Message:\n"

Bitcoin uses the prefix "Bitcoin Signed Message:\n" and Ethereum uses "Ethereum Signed Message:\n" for their respective message signing implementations. By adopting a similar format, Stellar's off-chain message signing maintains consistency with established approaches in other blockchains, making it easier for developers with multi-chain experience to understand and implement.

Hashing Algorithm

This proposal standardizes on single-round SHA-256 for hashing: messageHash = SHA256(encodedMessage). This approach is widely regarded as secure and efficient.

Signing Procedure

  1. Convert the message to a byte array if it is a string, using UTF-8 encoding.
  2. Serialize the prefix (bytes) and the user’s message (bytes) as described (prefix + message).
  3. Compute messageHash = SHA256(encodedMessage).
  4. Sign messageHash using the Stellar private key (ed25519). This yields a 64-byte signature.

Verification Procedure

  1. Convert the message to a byte array if it is a string (same UTF-8 method used during signing).
  2. Reconstruct the same canonical payload using the known prefix and the byte array message.
  3. Compute messageHash = SHA256(encodedMessage).
  4. Use the corresponding public key to verify the 64-byte ed25519 signature.
  5. If the signature matches, verification is successful; otherwise, it fails.

Handling Multi-language and Binary Data

  • Libraries SHOULD accept either a string or a byte array for the message.
  • If a string is provided, libraries MUST use UTF-8 encoding internally to get the byte array.
  • If binary data is provided, libraries MUST preserve the raw byte sequence (no extra encoding steps).
  • Wallet UIs can choose how to display non-ASCII or non-printable data (e.g., by rendering hex or base64). They SHOULD clearly communicate the data format to the user.
  • Visual verification for end users is critically important, we generally recommend using human-readable text (e.g., UTF-8–encoded strings) for most use cases.

Reference Implementation (Pseudo-Code)

function encodeMessage(message):
   prefix = "Stellar Signed Message:\n"
   # Convert message to bytes (if needed)
   if isString(message):
       messageBytes = utf8Encode(message)
   else:
       messageBytes = message
   return prefixBytes + messageBytes

function stellarSignMessage(privateKey, message):
   signedMessageBase = encodeMessage(message)
   messageHash = SHA256(encodedPayload)
   signature = ed25519_sign(privateKey, messageHash)
   return signature

function stellarVerifyMessage(publicKey, message, signature):
   signedMessageBase = encodeMessage(message)
   messageHash = SHA256(encodedPayload)
   return ed25519_verify(publicKey, messageHash, signature)

Test cases

  1. Sign a simple ASCII message
  • Message: Hello, World!
  • Seed: SAKICEVQLYWGSOJS4WW7HZJWAHZVEEBS527LHK5V4MLJALYKICQCJXMW
  • Address: GBXFXNDLV4LSWA4VB7YIL5GBD7BVNR22SGBTDKMO2SBZZHDXSKZYCP7L
  • Signature:
    • base64 encoded: fO5dbYhXUhBMhe6kId/cuVq/AfEnHRHEvsP8vXh03M1uLpi5e46yO2Q8rEBzu3feXQewcQE5GArp88u6ePK6BA==
    • hex encoded: 7cee5d6d885752104c85eea421dfdcb95abf01f1271d11c4bec3fcbd7874dccd6e2e98b97b8eb23b643cac4073bb77de5d07b0710139180ae9f3cbba78f2ba04
  1. Sign a Japanese message
  • Message: こんにちは、世界!
  • Seed: SAKICEVQLYWGSOJS4WW7HZJWAHZVEEBS527LHK5V4MLJALYKICQCJXMW
  • Address: GBXFXNDLV4LSWA4VB7YIL5GBD7BVNR22SGBTDKMO2SBZZHDXSKZYCP7L
  • Signature:
    • base64 encoded: CDU265Xs8y3OWbB/56H9jPgUss5G9A0qFuTqH2zs2YDgTm+++dIfmAEceFqB7bhfN3am59lCtDXrCtwH2k1GBA==
    • hex encoded: 083536eb95ecf32dce59b07fe7a1fd8cf814b2ce46f40d2a16e4ea1f6cecd980e04e6fbef9d21f98011c785a81edb85f3776a6e7d942b435eb0adc07da4d4604
  1. Sign a binary message
  • Message (base64 encoded): 2zZDP1sa1BVBfLP7TeeMk3sUbaxAkUhBhDiNdrksaFo=
  • Seed: SAKICEVQLYWGSOJS4WW7HZJWAHZVEEBS527LHK5V4MLJALYKICQCJXMW
  • Address: GBXFXNDLV4LSWA4VB7YIL5GBD7BVNR22SGBTDKMO2SBZZHDXSKZYCP7L
  • Signature:
    • base64 encoded: VA1+7hefNwv2NKScH6n+Sljj15kLAge+M2wE7fzFOf+L0MMbssA1mwfJZRyyrhBORQRle10X1Dxpx+UOI4EbDQ==
    • hex encoded: 540d7eee179f370bf634a49c1fa9fe4a58e3d7990b0207be336c04edfcc539ff8bd0c31bb2c0359b07c9651cb2ae104e4504657b5d17d43c69c7e50e23811b0d

Limitations

  • Ownership of a private key does not imply control of the account. Even though signatures prove that a signer controls a private key corresponding to a particular address, that does not necessarily mean they control the corresponding Stellar account. In multi-signer scenarios, possession of just one key may not grant full control over the account.

Backwards Compatibility

This is a new standard that does not conflict with existing SEPs, though it provides off-chain signature functionality distinct from transaction signing methods.

Acknowledgments

  • Inspired by message signing patterns in Bitcoin, Ethereum, etc.

References