Skip to content

Feature Request: Add SEP-53 message signing and verification to Keypair #1492

Description

@tomerweller

Summary

Add support for signing and verifying arbitrary messages per SEP-53 to the Keypair class.

This feature is already implemented in:

  • Python SDK (py-stellar-base) - Keypair.sign_message() / Keypair.verify_message()
  • Java SDK (java-stellar-sdk) - KeyPair.signMessage() / KeyPair.verifyMessage()
  • Stellar CLI (stellar-cli) - stellar message sign / stellar message verify

The JavaScript SDK should have parity with other Stellar SDKs.

Motivation

SEP-53 enables proving Stellar address ownership without on-chain transactions, useful for:

  • Proof of ownership: Verifying control of a Stellar address on social platforms, Discord bots, etc.
  • Off-chain agreements: Signing messages for contractual purposes
  • Cross-chain integrations: Verifying Stellar address ownership in multi-chain applications
  • Authentication: Using Stellar keys as an authentication mechanism (similar to "Sign in with Ethereum")

Proposed API

Following the pattern established by other SDKs:

// Signing a message
const keypair = StellarBase.Keypair.fromSecret('S...');
const signature = keypair.signMessage('Hello, World!');
// Returns: Buffer containing 64-byte ed25519 signature

// Verifying a message
const publicKeypair = StellarBase.Keypair.fromPublicKey('G...');
const isValid = publicKeypair.verifyMessage('Hello, World!', signature);
// Returns: boolean

// Binary message support
const binaryMessage = Buffer.from([0x01, 0x02, 0x03]);
const sig = keypair.signMessage(binaryMessage);

Implementation Details

Per SEP-53, the signing process is:

  1. Convert message to bytes (UTF-8 if string)
  2. Prepend the fixed prefix: "Stellar Signed Message:\n"
  3. Compute hash = SHA256(prefix + message)
  4. Sign hash with ed25519 private key
  5. Return 64-byte signature

Suggested Implementation

In src/keypair.js:

const MESSAGE_PREFIX = Buffer.from('Stellar Signed Message:\n');

/**
 * Sign an arbitrary message according to SEP-53.
 * @param {string|Buffer} message - The message to sign
 * @returns {Buffer} 64-byte ed25519 signature
 * @see https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md
 */
signMessage(message) {
  if (!this.canSign()) {
    throw new Error('Cannot sign: no secret key available');
  }
  const messageHash = this._calculateMessageHash(message);
  return this.sign(messageHash);
}

/**
 * Verify a SEP-53 signed message.
 * @param {string|Buffer} message - The original message
 * @param {Buffer} signature - The signature to verify
 * @returns {boolean} True if signature is valid
 */
verifyMessage(message, signature) {
  const messageHash = this._calculateMessageHash(message);
  return this.verify(messageHash, signature);
}

/**
 * Calculate the SEP-53 message hash.
 * @private
 */
_calculateMessageHash(message) {
  const messageBytes = typeof message === 'string' 
    ? Buffer.from(message, 'utf8') 
    : Buffer.from(message);
  const payload = Buffer.concat([MESSAGE_PREFIX, messageBytes]);
  return hash(payload); // Uses existing hash function (SHA-256)
}

Test Vectors

SEP-53 provides test vectors that should be used for validation:

Message Expected Signature (base64)
Hello, World! fO5dbYhXUhBMhe6kId/cuVq/AfEnHRHEvsP8vXh03M1uLpi5e46yO2Q8rEBzu3feXQewcQE5GArp88u6ePK6BA==
こんにちは、世界! CDU265Xs8y3OWbB/56H9jPgUss5G9A0qFuTqH2zs2YDgTm+++dIfmAEceFqB7bhfN3am59lCtDXrCtwH2k1GBA==

(Using public key GBXFXNDLV4LSWA4VB7YIL5GBD7BVNR22SGBTDKMO2SBZZHDXSKZYCP7L)

TypeScript Types

declare class Keypair {
  // ... existing methods ...
  
  /**
   * Sign an arbitrary message according to SEP-53.
   * @param message - The message to sign (string or Buffer)
   * @returns 64-byte signature as Buffer
   * @throws Error if keypair cannot sign (no secret key)
   */
  signMessage(message: string | Buffer): Buffer;
  
  /**
   * Verify a SEP-53 signed message.
   * @param message - The original message
   * @param signature - The signature to verify
   * @returns true if valid, false otherwise
   */
  verifyMessage(message: string | Buffer, signature: Buffer): boolean;
}

References

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status
Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions