Skip to content

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

@tomerweller

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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions