|
| 1 | +## Preamble |
| 2 | + |
| 3 | +``` |
| 4 | +SEP: 0053 |
| 5 | +Title: Sign and Verify Messages |
| 6 | +Author: Jun Luo (@overcat), Pamphile Roy (@tupui), OrbitLens (@orbitlens), Piyal Basu (@piyalbasu) |
| 7 | +Track: Standard |
| 8 | +Status: Draft |
| 9 | +Created: 2025-02-01 |
| 10 | +Updated: 2025-02-01 |
| 11 | +Version: 0.0.1 |
| 12 | +Discussion: https://github.com/stellar/stellar-protocol/discussions/1641 |
| 13 | +``` |
| 14 | + |
| 15 | +## Simple Summary |
| 16 | + |
| 17 | +This SEP proposes a canonical method for signing and verifying arbitrary |
| 18 | +messages using Stellar key pairs. It aims to standardize message signing |
| 19 | +functionality across various Stellar wallets, libraries, and services, |
| 20 | +preventing ecosystem fragmentation and ensuring interoperability. |
| 21 | + |
| 22 | +## Abstract |
| 23 | + |
| 24 | +Stellar uses ed25519 keys for transaction signatures by design, but there is |
| 25 | +currently no canonical specification for signing arbitrary messages outside the |
| 26 | +normal transaction flow. This proposal defines: |
| 27 | + |
| 28 | +- A message format supporting user-supplied data in various encodings |
| 29 | +- SHA-256 as the standard hashing function |
| 30 | +- Standardized procedures for signing and verifying messages off-chain |
| 31 | + |
| 32 | +By adopting this SEP, developers can seamlessly incorporate message signing |
| 33 | +capabilities for multi-lingual text or arbitrary binary data, enabling |
| 34 | +proof-of-ownership and authentication in off-chain scenarios such as social |
| 35 | +platform verification, cross-chain operations, and general data validation. |
| 36 | + |
| 37 | +## Motivation |
| 38 | + |
| 39 | +Many blockchain ecosystems provide "Sign Message" capabilities for proving key |
| 40 | +ownership outside of normal transactions. While Stellar has the fundamental |
| 41 | +cryptographic primitives, there is no official, widely adopted protocol for |
| 42 | +signing arbitrary messages. Without standardization of how bytes are composed, |
| 43 | +hashed, and verified, different implementations risk incompatibility. |
| 44 | + |
| 45 | +This functionality is particularly useful for: |
| 46 | + |
| 47 | +- Proving control of a Stellar address on social platforms |
| 48 | +- Verifying off-chain agreements or terms |
| 49 | +- Integrating with cross-chain or multi-chain dApps that require address proof |
| 50 | + |
| 51 | +## Specification |
| 52 | + |
| 53 | +### Prefix and Encodings |
| 54 | + |
| 55 | +To prevent confusion with raw transactions and to mitigate replay attacks, a |
| 56 | +fixed prefix string is used: `"Stellar Signed Message:\n"`, |
| 57 | + |
| 58 | +The implementation MUST handle the user-provided message in the way it is |
| 59 | +supplied: |
| 60 | + |
| 61 | +- If the message is provided as a string, it SHOULD be interpreted as UTF-8 |
| 62 | + text. |
| 63 | +- If the message is provided as raw bytes, it SHOULD be processed as binary |
| 64 | + data. |
| 65 | + |
| 66 | +A signed message can be any data, but human-readable text is generally |
| 67 | +recommended as it is easier for users to verify visually. Wallets and libraries |
| 68 | +MUST clearly display or otherwise confirm the content being signed, especially |
| 69 | +for complex or user-supplied data. |
| 70 | + |
| 71 | +### Message Format |
| 72 | + |
| 73 | +The canonical signing payload is constructed by concatenating: |
| 74 | +`<prefixBytes><message>`. |
| 75 | + |
| 76 | +Where: |
| 77 | + |
| 78 | +- `<prefixBytes>`: Fixed UTF-8 encoded string `"Stellar Signed Message:\n"`. |
| 79 | +- `<messageBytes>`: The byte representation of the message. If the input was a |
| 80 | + string, it should be UTF-8 encoded. If the input was already bytes, no |
| 81 | + further conversion is needed. |
| 82 | + |
| 83 | +#### Why `"Stellar Signed Message:\n"` |
| 84 | + |
| 85 | +Bitcoin uses the prefix `"Bitcoin Signed Message:\n"` and Ethereum uses |
| 86 | +`"Ethereum Signed Message:\n"` for their respective message signing |
| 87 | +implementations. By adopting a similar format, Stellar's off-chain message |
| 88 | +signing maintains consistency with established approaches in other blockchains, |
| 89 | +making it easier for developers with multi-chain experience to understand and |
| 90 | +implement. |
| 91 | + |
| 92 | +### Hashing Algorithm |
| 93 | + |
| 94 | +This proposal standardizes on single-round `SHA-256` for hashing: |
| 95 | +`messageHash = SHA256(encodedMessage)`. This approach is widely regarded as |
| 96 | +secure and efficient. |
| 97 | + |
| 98 | +### Signing Procedure |
| 99 | + |
| 100 | +1. Convert the message to a byte array if it is a string, using UTF-8 encoding. |
| 101 | +2. Serialize the prefix (bytes) and the user’s message (bytes) as described |
| 102 | + (`prefix + message`). |
| 103 | +3. Compute `messageHash = SHA256(encodedMessage)`. |
| 104 | +4. Sign `messageHash` using the Stellar private key (ed25519). This yields a |
| 105 | + 64-byte signature. |
| 106 | + |
| 107 | +### Verification Procedure |
| 108 | + |
| 109 | +1. Convert the message to a byte array if it is a string (same UTF-8 method |
| 110 | + used during signing). |
| 111 | +2. Reconstruct the same canonical payload using the known prefix and the byte |
| 112 | + array message. |
| 113 | +3. Compute `messageHash = SHA256(encodedMessage)`. |
| 114 | +4. Use the corresponding public key to verify the 64-byte ed25519 signature. |
| 115 | +5. If the signature matches, verification is successful; otherwise, it fails. |
| 116 | + |
| 117 | +### Handling Multi-language and Binary Data |
| 118 | + |
| 119 | +- Libraries SHOULD accept either a string or a byte array for the message. |
| 120 | +- If a string is provided, libraries MUST use UTF-8 encoding internally to get |
| 121 | + the byte array. |
| 122 | +- If binary data is provided, libraries MUST preserve the raw byte sequence (no |
| 123 | + extra encoding steps). |
| 124 | +- Wallet UIs can choose how to display non-ASCII or non-printable data (e.g., |
| 125 | + by rendering hex or base64). They SHOULD clearly communicate the data format |
| 126 | + to the user. |
| 127 | +- Visual verification for end users is critically important, we generally |
| 128 | + recommend using human-readable text (e.g., UTF-8–encoded strings) for most |
| 129 | + use cases. |
| 130 | + |
| 131 | +### Reference Implementation (Pseudo-Code) |
| 132 | + |
| 133 | +```pseudo |
| 134 | +function encodeMessage(message): |
| 135 | + prefix = "Stellar Signed Message:\n" |
| 136 | + # Convert message to bytes (if needed) |
| 137 | + if isString(message): |
| 138 | + messageBytes = utf8Encode(message) |
| 139 | + else: |
| 140 | + messageBytes = message |
| 141 | + return prefixBytes + messageBytes |
| 142 | +
|
| 143 | +function stellarSignMessage(privateKey, message): |
| 144 | + signedMessageBase = encodeMessage(message) |
| 145 | + messageHash = SHA256(encodedPayload) |
| 146 | + signature = ed25519_sign(privateKey, messageHash) |
| 147 | + return signature |
| 148 | +
|
| 149 | +function stellarVerifyMessage(publicKey, message, signature): |
| 150 | + signedMessageBase = encodeMessage(message) |
| 151 | + messageHash = SHA256(encodedPayload) |
| 152 | + return ed25519_verify(publicKey, messageHash, signature) |
| 153 | +``` |
| 154 | + |
| 155 | +### Test cases |
| 156 | + |
| 157 | +1. Sign a simple ASCII message |
| 158 | + |
| 159 | +- Message: `Hello, World!` |
| 160 | +- Seed: `SAKICEVQLYWGSOJS4WW7HZJWAHZVEEBS527LHK5V4MLJALYKICQCJXMW` |
| 161 | +- Address: `GBXFXNDLV4LSWA4VB7YIL5GBD7BVNR22SGBTDKMO2SBZZHDXSKZYCP7L` |
| 162 | +- Signature (base64 encoded): |
| 163 | + `fO5dbYhXUhBMhe6kId/cuVq/AfEnHRHEvsP8vXh03M1uLpi5e46yO2Q8rEBzu3feXQewcQE5GArp88u6ePK6BA==` |
| 164 | + |
| 165 | +2. Sign a Japanese message |
| 166 | + |
| 167 | +- Message: `こんにちは、世界!` |
| 168 | +- Seed: `SAKICEVQLYWGSOJS4WW7HZJWAHZVEEBS527LHK5V4MLJALYKICQCJXMW` |
| 169 | +- Address: `GBXFXNDLV4LSWA4VB7YIL5GBD7BVNR22SGBTDKMO2SBZZHDXSKZYCP7L` |
| 170 | +- Signature (base64 encoded): |
| 171 | + `CDU265Xs8y3OWbB/56H9jPgUss5G9A0qFuTqH2zs2YDgTm+++dIfmAEceFqB7bhfN3am59lCtDXrCtwH2k1GBA==` |
| 172 | + |
| 173 | +3. Sign a binary message |
| 174 | + |
| 175 | +- Message (base64 encoded): `2zZDP1sa1BVBfLP7TeeMk3sUbaxAkUhBhDiNdrksaFo=` |
| 176 | +- Seed: `SAKICEVQLYWGSOJS4WW7HZJWAHZVEEBS527LHK5V4MLJALYKICQCJXMW` |
| 177 | +- Address: `GBXFXNDLV4LSWA4VB7YIL5GBD7BVNR22SGBTDKMO2SBZZHDXSKZYCP7L` |
| 178 | +- Signature (base64 encoded): |
| 179 | + `VA1+7hefNwv2NKScH6n+Sljj15kLAge+M2wE7fzFOf+L0MMbssA1mwfJZRyyrhBORQRle10X1Dxpx+UOI4EbDQ==` |
| 180 | + |
| 181 | +## Limitations |
| 182 | + |
| 183 | +- Ownership of a private key does not imply control of the account. Even though |
| 184 | + signatures prove that a signer controls a private key corresponding to a |
| 185 | + particular address, that does not necessarily mean they control the |
| 186 | + corresponding Stellar account. In multi-signer scenarios, possession of just |
| 187 | + one key may not grant full control over the account. |
| 188 | + |
| 189 | +## Backwards Compatibility |
| 190 | + |
| 191 | +This is a new standard that does not conflict with existing SEPs, though it |
| 192 | +provides off-chain signature functionality distinct from transaction signing |
| 193 | +methods. |
| 194 | + |
| 195 | +## Acknowledgments |
| 196 | + |
| 197 | +- Inspired by message signing patterns in Bitcoin, Ethereum, etc. |
| 198 | + |
| 199 | +## References |
| 200 | + |
| 201 | +- https://github.com/bitcoin/bips/blob/master/bip-0137.mediawiki |
0 commit comments